草庐IT

php - 为什么我们需要构造函数?

coder 2024-05-02 原文

假设:(1) 有一个带有构造函数的类,其方法使用注入(inject)对象,如:

class SomeClass
{
    protected $object1;
    protected $object2;

    public function __construct(
        Object1Interface $object1,
        Object2Interface $object2
    ) {
        $this->object1 = $object1;
        $this->object2 = $object2;
    }

// methods that use Object1 and Object2 classes by $this->object1 and $this->object2.
}

和 (2) 是同一个类,没有构造函数,但是类方法接受 Object1Object2 作为依赖项,如下所示:

class SomeClass
{
    public function doStuff1(Object1Interface $object1)
    {// do the stuff}

    public function doStuff2(Object2Interface $object2)
    {// do the stuff}
}

互联网上有许多提倡第一种变体的例子。

但是它们之间有什么区别呢?

最佳答案

它们是不同的,并没有真正提倡一个比另一个。

(1) 被广泛称为构造函数依赖注入(inject)。它被提倡为比 setter 依赖注入(inject)更好的首选形式,而不是 (2)。这些依赖项对消费者是“隐藏”的,并且在对象生命周期内通常不会更改。

考虑抽象的 http 客户端:

$httpClient = new HttpClient(new CurlAdapter());
// or
$httpClient = new HttpClient(new SocketAdapter());

切换适配器不会影响客户端的使用方式:

$response = $httpClient->get($url);

构造函数 DI 被提倡优于 setter,因为它强制注入(inject)依赖项。此外,setter 通常允许在对象生命周期内更改依赖关系,从而改变其行为并为棘手且难以调试的错误打开可能性。

$dataContainer = $serviceLocator->get('SomeDataContainer');
$dataContainer->setStorage(new ArrayStorage());
$dataContainer->set('a','b');
$dataContainer->get('a'); // => 'b'
    // called somewhere else
    {
    $serviceLocator
        ->get('SomeDataContainer')
        ->setStorage(new RedisStorage());
    }
$dataContainer->get('a'); // => 'foobar' WAT

(2) 用于不同的用例,通常不与 DI 重叠。 以这种方式传递依赖关系有多种原因。它们可能是交互界面的一部分,在对象生命周期中经常更改或在调用时决定。依赖项在概念上可能不属于对象,但对于该特定操作仍然需要。重要的是,它们不应存储在对象中并以可能导致副作用的方式使用!

class SomeClass
{
    protected $dep;

    public function doSomething(DepInterface $dep)
    {
        // do the stuff
        // store dep for later
        $this->dep = $dep;
        // This is similar to issue with setters but much worse.
        // Side effect is not obvious
    }

    public function doSomethingElse()
    {
       $this->dep->increment();
    }
}

for ($i = 0; $i < 100; $i++) {
   $foo->doSomething($bar);
   if (rand(0, 100) == $i) {
       // represents conditions out of direct control.
       // such as conditional or timing errors, alternative flows,
       // unanticipated code changes
       $foo->doSomethng($baz);
   }
   $foo->doSomethingElse();
}
// what will be the result?

考虑这个 DDD 示例来解决一些不切实际的问题:

class Bus
{
    public function board(Passenger $passenger, TicketingService $tservice)
    {
        // @todo check bus have seats
        // throws exception if ticket is invalid
        $tservice->punchPassengerTicket($this, $passenger);
        $this->passengers[] = $passenger;
    }
}

if ($bus->isIntercity()) {
    $ticketingService = new BookingService();
} else {
    $ticketingService = new PrepaidCityTransportCardsService();
} 
$bus->board($passenger, $ticketingService);

在这个例子中,我们明确强制登车巴士需要门票,并且在上车时打票。但是,如果我们要在 Bus 实例化时注入(inject) TicketingService,即使在根本没有上车的情况下,我们也会得到更复杂的依赖图。在这种情况下,通过服务来执行操作但从不存储它会大大降低复杂性并显着提高可测试性。

这种技术在领域驱动设计上下文中称为双重分派(dispatch)(不要与 Double Dispatch 混淆)并被广泛使用。
您可能认为我们可以在登机前检查机票并完全消除对票务服务的依赖。好吧,这完全是另一个话题。

关于php - 为什么我们需要构造函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35817123/

有关php - 为什么我们需要构造函数?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby - 我需要将 Bundler 本身添加到 Gemfile 中吗? - 2

    当我使用Bundler时,是否需要在我的Gemfile中将其列为依赖项?毕竟,我的代码中有些地方需要它。例如,当我进行Bundler设置时:require"bundler/setup" 最佳答案 没有。您可以尝试,但首先您必须用鞋带将自己抬离地面。 关于ruby-我需要将Bundler本身添加到Gemfile中吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/4758609/

  3. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  4. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  5. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  6. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  7. ruby - rspec 需要 .rspec 文件中的 spec_helper - 2

    我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只

  8. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

  9. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  10. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

随机推荐