4 回答
TA贡献1824条经验 获得超8个赞
在提供程序的编程中很常见- 即。对其消费者myFunctionA()一无所知。处理这个问题的唯一正确方法是预先定义一个 API 并且永远不要更改它;) myFunctionB()
我看不到对消费者进行版本控制的目的——这个原因必须是“下游” myFunctionB()——即。的消费者,myFunctionB()作者myFunctionB()无法控制...在这种情况下,它myFunctionB()本身成为提供者,作者将不得不处理这个问题(可能使用与您相同的模式)...但这不是您的要处理的问题。
至于您的提供者 myFunctionA():如果您不能预先为数据本身定义接口/API - 即。你知道数据的结构必须改变(以非向后兼容的方式),但你不知道如何......那么你将需要以一种或另一种方式对某些东西 进行版本控制。
你比大多数人领先几英里,因为你看到了这一点,并从一开始就计划好了。
避免myFunctionB()在某些时候对消费者进行更改的唯一方法是以向后兼容的方式对提供者进行所有更改。myFunctionA()您描述的更改不是向后兼容的,因为myFunctionB()在不修改的情况下不可能知道如何处理新输出myFunctionA()。
您提出的解决方案听起来应该可行。但是,至少有两个缺点:
它要求您保留不断增长的遗留功能列表,以防有任何消费者请求他们的数据。这将变得非常难以维护,并且从长远来看可能是不可能的。
根据将来需要进行哪些更改,可能根本无法再生成输出myFunctionA__v1_0()- 在您的示例中page_types,您在系统中添加了几个的可能性 - 在这种情况下,您可能只需重写v1_0以使用第一个和传统的消费者会很高兴。但是,如果您决定page_types从系统中完全放弃 的概念,则必须计划完全删除v1_0一种或另一种方式。因此,您需要建立一种与消费者沟通的方式。
处理这个问题的唯一正确方法仍然是预先定义一个 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']
);
......如果我是你,我会小心不要让事情过于复杂......总是亲吻
TA贡献1815条经验 获得超10个赞
依赖注入在 OOP 上下文中更相关。但是,我在这里要做的主要事情是停止考虑返回可用的东西,并开始考虑这两种方法如何协同工作以及它们的合同是什么。
弄清楚 myFunctionA() 的逻辑输出是什么,将该合约编码为一个对象,然后将您拥有的数据转换为该格式。这样,即使您在 myFunctionA() 中获取数据的方式发生了变化,您也只需更新该一次转换。
只要您遵守该合同(可以通过自定义对象表示)、myFunctionB() 和其他希望根据合同接收数据的方法,您就不必再更改这些方法。
所以我在这里的主要收获是开始考虑你需要的数据,而不是在你收到它的结构中传递它,而是以它对你的应用程序最有意义的方式传递。
TA贡献1772条经验 获得超6个赞
首先,这不是依赖注入。
你myFunctionA()可以被称为生产者,因为它提供数据,它应该被证明是一个Data Structure. 您myFunctionB()可以被称为消费者,因为它使用提供的数据myFunctionA。
因此,为了使您的生产者和消费者独立工作,您需要在它们之间添加另一层,调用Converter. 该Converter层会将Data Structure提供的内容转换为可以理解的Producer众所周知的Data StructureConsumer
我真的建议您阅读本书Clean Code第 6 章:对象和数据结构。这样你就可以完全理解上面的概念,关于Data Structure
例子
假设我们有Data Structurecall Hand,有属性右手和左手。
class Hand {
private $rightHand;
private $leftHand
// Add Constructor, getter and setter
}
myFunctionA()将提供对象Hand,Hand是一个Data Structure
function myFunctionA() {
$hand = Hand::createHand(); //Assume a function to create new Hand object
return $hand;
}
假设我们还有另一个Data Structurecall Leg, Leg 将能够通过 myFunctionB() 消费;
class Leg {
private $rightLeg;
private $leftLeg
// Add Constructor, getter and setter
}
然后,我们需要有一个转换器,在中间,从手转换为腿,并使用myFunctionB()
class Converter {
public static function convertFromHandToLeg($hand) {
$leg = makeFromHand($hand); //Assume a method to convert from Hand to Leg
return $leg;
}
}
myFunctionB(Converter::convertFromHandToLeg($hand))
因此,每当您编辑 的响应时myFunctionA(),意味着您将要编辑Data Structure的Hand。您只需要编辑Converter以确保它继续Hand正确转换Leg。你不需要触摸myFunctionB,反之亦然。
当您有另一个Producer会Hand像您在问题中提到的那样提供时,这将非常有帮助myFunctionC(),myFunctionD()...而且您还有许多其他Consumer会Leg像myFunctionH()...一样消耗的myFunctionG()东西
希望这有帮助
- 4 回答
- 0 关注
- 109 浏览
添加回答
举报