我有两个功能:myFunctionA()
和myFunctionB()
。myFunctionA()
返回一个Object
,其中包含具有字符串值的键Page_Type
。myFunctionB()
处理Object
返回的myFunctionA()
中的许多条目,包括Page_Type
及其字符串值。
后来,更新了myFunctionA()
,因此它不再返回包含键Page_Type
的对象,而是返回键Page_Types
-具有数组值的对象。
因此,现在还需要更新myFunctionB()
-它不再处理字符串的Page_Type
,而是数组的Page_Types
。
如果我理解正确(但我可能没有理解),则以上是依赖关系请求的示例,并且可以(通过我认为)部署依赖关系注入模式(甚至可能是服务定位器模式)来避免它引发的大量重构。代替(??)
但是,尽管阅读了这个主题,但我仍然不确定依赖注入如何在PHP或Javascript函数中工作(大部分解释涉及像C++
这样的编程语言和像Classes
这样的OOP概念,而我正在处理PHP和javascript中的第三方功能)。
有什么方法可以构造我的代码,以便(以任何重要方式)更新myFunctionA()
时将不需要我也更新myFunctionB()
(以及所有其他调用myFunctionA()
的函数-例如myFunctionC()
,myFunctionD()
,myFunctionE()
等)?
如果myFunctionH()
需要myFunctionG()
需要myFunctionF()
而又需要myFunctionA()
怎么办?我不想现在更新myFunctionA()
意味着必须再更新三个函数(F
,G
和H
)。
尝试回答:
我目前能想到的最佳答案-这可能不是最佳实践答案,因为我尚不知道是否存在与我在上述问题中描述的问题相对应的形式问题-以下是对我如何介绍设置:
我有两个(不可变的)函数:myFunctionA__v1_0()
和myFunctionB__v1_0()
。
myFunctionA__v1_0()
返回一个包含密钥的对象
Page_Type
具有字符串值。
myFunctionB__v1_0()
处理对象中的许多条目
由myFunctionA__v1_0()
返回,包括Page_Type
及其
字符串值。
后来,myFunctionA__v1_0()
仍然存在,但也被myFunctionA__v2_0()
所取代
返回一个包含键Page_Types
的对象-该键具有数组值。
为了使myFunctionB
访问myFunctionA__v2_0()
返回的对象,
现在还需要一个能够处理的myFunctionB__v1_1()
数组Page_Types
。
可以总结为:myFunctionB__v1_0()
需要myFunctionA__v1_0()
返回的对象myFunctionB__v1_1()
需要myFunctionA__v2_0()
返回的对象
由于每个函数在被正式命名后都变得不可变,所以永远不会发生的是,我们最终得到一个myFunctionB__v1_0()
要求myFunctionA__v2_0()
返回对象的例子。
我不知道我是否采用正确的方法,但这是我到目前为止提出的最佳方法。
最佳答案
在提供程序的编程中非常常见-即。 myFunctionA()
不了解其消费者myFunctionB()
。解决此问题的唯一正确方法是预先定义一个API,并且永远不要更改它;)
我看不到对消费者进行版本控制的目的-该原因必须是myFunctionB()
的“下游”-即。 myFunctionB()
的使用者,则myFunctionB()
的作者不受控制...在这种情况下,myFunctionB()
本身将成为提供者,而作者必须对此进行处理(也许使用与您相同的模式)...但这不是您要解决的问题。
至于您的提供者myFunctionA()
:如果您不能为数据本身预先定义接口/ API,即。您知道数据的结构将必须更改(以非向后兼容的方式),但是您不知道如何...那么您将需要以一种或另一种方式进行版本控制。
因为您看到了这种情况并从头开始计划,所以您比大多数人领先。
避免必须在某个时候对使用者myFunctionB()
进行更改的唯一方法是以向后兼容的方式对提供者myFunctionA()
进行所有更改。您描述的更改不向后兼容,因为myFunctionB()
可能不知道如何修改myFunctionA()
的新输出,而不能进行修改。
您提出的解决方案听起来应该可行。但是,至少有两个缺点:
它要求您保留不断增加的旧版功能列表,以防万一有任何使用者要求其数据。维护起来将变得非常复杂,从长远来看可能是不可能的。
根据将来需要进行的更改,可能根本无法再生成myFunctionA__v1_0()
的输出-在您的示例中,您可能会在系统中添加多个page_types
-在这种情况下,您可能只需要重写v1_0
以使用第一个,旧用户将感到高兴。但是,如果您决定完全从系统中删除page_types
的概念,则必须计划以一种或另一种方式完全删除v1_0
。因此,您需要建立一种将其传达给消费者的方法。
解决此问题的唯一正确方法是预先定义一个API,并且永远不要更改它。
由于我们已经确定:
您将不得不进行向后不兼容的更改
您对消费者一无所知,也无权在需要时进行更改
我建议您定义一个不可变的API,而不是为您的数据定义一个不可变的API,该API允许您在消费者应该或必须升级时与他们进行通信。
这听起来可能很复杂,但是不必:
在提供程序中接受版本参数:
想法是让使用者明确告知提供者要返回哪个版本。
提供程序可能看起来像这样:
function myFunctionA(string $version) {
$page_types = ['TypeA', 'TypeB'];
$page = new stdClass();
$page->title = 'Page title';
switch ($version) {
case '1.0':
$page->error = 'Version 1.0 no longer available. Please upgrade!';
break;
case '1.1':
$page->page_type = $page_types[0];
$page->warning = 'Deprecated version. Please upgrade!';
break;
case '2.0':
$page->page_types = $page_types;
break;
default:
$page->error = 'Unknown version: ' . $version;
break;
}
return $page;
}
因此,提供者接受的参数将包含使用者可以理解的版本-通常是使用者上次更新时最新的版本。
提供者尽力提供所需的版本
如果不可能,则有一个“合同”来通知消费者(返回值上将存在
$page->error
)如果有可能,但是有可用的较新版本,则存在另一个“合同”来通知使用者此消息(返回值上将存在
$page->warning
)。并处理一些消费者的情况:
使用者需要发送期望的版本作为参数。
function myFunctionB() {
//The consumer tells the provider which version it wants:
$page = myFunctionA('2.0');
if ($page->error) {
//Notify developers and throw an error
pseudo_notify_devs($page->error);
throw new Exception($page->error);
} else if ($page->warning) {
//Notify developers
pseudo_notify_devs($page->warning);
}
do_stuff_with($page);
}
myFunctionB()
的较旧版本的第二行-或完全不同的使用者myFunctionC()
可能会要求使用较旧的版本:$page = myFunctionA('1.1');
这使您可以随时进行向后兼容的更改-消费者无需执行任何操作。您可以尽最大可能仍然支持旧版本,从而在旧版使用者中提供“优美”的降级。
如果确实需要进行重大更改,则可以继续支持旧版本一段时间,然后再将其完全删除。
元信息
我不确定这是否有用...但是您可以使用过时的版本为消费者添加一些元信息:
function myFunctionA(string $version) {
# [...]
if ($page->error || $page->warning) {
$page->meta = [
'current_version' => '3.0',
'API_docs' => 'http://some-url.fake'
]
}
return $page;
}
然后可以在使用者中使用它:
pseudo_notify_devs(
$page->error .
' - Newest version: ' . $page->meta['current_version'] .
' - Docs: ' . $page->meta['API_docs']
);
...如果我是你,我会注意不要使事情过于复杂...总是KISS