【问题标题】:Sharing dependency among classes while allowing dependency injection在允许依赖注入的同时在类之间共享依赖
【发布时间】:2015-02-07 06:42:19
【问题描述】:

我有两门课

ClassA , ClassB

类通常依赖于两个基本服务和存储库

ServiceA , ServiceB

类(ClassA , ClassB)使用DI原理通过构造函数注入依赖。

由于上面提到的所有三个共享一些公共服务,我想将所有公共方法和服务分组到一个类 Base 像这样

基类

class Base {

    protected $A;
    protected $B;

    public function __construct(ServiceA $A, ServiceB $B){
       $this->A = $A;
       $this->B = $B;
    }
}

儿童服务

class Child extends Base {

    protected $C;        

    public function __construct(ChildDependency $C){
       $this->C = $C;
    }

    public function doStuff()
    {
         //access the $A (**Its null**)
         var_dump($this->A);
    }

}

问题

如何在不违反 IoC 原则的情况下拥有共同的父依赖关系?

可能的情况 1

我知道我必须调用 parent::__construct() 来初始化 Base 构造函数。但是然后我必须在所有子类中定义父类的依赖关系,如下所示。

(但是对于大量的孩子,我必须重复这个过程。它违背了拥有共同 DI 点的目的)。

class Child extends Base {

    protected $C;        

    public function __construct(ChildDependency $C, ParentDepen $A, ParentDepn $B){
       parent::_contruct($A,$B);
       $this->C = $C;
    }
}

可能的情况 2

使用 Getter 和 Setter。但我认为他们违反了 IoC 原则。

【问题讨论】:

  • 您能否与我们分享您的实际用例?我相信根据 Base Child 和实际依赖关系可能有不同的方法来解决这个问题。
  • 这听起来像是你打破了SRP(可能还有其他一些SOLID 原则),以及拥有lasagna code
  • ClassA , ClassB 他们在哪里?
  • @sectus 它们在同一个文件夹中。但不限于在这里。
  • 我需要更多的上下文来确定可能的设计缺陷在哪里。您要解决的具体问题是什么?

标签: php oop laravel php-5.5


【解决方案1】:

如果您必须从创建对象的任何内容中注入依赖项,这似乎是您最好的选择:

class Child extends Base {

    protected $C;        

    public function __construct(ServiceA $A, ServiceB $B, ChildDependency $C){
       parent::__contruct($A, $B);
       $this->C = $C;
    }
}

您可以尝试改用Traits

trait ServiceATrait {
    public $A = new ServiceA();
    public function getServiceA() { return $this->A; }
}
trait ServiceBTrait {
    public $B = new ServiceB();
    public function getServiceB() { return $this->B; }
}
class Base {
    use ServiceATrait;
    use ServiceBTrait;
}
class Child extends Base {
    protected $C;        

    public function __construct(ChildDependency $C) {
       $this->C = $C;
    }
}

function() {
    $c = new Child(new ChildDependency());
    echo $c->getServiceB()->toString();
}

【讨论】:

  • 谢谢,但正如我在方案 1 中提到的,如果我有大量的类,然后如果我想更改依赖关系,我必须编辑所有子类。
  • 这听起来更像是一个设计问题。如果您使用的是 PHP 5.4 或更高版本,您可以尝试使用特征来解决此问题。 php.net/manual/en/language.oop5.traits.php
  • 我很欣赏你对 Traits 的想法。但是再次无法在运行时更改 Traits 依赖项,因此我必须使用 Getter 和 Setter,这再次打破了 IoC 原则,因为我无法在运行时切换类
  • 组合超过继承:D
  • 我同意@Derek。 Traits 是在 php 中处理 DI 的好方法。它们允许你只继承你需要的东西。他们可以完成以下所有事情或您需要的事情:向以前不存在的类添加新属性(服务),将这些服务设置为默认类型,提供设置器以便您可以更改/覆盖类型(允许它在运行时更改),提供 getter。
【解决方案2】:

为了使具有几个依赖项的继承场景保持干净和可维护,我个人更喜欢 setter 注入而不是构造函数注入。 Martin Fowler对此也有一些想法,值得一读。

你也可以选择用构造器注入来注入公共依赖,用setter注入来注入子依赖,以免弄乱构造器。

尽管如此,如果您使用 setter 注入,确保对象没有部分初始化或缺少某些依赖项非常重要。确保这一点的好方法是在服务的 getter 方法中(尽管您只会在运行时注意到这一点)。扩展 Derek 的示例,您可以将 getter 定义如下:

trait ServiceATrait {
    private $A;

    public function initServiceA(ServiceA $serviceA) { 
        $this->A = $serviceA;
    }

    public function getServiceA() { 
        if (null === $this->A) {
            throw new \LogicException("ServiceA has not been initialized in object of type " . __CLASS__);
        }
        return $this->A; 
    }
}

无论您选择什么选项,请确保在您的代码库中一致使用它。

【讨论】:

  • 我会使用 if (is_null($this->A)) {... 而不是 if (null === $this->A) { 但这只是个人喜好
【解决方案3】:

我不确定我是否理解您试图通过使用继承来完成什么。继承应该用于概念上相同类型的事物,而不是用于公共依赖注入点。如果您有使用继承的自然案例,并且您也想使用依赖注入,以遵循良好的实践,那么这是有道理的。但是如果继承和构造函数参数是要走的路,如果你有很多继承,你就无法避免在很多类中更改它们。但是拥有大量继承通常是您过度使用继承的标志。继承经常被过度使用。它使类的行为复杂且难以阅读和更改。始终寻找继承的替代方法,并且仅在其效果优于替代方法时才使用它。

你有另一种代码味道。如果您需要传递足够多的构造函数参数,这会造成可维护性问题,那么您的类可能违反了单一责任原则。这意味着您需要将您的课程分成更多的课程。您可能还需要将一些构造函数参数更改为相应方法的方法参数。

正如其他人所提到的,您可以使用特征。这将使仅继承您想要的确切行为变得更加容易。 trait 可以创建属性,使用一些默认类型设置它们,并提供一种获取和设置这些属性的方法。

至于您担心事情能够在运行时发生变化,我不确定您的意思。它都在运行时运行。到头来都只是变数。构造函数可以在运行时调用,你可以传递它们需要的任何值。您也可以在运行时使用 setter。根据定义,依赖注入是一种运行时技术。如果你的类在创建有问题的类型时没有提供通过 DI 更改类型的方法,那么我猜它在运行时是不可更改的,但没有人建议这样做。

您可以使用工厂类或静态工厂方法。静态工厂方法可以有不同的名称来描述它的构造方式。工厂类可以有一个工厂方法,在这种情况下,您可以将工厂实例设置为能够正确构造对象的工厂。

不要忘记默认参数。无论您如何进行依赖注入或构建对象,都应牢记这些。示例:

class someClass {
    public function _construct($serviceA = new serviceA()) {
        $this->serviceA = $serviceA;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-02-15
    • 1970-01-01
    • 2015-10-06
    • 2016-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多