我有一个模型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(建议争论越少越好。最好为给定操作创建专用方法,而不是具有大量参数的通用方法。这样,您的代码对读者来说将更具表现力。
在您的示例中,如果您为每个更改状态的操作创建专用方法,则状态本身的更改将成为订单对象的实现细节,并且不会在对象外部公开。这是一个很大的优势,因为您可以更改它并且排序依赖类不需要任何更改。