Шаблон проектирования для обработки ответа

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 дней (я сам закрываю спор, а затем возвращаю деньги).
Songo
источник

Ответы:

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, потому что он вызывает метод this с $ order и другой строкой для стратегии.

Итак, в основном, я представляю каждого из тех, кто выглядит так:

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

Где $ order является частным членом Стратегии (помните, я сказал, что это должно обернуть порядок), а второй аргумент отличается в каждом классе. Опять же, это может быть совершенно неуместно. Возможно, вы захотите переместить insertRecordInOrderHistoryTable в базовый класс Strategy и не передавать класс Authorization. Или вы можете захотеть сделать что-то совершенно другое, это был просто пример.

Аналогично, я ограничил остальную часть отличающегося кода методами pre и postProcess. Это почти наверняка не лучшее, что вы можете с этим сделать. Дайте ему более подходящие имена. Разделите его на несколько методов. Что бы ни делало вызывающий код более читабельным.

Вы можете предпочесть сделать это:

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

И пусть некоторые ваши стратегии реализуют пустые методы для методов IfNeeded.

Что бы ни делало вызывающий код более читабельным.

прецизионный самописец
источник
Спасибо за ваш ответ, но не могли бы вы подробнее рассказать о вашем коде. То , что я понял, что getOrderStrategyэто фабричный метод , который возвращает strategyобъект в зависимости от статуса заказа, но каковы preProcess()и preProcess()функции. И почему ты перешел $thisна updateOrderHistory($this)?
Сонго
1
@ Сонго: Надеюсь, что редактирование выше поможет.
фунтовые
Ага! Я думаю, я понял это сейчас. Определенно голосование от меня :)
Songo
+1, может, вы уточните, будет ли строка, var $ Strategy = $ this.getOrderStrategy ($ order); будет иметь случай переключения, чтобы определить стратегию.
Навин Кумар
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 для обработки состояния, рассмотрите шаблон состояния .

Возник вопрос о конкретном способе его использования: имеет ли смысл такая реализация шаблона состояния?

Я новичок в этой скороговорке, но я все равно поставил ответ, чтобы убедиться, что понимаю, когда его использовать (избегайте «все проблемы выглядят как гвозди к молотку»).

JeffO
источник
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();
   }
}

Есть много способов сделать это. Вы можете иметь иерархию наследования Order, DisputedOrder, DisputedOrderLessThan7Days, DisputedOrderCanceledи т.д. Это не хорошо, но это также будет работать.

В моем примере выше я смотрю на тип заказа и получаю соответствующую стратегию для этого. Вы можете заключить этот процесс в фабрику:

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. 
}

В настоящее время у вашего примера слишком много обязанностей. Все, что я сделал, это заключил эти обязанности в методы. Код выглядит чище, и у вас нет условных выражений повсюду.

Завод инкапсулирует строительство объектов. Вам не нужно инкапсулировать конструкцию чего-либо в вашем примере, все, что вам нужно сделать, это отделить ваши проблемы.

CodeART
источник
Спасибо за ваш ответ, но мои обработчики для каждого статуса ответа могут быть действительно сложными. пожалуйста, смотрите обновление к вопросу.
Сонго
Это ничего не меняет. Ответственность обрабатывать спорный заказ , используя какую - то стратегию. Стратегия будет варьироваться в зависимости от типа спора.
CodeART
Пожалуйста, смотрите обновление. Для более сложной логики вы могли бы использовать фабрику для построения ваших спорных стратегий заказов.
CodeART
1
+1 Спасибо за обновление. Теперь все намного понятнее.
Сонго