Php 在类之间共享依赖项,同时允许依赖项注入

Php 在类之间共享依赖项,同时允许依赖项注入,php,oop,laravel,php-5.5,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

我有两节课

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::\u construct()
来初始化基本构造函数。但我必须在所有子类中定义父类的依赖项,如下所示

(但对于很多孩子,我不得不重复这个过程。它违背了拥有共同DI点的目的)

可能的情况2


具有使用Getter和Setter的能力。但我认为它们违反了IoC原则。

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

class Child extends Base {

    protected $C;        

    public function __construct(ServiceA $A, ServiceB $B, ChildDependency $C){
       parent::__contruct($A, $B);
       $this->C = $C;
    }
}
您可以尝试改用:


为了保持具有两个依赖项的继承场景的整洁和可维护性,我个人喜欢setter注入而不是构造函数注入。在这一点上也有一些想法,值得一读

您还可以选择使用构造函数注入来注入公共依赖项,使用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; 
    }
}

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

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

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

正如其他人提到的,你可以使用特质。这将使只继承您想要的确切行为变得更容易。traits可以创建属性,使用默认类型设置属性,并提供获取和设置这些属性的方法

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

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

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

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

谢谢,但正如我在场景1中提到的,如果我有大量的类,然后如果我想更改依赖关系,我必须编辑所有的子类。这听起来更像是一个设计问题。如果您使用的是PHP5.4或更高版本,您可以尝试使用traits来解决这个问题。我很欣赏你关于性格的想法。但是,特性依赖项在运行时不能改变,因为我必须使用Getter和Setter,这再次打破了IoC原则,因为我无法在运行时将类组合转换为继承:我同意@Derek。Traits是在php中处理DI的好方法。他们只允许你继承你需要的东西。他们可以完成以下所有任务,或者满足您的需要:向以前不存在的类添加新属性(服务),将这些服务设置为默认类型,提供setter以便您可以更改/覆盖类型(允许在运行时更改),提供getter。您能与我们分享您的实际用例吗?我相信,根据
Base
和依赖项的实际情况,可能会有不同的方法来解决这个问题。这听起来像是在打破(可能还有其他一些原则),同时也有。
ClassA、ClassB
它们在哪里?@sectus它们在同一个文件夹中。但不限于这里。我需要更多的上下文来确定可能的设计缺陷在哪里。你想解决的具体问题是什么?如果(
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; 
    }
}
class someClass {
    public function _construct($serviceA = new serviceA()) {
        $this->serviceA = $serviceA;
    }
}