在允许依赖注入的同时在类之间共享依赖

时间:2015-02-07 06:42:19

标签: php oop laravel php-5.5

我有两个班级

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构造函数。但是我必须在所有子类中定义Parent的依赖关系,如下所示。

(但对于大量的孩子,我必须重复这个过程。它失去了共同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原则。

3 个答案:

答案 0 :(得分:4)

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

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 :(得分:0)

为了保持对于具有几个依赖项的继承方案的清洁和可维护性,我个人更喜欢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; 
    }
}

无论您选择哪种方式,请务必在代码库中始终使用

答案 2 :(得分:0)

我不确定我是否理解您要通过继承来实现的目标。继承应该用于概念上相同类型的事物,而不是用于公共依赖注入点。如果您有使用继承的自然情况,并且您也希望使用依赖注入,那么为了遵循良好实践,那么这是有道理的。但是如果继承和构造函数参数是可行的方法,那么如果你有很多继承,你就不能在很多类中改变它们。但是拥有大量继承通常表明你过度使用继承。继承经常被滥用。它使类的行为变得复杂,难以阅读和更改。总是寻找继承的替代方案,只有当它比替代方案更好时才使用它。

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

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

至于你对事情能够在运行时改变的担忧,我不确定你的意思。它全部在运行时运行。最后只是变量。可以在运行时调用构造函数,并且可以传递所需的任何值。您也可以在运行时使用setter。根据定义,依赖注入是一种运行时技术。如果你的类正在创建有问题的类型,而没有提供通过DI更改类型的方法,那么我想它在运行时是不可更改的,但没有人建议。

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

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

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