我有两个班级
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原则。
答案 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;
}
}