Дизайн-схема для обробки відповіді


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на заводський зразок, але я використовував лише фабрику для створення об'єктів, що не змінюють поведінку. Хтось має для цього краще рішення?

Примітка:

Це лише приклад використання системи входу. Я прошу загального рішення такої поведінки, використовуючи добре побудовану схему ОО. Цей вид 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, DisputeCancelatedStrategy, DisputeOlderThan7DaysStrategy тощо, кожен з яких знає, як впоратися із заданою ситуацією.

Відредагуйте, щоб відповісти на питання в коментарях.

ви можете, будь ласка, детальніше розглянути свій код. Я зрозумів, що getOrderStrategy - це заводський метод, який повертає об'єкт стратегії залежно від стану замовлення, але які функції preProcess () та preProcess (). Крім того, чому ви передали $ this для оновленняOrderHistory ($ this)?

Ви зосереджуєтесь на прикладі, який може бути абсолютно невідповідним для вашої справи. У мене не вистачає деталей, щоб бути впевненим у найкращому виконанні, тому я придумав неясний приклад.

Єдиний поширений фрагмент коду, який ви маєте, є insertRecordInOrderHistoryTable, тому я вирішив використовувати це (з дещо більш загальною назвою) як центральну точку стратегії. Я передаю йому це $, тому що він викликає метод на цьому, з $ порядком та різною строкою на стратегію.

Отже, я в основному уявляю кожного з таких, хто виглядає так:

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

Де $ order є приватним членом Стратегії (пам’ятайте, я сказав, що він повинен завершити порядок), а другий аргумент у кожному класі різний. Знову ж таки, це може бути абсолютно недоречно. Ви можете перенести insertRecordInOrderHistoryTable до базового класу Strategy, а не передати клас авторизації в. Або ви можете зробити щось зовсім інше, це був лише приклад.

Так само я обмежив решту різного коду методами до і після. Це майже точно не найкраще, що ви можете зробити з цим. Дайте йому більш відповідні назви. Розбийте його на кілька методів. Що б не зробило код для виклику більш читаним.

Ви могли б віддати перевагу , щоб зробити це:

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

А деякі ваші Стратегії реалізують порожні методи для методів "Якщо потрібно".

Що б не зробило код для виклику більш читаним.


Дякуємо за вашу відповідь, але ви можете, будь ласка, детальніше розглянути свій код. Я зрозумів, що getOrderStrategyце заводський метод, який повертає strategyоб'єкт залежно від стану замовлення, але які є preProcess()та preProcess()функції. Крім того, чому ви передаєте $thisв updateOrderHistory($this)?
Сонго

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 для обробки статусу, врахуйте зразок стану .

Виникло питання щодо конкретного способу його використання: чи має сенс здійснення цієї структури держави?

Я новачок у цьому малюнку, але все-таки я відповів, щоб переконатися, що я розумію, коли його використовувати (Уникайте "всі проблеми виглядають як цвяхи до молотка").


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

Наразі ваш приклад має занадто багато обов'язків. Все, що я зробив, було інкапсулювати ці обов'язки в рамках методів. Код виглядає більш чистим, і ви не маєте тверджень про стан.

Завод інкапсулює будівництво об'єктів. Вам не потрібно інкапсулювати конструкцію нічого у своєму прикладі, все, що вам потрібно зробити, - це відокремити свої проблеми.


Дякую за вашу відповідь, але мої обробники для кожного статусу відповіді можуть бути справді складними. дивіться оновлення до питання.
Songo

Це нічого не змінює. Відповідальність полягає в обробці спірного замовлення за допомогою певної стратегії. Стратегія відрізнятиметься від типу суперечок.
CodeART

Перегляньте оновлення. Для більш складної логіки ви можете використовувати завод для побудови своїх спірних стратегій замовлення.
CodeART

1
+1 Дякуємо за оновлення. Зараз це набагато зрозуміліше.
Сонго
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.