myo*_*yol 20 php oop design-patterns
在面向对象的样式中,依赖关系倾向于被反转,构造函数具有不同的Spartan角色.它唯一的工作是确保对象初始化为满足其基本不变量的状态(换句话说,它确保对象实例以有效状态启动,而不是更多).
这是一个类的基本示例.在创建类时,我传入需要解析的HTML,然后设置类属性.
OrderHtmlParser
{
protected $html;
protected $orderNumber;
public function __construct($html)
{
$this->html = $html;
}
public function parse()
{
$complexLogicResult = $this->doComplexLogic($this->html);
$this->orderNumber = $complexLogicResult;
}
public function getOrderNumber()
{
return $this->orderNumber;
}
protected function doComplexLogic($html)
{
// ...
return $complexLogicResult;
}
}
Run Code Online (Sandbox Code Playgroud)
我正在使用它
$orderparser = new OrderHtmlParser($html);
$orderparser->parse()
$orderparser->getOrderNumber();
Run Code Online (Sandbox Code Playgroud)
我使用一个parse
函数,因为我不希望构造函数做任何逻辑,因为上面的文章和本文都说这是一个糟糕的做法.
public function __construct($html)
{
$this->html = $html;
$this->parse(); // bad
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我不使用该parse
方法,那么我的所有属性(在此示例中只是一个)将返回null
.
这被称为"无效状态"中的对象吗?
另外,有点感觉我的parse方法是initialise
伪装的一个函数,另一篇文章也认为它很糟糕(虽然我不确定这是否仅在构造函数调用该方法时,手动调用时或两者都有).无论如何,初始化方法在设置属性之前仍然在做一些复杂的逻辑 - 这需要在可靠地调用getter之前发生.
所以要么我误解了这些文章,要么这些文章让我觉得可能我这个简单类的整体实现是不正确的.
通常,在构造函数中执行工作是一种代码气味,但实践背后的原因更多地与编程语言有关,而不是对最佳实践的意见.有真正的边缘情况会引入错误.
在某些语言中,派生类的构造函数从下到上执行,而在其他语言中从上到下执行.在PHP中,它们从上到下调用,你甚至可以通过不调用来停止链parent::__construct()
.
这会在基类中创建未知的状态期望,并且更糟糕的是,PHP允许您在构造函数中首先调用parent或者最后调用last.
例如;
class A extends B {
public __construct() {
$this->foo = "I am changing the state here";
parent::__construct(); // call parent last
}
}
class A extends B {
public __construct() {
parent::__construct(); // call parent first
$this->foo = "I am changing the state here";
}
}
Run Code Online (Sandbox Code Playgroud)
在上面的示例中,类B
的构造函数以不同的顺序调用,如果B
在构造函数中执行了大量工作,那么它可能不会处于程序员期望的状态.
那么你如何解决你的问题呢?
你需要两个班级.一个将包含解析器逻辑,另一个将包含解析器结果.
class OrderHtmlResult {
private $number;
public __construct($number) {
$this->number = $number;
}
public getOrderNumber() {
return $this->number;
}
}
class OrderHtmlParser {
public parse($html) {
$complexLogicResult = $this->doComplexLogic($this->html);
return new OrderHtmlResult($complexLogicResult);
}
}
$orderparser = new OrderHtmlParser($html);
$order = $orderparser->parse($html)
echo $order->getOrderNumber();
Run Code Online (Sandbox Code Playgroud)
在上面的示例中,如果无法提取订单号,您可以让parse()
方法返回null
,或者举例.但是这两个阶级都没有进入无效状态.
这个模式有一个名称,其中一个方法产生另一个对象作为结果以封装状态信息,但我记得它被称为什么.
这是否称为处于“无效状态”的对象?
是的。您完全正确,该parse
方法是initialise
变相的函数。
为了避免初始化解析,请懒惰。最懒惰的方法是消除$orderNumber
字段并从函数$html
内部解析它getOrderNumber()
。如果您希望该函数被重复调用和/或您希望解析很昂贵,那么保留该$orderNumber
字段但将其视为缓存。检查它的null
内部getOrderNumber()
并仅在第一次调用时解析它。
关于链接的文章,我原则上同意构造函数应该仅限于字段初始化;然而,如果这些字段是从一个文本块中解析出来的,并且期望客户端将使用大部分或全部解析的值,那么延迟初始化就没有什么价值了。此外,当文本解析不涉及 IO 或new
ing 域对象时,它不应该妨碍黑盒测试,因为急切初始化与延迟初始化是不可见的。