处理响应的设计模式


10

大多数时候,当我编写一些代码来处理某个函数调用的响应时,我会得到以下代码结构:

示例:此功能将处理登录系统的身份验证

class Authentication{

function login(){ //This function is called from my Controller
$result=$this->authenticate($username,$password);

if($result=='wrong password'){
   //increase the login trials counter
   //send mail to admin
   //store visitor ip    
}else if($result=='wrong username'){
   //increase the login trials counter
   //do other stuff
}else if($result=='login trials exceeded')
   //do some stuff
}else if($result=='banned ip'){
   //do some stuff
}else if...

function authenticate($username,$password){
   //authenticate the user locally or remotely and return an error code in case a login in fails.
}    
}

问题

  1. 如您所见,代码建立在一个if/else结构上,这意味着新的故障状态将意味着我需要添加else if一条违反开放式封闭原则的声明
  2. 我感觉到该函数具有不同的抽象层,因为我可能只是在一个处理程序中增加了登录试验计数器,而在另一个处理程序中做了更严肃的事情。
  3. 例如,某些功能被重复increase the login trials

我曾考虑过将倍数if/else转换为工厂模式,但我仅使用工厂来创建对象,而不能改变行为。有谁对此有更好的解决方案?

注意:

这只是使用登录系统的示例。我要求使用构建良好的OO模式对此行为的一般解决方案。这种if/else处理程序出现在我的代码中的太多地方,我只是使用登录系统作为一个简单易懂的示例。我的实际用例在这里发布要复杂得多。:D

请不要将答案限于PHP代码,并随时使用您喜欢的语言。


更新

另一个更复杂的代码示例只是为了澄清我的问题:

  public function refundAcceptedDisputes() {            
        $this->getRequestedEbayOrdersFromDB(); //get all disputes requested on ebay
        foreach ($this->orders as $order) { /* $order is a Doctrine Entity */
            try {
                if ($this->isDisputeAccepted($order)) { //returns true if dispute was accepted
                    $order->setStatus('accepted');
                    $order->refund(); //refunds the order on ebay and internally in my system
                    $this->insertRecordInOrderHistoryTable($order,'refunded');                        
                } else if ($this->isDisputeCancelled($order)) { //returns true if dispute was cancelled
                    $order->setStatus('cancelled');
                    $this->insertRecordInOrderHistory($order,'cancelled');
                    $order->rollBackRefund(); //cancels the refund on ebay and internally in my system
                } else if ($this->isDisputeOlderThan7Days($order)) { //returns true if 7 days elapsed since the dispute was opened
                    $order->closeDispute(); //closes the dispute on ebay
                    $this->insertRecordInOrderHistoryTable($order,'refunded');
                    $order->refund(); //refunds the order on ebay and internally in my system
                }
            } catch (Exception $e) {
                $order->setStatus('failed');
                $order->setErrorMessage($e->getMessage());
                $this->addLog();//log error
            }
            $order->setUpdatedAt(time());
            $order->save();
        }
    }

功能用途:

  • 我在ebay上出售游戏。
  • 如果客户希望取消他的订单并取回他的钱(即退款),我必须先在ebay上打开“争议”。
  • 一旦发生纠纷,我必须等待客户确认他同意退款(很傻,因为他是告诉我退款的人,但这就是在ebay上的工作方式)。
  • 此功能可让我提出所有争议,并定期检查其状态,以查看客户是否已回复该争议。
  • 客户可能同意(然后我退款)或拒绝(然后我回滚),或者可能在7天内没有响应(我自己关闭争议然后退款)。

Answers:


15

这是该战略模式的主要候选人。

例如,此代码:

if ($this->isDisputeAccepted($order)) { //returns true if dispute was accepted
    $order->setStatus('accepted');
    $order->refund(); //refunds the order on ebay and internally in my system
    $this->insertRecordInOrderHistoryTable($order,'refunded');                        
} else if ($this->isDisputeCancelled($order)) { //returns true if dispute was cancelled
    $order->setStatus('cancelled');
    $this->insertRecordInOrderHistory($order,'cancelled');
    $order->rollBackRefund(); //cancels the refund on ebay and internally in my system
} else if ($this->isDisputeOlderThan7Days($order)) { //returns true if 7 days elapsed since the dispute was opened
    $order->closeDispute(); //closes the dispute on ebay
    $this->insertRecordInOrderHistoryTable($order,'refunded');
    $order->refund(); //refunds the order on ebay and internally in my system
}

可以减少到

var $strategy = $this.getOrderStrategy($order);
$strategy->preProcess();
$strategy->updateOrderHistory($this);
$strategy->postProcess();

其中getOrderStrategy将订单包装在DisputeAcceptedStrategy,DisputeCancelledStrategy,DisputeOlderThan7DaysStrategy等中,它们每个都知道如何处理给定情况。

编辑,以回答评论中的问题。

您能否详细说明一下您的代码。我了解到的是getOrderStrategy是一个工厂方法,该方法根据订单状态返回一个策略对象,但是preProcess()和preProcess()函数是什么。另外,为什么还要将$ this传递给updateOrderHistory($ this)?

您将重点放在示例上,这可能完全不适合您的情况。我没有足够的细节来确保最佳实现,因此我想出了一个模糊的示例。

您拥有的一个常见代码段是insertRecordInOrderHistoryTable,因此我选择将其(具有更通用的名称)用作该策略的中心点。我将$ this传递给它,因为它正在对此上调用一个方法,每个策略使用$ order和一个不同的字符串。

因此,基本上,我设想了每个看起来像这样的人:

public function updateOrderHistory($auth) {
    $auth.insertRecordInOrderHistoryTable($order, 'cancelled');
}

$ order是该策略的私有成员(请记住,我说过应该包装该命令),并且每个类中的第二个参数都不同。同样,这可能是完全不合适的。您可能想将insertRecordInOrderHistoryTable移到策略的基类,而不要传递Authorization类。或者您可能想要做一些完全不同的事情,这只是一个示例。

同样,我将其余不同的代码限制为preProcess和postProcess方法。几乎可以肯定,这不是您能做到的最好的选择。给它起更合适的名字。将其分解为多种方法。任何使调用代码更具可读性的方法。

可能更喜欢这样做:

var $strategy = $this.getOrderStrategy($order);
$strategy->setStatus();
$strategy->closeDisputeIfNecessary();
$strategy->refundIfNecessary();
$strategy->insertRecordInOrderHistoryTable($this);                        
$strategy->rollBackRefundIfNecessary();

并让您的某些策略为“ IfNecessary”方法实现空方法。

任何使调用代码更具可读性的方法。


感谢您的答复,但请您详细说明一下代码。我了解的是,这getOrderStrategy是一种工厂方法,该方法strategy根据订单状态返回对象,但preProcess()and preProcess()函数是什么。你为什么还要$thisupdateOrderHistory($this)
Songo

1
@Songo:希望上面的编辑对您有所帮助。
pdr

啊哈!我想我现在明白了。绝对是我的投票:)
Songo

+1,可以详细说明一下,是否行var $ strategy = $ this.getOrderStrategy($ order); 将有一个转换案例来确定策略。
Naveen Kumar

2

如果您确实想分散逻辑,那么策略模式是一个很好的建议,但是对于像您这样小的示例,它似乎像是间接的过大杀伤力。就个人而言,我将采用“编写较小的函数”模式,例如:

if($result=='wrong password')
   wrongPassword();
else if($result=='wrong username')
   wrongUsername();
else if($result=='login trials exceeded')
   excessiveTries();
else if($result=='banned ip')
   bannedIp();

1

当您开始拥有大量的if / then / else语句来处理状态时,请考虑使用State Pattern

关于使用它的特定方式存在一个问题:这种状态模式的实现是否有意义?

我是这个新手,但是无论如何我都会给出答案,以确保我理解何时使用它(避免“所有问题都像钉子钉在锤子上。”)。


0

正如我在评论中所说,复杂的逻辑并不会真正改变任何东西。

您要处理有争议的订单。有多种方法可以做到这一点。有争议的订单类型可以是Enum

public void ProcessDisputedOrder(DisputedOrder order)
{
   switch (order.Type)
   {
       case DisputedOrderType.Canceled:
          var strategy = new StrategyForDisputedCanceledOrder();
          strategy.Process(order);  
          break;

       case DisputedOrderType.LessThan7Days:
          var strategy = new DifferentStrategy();
          strategy.Process(order);
          break;

       default: 
          throw new NotImplementedException();
   }
}

有很多方法可以做到这一点。你可以有继承层次OrderDisputedOrderDisputedOrderLessThan7DaysDisputedOrderCanceled,等,这是不是很好,但它也将工作。

在上面的示例中,我查看了订单类型并获得了相关的策略。您可以将该过程封装到工厂中:

var strategy = DisputedOrderStrategyFactory.Instance.Build(order.Type);

这将查看订单类型,并为该订单类型提供正确的策略。

您可能最终得到以下结果:

public void ProcessDisputedOrder(DisputedOrder order)
{
   var strategy = DisputedOrderStrategyFactory.Instance.Build(order.Type);   
   strategy.Process(order);
}

原始答案,不再相关,因为我认为您正在追求简单一些:

我在这里看到以下问题:

  • 检查被禁止的IP。检查用户的IP是否在禁止的IP范围内。无论如何,您都将执行此操作。
  • 检查是否超过了试验次数。检查用户是否超出其登录尝试。无论如何,您都将执行此操作。
  • 验证用户。尝试验证用户。

我将执行以下操作:

CheckBannedIP(login.IP);
CheckLoginTrial(login);

Authenticate(login.Username, login.Password);

public void CheckBannedIP(string ip)
{
    // If banned then re-direct, else do nothing.
}

public void CheckLoginTrial(LoginAttempt login)
{
    // If exceeded trials, then inform user, else do nothing
}

public void Authenticate(string username, string password)
{
     // Attempt to authenticate. On success redirect, else catch any errors and inform the user. 
}

当前,您的示例职责过多。我所做的只是将这些职责封装在方法中。代码看起来更简洁,您到处都没有条件语句。

工厂封装对象的构造。您无需在示例中封装任何内容的构造,只需要做的就是分离您的关注点。


感谢您的回复,但是我的每个响应状态的处理程序都可能非常复杂。请参阅问题的更新。
Songo

它没有任何改变。责任是使用某种策略来处理有争议的订单。策略因争议类型而异。
CodeART 2012年

请查看更新。对于更复杂的逻辑,您可以利用工厂来构建有争议的订单策略。
CodeART

1
+1感谢您的更新。现在更加清晰了。
Songo
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.