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


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

Ответы:


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 для обработки состояния, рассмотрите шаблон состояния .

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

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


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

1
+1 Спасибо за обновление. Теперь все намного понятнее.
Сонго
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.