什么定义了有效的对象状态?

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之前发生.

所以要么我误解了这些文章,要么这些文章让我觉得可能我这个简单类的整体实现是不正确的.

Rea*_*lar 6

通常,在构造函数中执行工作是一种代码气味,但实践背后的原因更多地与编程语言有关,而不是对最佳实践的意见.有真正的边缘情况会引入错误.

在某些语言中,派生类的构造函数从下到上执行,而在其他语言中从上到下执行.在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,或者举例.但是这两个阶级都没有进入无效状态.

这个模式有一个名称,其中一个方法产生另一个对象作为结果以封装状态信息,但我记得它被称为什么.


jac*_*646 5

这是否称为处于“无效状态”的对象?

是的。您完全正确,该parse方法是initialise变相的函数。

为了避免初始化解析,请懒惰。最懒惰的方法是消除$orderNumber字段并从函数$html内部解析它getOrderNumber()。如果您希望该函数被重复调用和/或您希望解析很昂贵,那么保留该$orderNumber字段但将其视为缓存。检查它的null内部getOrderNumber()并仅在第一次调用时解析它。


关于链接的文章,我原则上同意构造函数应该仅限于字段初始化;然而,如果这些字段是从一个文本块中解析出来的,并且期望客户端将使用大部分或全部解析的值,那么延迟初始化就没有什么价值了。此外,当文本解析不涉及 IO 或newing 域对象时,它不应该妨碍黑盒测试,因为急切初始化与延迟初始化是不可见的。