我想通过合成将两个对象与一个可选对象组合在一起,并保留一个通用且干净的API。举个例子:
class Composition implements Object1Interface, Object2Interface
{
private $object1;
private $object2;
public function __construct($object1, $object2 = null)
{
$this->$object1 = $object1;
$this->$object2 = $object2;
}
// This will always work since the object is mandatory.
public function getId() {
return $this->object1->getId();
}
// This may fail because the object2 can be null.
public function getName() {
return $this->object2->getName();
}
}
你可以看到,这开始很快失败,因为我可以在这里代理一个空对象。什么是解决这个问题的好方法?
如果我要强制$object2
而我可能没有任何构造函数数据一直填写$object2
,我需要使构造函数的所有参数都是可选的。这似乎是一个很大的禁忌,因为它不再需要论证。
一个选项是创建一个返回$object2
的方法,但这需要用户链接,如下所示:
$composition->getObject2()->getName()
这可以接受,但我正在破坏我所拥有的干净API,现在正在链接方法。
这里有什么可以解决的问题,或者我应该简单地使用上面提到的方法解决方案吗?
答案 0 :(得分:1)
我猜你的意思是null-safe operator。
与Hack不同,PHP没有此运算符(相关的RFC),因此您需要explicitly check null的值。您也可以使用Option Type之类的内容。至少你可以动态创建"默认"依赖对象的版本:
class Composition implements Object1Interface, Object2Interface
{
private $object1;
// ...
public function __construct(Object1Interface $object1 = null, Object2Interface $object2 = null)
{
if ($object1 !== null) {
$this->object1 = $object1;
} else {
$this->object1 = new class implements Object1Interface
{
public function getId()
{
return null;
}
};
}
// ...
}
public function getId() {
return $this->object1->getId(); // Always works.
}
}
答案 1 :(得分:1)
您有两种选择:在从对象返回之前始终检查对象是否已设置,或者使用" dummy"如果没有提供,则代替用户提供的对象。
前者可能变得非常混乱,因为你必须在所有东西周围添加警卫,所以我个人更喜欢后者。
使实现后者更简单的一件事是使用接口来指定期望对象的规范,并让构造函数在没有提供真实对象的情况下实例化虚拟对象。
interface AThingThatDoesSomething
{
public function getValue() : integer
}
class RealClass implements AThingThatDoesSomething
{
public function getValue() : integer
{
return mt_rand();
}
}
class DummyClass implements AThingThatDoesSomething
{
public function getValue() : integer
{
// You could return a dummy value here, or throw an exception, or whatever you deem to be the correct behaviour for the dummy class. This implementation just returns 0 for simplicity
return 0;
}
}
class ConsumingClass
{
private $requiredInstance = null;
private $optionalInstance = null;
public function __construct(AThingThatDoesSomething $requiredInstance, AThingThatDoesSomething $optionalInstance = null)
{
if (null === $optionalInstance)
{
$optionalInstance = new DummyClass();
}
$this->requiredInstance = $requiredInstance;
$this->optionalInstance = $optionalInstance;
}
public function getRequiredVal() : integer
{
return $this->requiredInstance->getValue();
}
// You don't need to worry if the user supplied an optional instance here because if they didn't then the dummy instance will have been instantiated instead
public function getOptionalVal() : integer
{
return $this->optionalInstance->getValue();
}
}
这似乎是一个人为的例子,当然你是对的,但它也展示了一种名为Design By Contract的模式的好处之一。只要对象承诺满足某些条件(在这种情况下通过实现接口),您就可以替换满足这些条件的任何对象,即使该对象实际上没有做任何事情。
在现实生活中,我将它用于需要记录的类。我使用psr \ log包并在构造函数中设置一个新的NullLogger。如果我需要实际记录,那么我使用setLogger()来传递记录器,但如果我不这样做,我就不必担心$ this->记录器失败,因为它始终是组。