【问题标题】:Implementing the inheritance hierarchy in the State Pattern在状态模式中实现继承层次结构
【发布时间】:2013-02-11 14:17:14
【问题描述】:

我有一个与此非常相似的设计:

这里 NewOrderRegisteredGranted 都有公共方法 AddOrderline()Cancel() ,因此将这两种方法重构为父方法上课很简单。

当我想Cancel 一个Shipped 行时出现问题(目前图中未显示)。

由于 Shipped 行不支持 AddOrderline(),所以我需要打破 NewOrderRegistered授予 2 类,一类用于Cancel(),另一类用于AddOrderline()

现在NewOrder 必须扩展 2 个父类才能获得 2 个函数。

备注

  1. 这个例子非常简单。我的真实应用程序有大约 12 个状态。
  2. 代码用 PHP 编写,但欢迎使用 C# 或 Java 解决方案,因为我认为解决方案将是相似的,因为它们都不支持多重继承。

【问题讨论】:

    标签: java c# php design-patterns state-pattern


    【解决方案1】:

    我会考虑将接口与实现分开。例如在Java中

    interface Cancellable {
       void cancel();
    }
    interface Shippable{
       void ship();
    }
    

    等等

    public class NewState implements Cancellable, Shippable {
      public void cancel() { ... }
      public void ship() { ... }
    }
    

    如果你有一个底层私有状态,它可以实现所有所需的接口,而你的公共状态只需要委托那些支持的状态。例如

     public class UnderlyingState implements Cancellable, Shippable ... {
        public void cancel() { ... }
        public void ship() { ... }
     }
    
      public class ShippableState implements Shippable {
         private UnderlyingState ustate = new UnderlyingState();
         public void cancel() {
            // you can *only* cancel this
            ustate.cancel();    
         }
       }
    

    在上面,您可能需要返回一个新的状态对象(而不是void)并让您的Order 采用该新状态。 UnderlyingState 对象会强制执行一些状态机。

    令人头疼的是,随着您的状态数量增加,您的接口和实现也会增加。

    【讨论】:

    • 向刚刚投票的人道歉。我完全重写了它。
    • 我仍然不确定……订单状态真的应该是运送/取消/等订单的代码吗?在这里考虑依赖注入......这个状态对象将如何访问数据库(我认为它需要运送或取消订单)。在我看来,国家的工作是决定什么可以做,什么不能做,而不是如何去做。
    • 它可以在 Order 对象上调用 back 来获取 Order 执行某些操作。或者,如果 Order 实现了每个 State 接口(Shippable/Cancellable 等),它可以委托给 state 对象以获取新状态,然后自己执行相关的 DAO 操作。你是对的,国家不应该做 DAO 的事情(你可能希望其他实体的 State 对象 - 不仅仅是订单)
    • @BrianAgnew 那么,如果状态操作最终还是要回调到执行该操作的命令,为什么除了返回 true/false 之外还要执行其他操作?
    • @ColinMorelli - 它可以,但我宁愿它返回要移动到的显式状态,因为这意味着状态机被完全封装
    【解决方案2】:

    首先,您需要一个状态管理器来处理状态:

    <?php
    class StateManager
    {
        protected $states = array();
    
        public function registerState(StateInterface $state)
        {
            $this->states[$state->getName()] = $state;
        }
    
        public function getState($state)
        {
            if (!array_key_exists($state, $this->states)) {
                throw new InvalidArgumentException();
            }
    
            return $this->states[$state];
        }
    }
    

    那么您就有了一个可以针对订单执行操作的订单经理:

    <?php
    class OrderManager
    {
        protected $stateManager;
    
        public function ship(OrderInterface $order)
        {
            try {
                $this->stateManager->getState($order->getState())->ship($order);
            } catch (OperationNotAllowedException $exception) {
                // However you want to handle the fact that the state can't be shipped
            }
        }
    }
    

    如果订单在特定状态下无法执行操作,则会引发异常:

    <?php
    class OperationNotAllowedException extends Exception
    {
    }
    

    状态接口:

    <?php
    interface StateInterface
    {
        public function getName();
    
        // Then your normal functions
        public function ship(OrderInterface $order);
        public function cancel(OrderInterface $cancel);
        public function addOrderLine(OrderInterface $order);
        public function refund(OrderInterface $order);
    }
    

    现在,当您设置应用程序时:

    $newOrderState = new NewState($database, $otherdependencies);
    $stateManager->registerState($newOrderState);
    

    您的订单对象仅返回其所在状态的字符串名称,该名称与该状态的 getName 方法之一返回的名称相匹配。

    此方法还允许轻松模拟和测试(这对于任何应用程序都很重要,尤其是处理人们的钱和产品的电子商务)。

    【讨论】:

    • 请在我更新问题以澄清问题时再次检查问题。
    • 我宁愿询问订单本身是否可以发货,而不是询问订单包含的状态。得墨忒耳之类的……
    • @ColinMorelli 例如,如果我需要更改 NewOrderCancelled 的方式怎么办?我从你的代码中看到它只能有一个实现。
    • @Songo如果有必要,您可以让各州执行该操作。但如果这不是你会做的事情 - 我会避免优化它。大多数简单的方法(你会在 Stack Overflow 上得到答案,因为我们显然无法为你编写代码库)会错误地执行它(不使用正确的依赖注入等),这将以后让维护成为一场噩梦。
    • @Songo 更新以支持各州的依赖注入,并回答您之前的问题。现在每个状态都有一个对象可以重复使用。
    猜你喜欢
    • 1970-01-01
    • 2017-09-14
    • 2014-12-22
    • 1970-01-01
    • 2016-04-14
    • 1970-01-01
    • 2013-06-24
    • 1970-01-01
    • 2011-06-16
    相关资源
    最近更新 更多