哪种保存模型属性的方法更好


Which approach of saving model property is better?

我有一个模型Order(模型在CakePhp中的样子(,它具有属性状态当订单完成处理步骤时,此属性已更改。当前状态更改的工作方式如下:

我有相关的模型 OrderStatus,其中我有一组可能的状态,例如

<?php
OrderStatus::STARTED
...
OrderStatus::PAID
...
OrderStatus::PROCESSED

订单模型中,我有一种更改状态的方法:

<?php
/**
 * Save new status value for Order.
 *  
 * @param  int $id Order id
 * @param  int $status   New status value
 * @param  int $current  [Optional] Current status value
 * @return boolean
 */
public function changeStatus($id, $status, $current = null) {
    // some code
}

当我需要在控制器或其他模型中的某个地方更改它时,我只是这样称呼它:

<?php
$Order->changeStatus($id, OrderStatus::PAID);

到目前为止,使用此解决方案一切正常,但现在我在想这种方法的正确性。如果明天我决定对特定状态更改执行一些其他操作,因此我必须添加一些其他逻辑并扩展 changeStatus 方法。另外,我不太喜欢的是,我必须在需要更改订单状态的任何地方使用OrderStatus常量,因此它在整个代码中传播。

添加单独的方法来设置每个新状态,在这些方法中使用 OrderStatus 常量并将所有相关逻辑也放在那里,并以如下方式在 Order 模型外部更改状态不是更正确:

<?php
$Order->makePaid($id);
// and 
$Order->makeProcessed($id);

从 OOP 原则和最佳实践的角度来看,哪一个更好,或者也许还有另一个更好的解决方案。

首先,你和大多数"MVC"(注意它在引号中,因为它们实际上不是MVC,但更像是试图模仿模式,但它们不是(框架称为"模型"是一个具有业务逻辑的实体。在大多数情况下,他们也使用活动记录模式,以便能够以更抽象的方式表示数据库中的链接。

当涉及到你的问题时,首先,从"控制器"的角度来看(再次用引号引起来,因为它不完全是一个控制器,因为它做得更多(与实体本身有那么多的参与是错误的。最好有一个"服务"层来抽象你想要做的事情,而"控制器"本身知道如何处理这些请求。我想说的是,控制器只中继事件。

在您的情况下,应该有类似 OrdersService 的东西来保留订单的逻辑。例如:制作新的,支付此类订单等。控制器创建OrdersService的实例并调用$ordersService->createOrder($requiredData)$ordersService->processOrder($id)。另一方面,OrdersService调用订单"模型"来单独处理此类事件。不同类中的逻辑分离越多越好。通常,许多人试图遵循框架的逻辑,最终得到大量的上帝类,里面有太多的逻辑。

如果您想开始使用更好的 OOP 实践,请考虑阅读 SOLID 原则和关注点分离。

我建议 Order 对象应该表示单个订单(单个 id(,其中包含与可以对订单执行的操作相关的方法。这对于应用程序尝试解决的问题应该有意义。因此,如果需要支付订单,为什么不采用pay()方法;其副作用可能是设置正确的当前状态。启动和过程也是如此。

class Order {
    private $id;
    private $currentStatus;
    //other methods...
    public function pay(Money $payment) {
        //handle payment etc.
        $this->currentStatus = OrderStatus::PAID;
    }
    public function start() {
        //do business logic related to starting an order.
        $this->currentStatus = OrderStatus::STARTED;
    }
    public function process() {
        //process an order
        $this->currentStatus = OrderStatus::PROCESSED;
    }
}

在"干净的代码"中,罗伯特·C·马丁(Robert C. Martin(建议争论越少越好。最好为给定操作创建专用方法,而不是具有大量参数的通用方法。这样,您的代码对读者来说将更具表现力。

在您的示例中,如果您为每个更改状态的操作创建专用方法,则状态本身的更改将成为订单对象的实现细节,并且不会在对象外部公开。这是一个很大的优势,因为您可以更改它并且排序依赖类不需要任何更改。