为了账号安全,请及时绑定邮箱和手机立即绑定

编写处理来自另一个函数的输出的 PHP 函数时使用什么设计模式?

编写处理来自另一个函数的输出的 PHP 函数时使用什么设计模式?

PHP
浮云间 2022-07-22 18:48:18
我有两个功能:myFunctionA()和myFunctionB().myFunctionA()返回一个Object包含Page_Type具有字符串值的键的值。myFunctionB()处理Object返回的多个条目myFunctionA(),包括Page_Type及其字符串值。稍后,myFunctionA()它被更新,因此它不再返回包含键的对象Page_Type,而是返回包含Page_Types数组值的键。因此,myFunctionB()现在也需要更新——它将不再处理Page_Type哪个是字符串,而是Page_Types哪个是数组。如果我理解正确(我可能没有),以上是依赖请求的一个示例,并且可以通过(我认为)部署依赖注入模式(甚至可能是服务定位器模式)来避免它引发的大量重构?)反而 (??)但是,尽管阅读了这个主题,我仍然不确定依赖注入如何在 PHP 或 Javascript 函数中工作(大部分解释涉及编程语言C++和 OOP 概念Classes,而我正在处理 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()处理由 返回的 Object 中的许多条目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()。我不知道我是否以正确的方式接近这个,但这是迄今为止我想出的最好的方法。
查看完整描述

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']

);

......如果我是你,我会小心不要让事情过于复杂......总是亲吻


查看完整回答
反对 回复 2022-07-22
?
DIEA

TA贡献1820条经验 获得超2个赞

通过将返回类型从字符串更改为数组,您似乎破坏了 myFunctionA 和 myFunctionB 之间的接口。我不认为 DI 可以提供帮助。



查看完整回答
反对 回复 2022-07-22
?
动漫人物

TA贡献1815条经验 获得超10个赞

依赖注入在 OOP 上下文中更相关。但是,我在这里要做的主要事情是停止考虑返回可用的东西,并开始考虑这两种方法如何协同工作以及它们的合同是什么。

弄清楚 myFunctionA() 的逻辑输出是什么,将该合约编码为一个对象,然后将您拥有的数据转换为该格式。这样,即使您在 myFunctionA() 中获取数据的方式发生了变化,您也只需更新该一次转换。

只要您遵守该合同(可以通过自定义对象表示)、myFunctionB() 和其他希望根据合同接收数据的方法,您就不必再更改这些方法。

所以我在这里的主要收获是开始考虑你需要的数据,而不是在你收到它的结构中传递它,而是以它对你的应用程序最有意义的方式传递。


查看完整回答
反对 回复 2022-07-22
?
梦里花落0921

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()东西


希望这有帮助


查看完整回答
反对 回复 2022-07-22
  • 4 回答
  • 0 关注
  • 109 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信