Modello di progettazione per la gestione di una risposta


10

Il più delle volte quando scrivo del codice che gestisce la risposta per una determinata chiamata di funzione ottengo la seguente struttura di codice:

esempio: questa è una funzione che gestirà l'autenticazione per un sistema di login

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

Problema

  1. Come puoi vedere, il codice si basa su una if/elsestruttura, il che significa che un nuovo stato di errore significherà che devo aggiungere else ifun'istruzione che costituisce una violazione del principio chiuso aperto .
  2. Ho la sensazione che la funzione abbia diversi livelli di astrazione in quanto potrei semplicemente aumentare il contatore delle prove di accesso in un gestore, ma fare cose più serie in un altro.
  3. Alcune funzioni sono ripetute increase the login trialsper esempio.

Ho pensato di convertire il multiplo if/elsein un modello factory, ma ho usato solo factory per creare oggetti senza alterare i comportamenti. Qualcuno ha una soluzione migliore per questo?

Nota:

Questo è solo un esempio usando un sistema di login. Sto chiedendo una soluzione generale a questo comportamento usando un modello OO ben costruito. Questo tipo di if/elsegestori appare in troppi punti del mio codice e ho appena usato il sistema di login come un semplice esempio facile da spiegare. I miei casi d'uso reali sono molto complicati da pubblicare qui. : D

Non limitare la tua risposta al codice PHP e sentiti libero di usare la lingua che preferisci.


AGGIORNARE

Un altro esempio di codice più complicato solo per chiarire la mia domanda:

  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();
        }
    }

scopo della funzione:

  • Vendo giochi su ebay.
  • Se un cliente desidera annullare il suo ordine e ottenere indietro i suoi soldi (ad esempio un rimborso), devo prima aprire una "Controversia" su ebay.
  • Una volta aperta una controversia, devo attendere che il cliente confermi che accetta il rimborso (sciocco perché è lui che mi ha detto di rimborsare, ma è così che funziona su eBay).
  • Questa funzione mi consente di aprire tutte le controversie e di controllare periodicamente il loro stato per vedere se il cliente ha risposto alla controversia o meno.
  • Il cliente può concordare (quindi rimborsare) o rifiutare (quindi eseguire il rollback) o non rispondere per 7 giorni (chiudo la controversia e quindi rimborso).

Risposte:


15

Questo è un candidato principale per il modello di strategia .

Ad esempio, questo codice:

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
}

Potrebbe essere ridotto a

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

dove getOrderStrategy avvolge l'ordine in DisputeAcceptedStrategy, DisputeCancelledStrategy, DisputeOlderThan7DaysStrategy, ecc., ognuno dei quali sa come gestire la situazione data.

Modifica, per rispondere alla domanda nei commenti.

potresti per favore approfondire di più sul tuo codice. Quello che ho capito è che getOrderStrategy è un metodo factory che restituisce un oggetto strategia in base allo stato dell'ordine, ma quali sono le funzioni preProcess () e preProcess (). Inoltre, perché hai passato $ this per updateOrderHistory ($ this)?

Ti stai concentrando sull'esempio, che potrebbe essere del tutto inappropriato per il tuo caso. Non ho abbastanza dettagli per essere sicuro della migliore implementazione, quindi ho trovato un esempio vago.

L'unico pezzo comune di codice che hai è insertRecordInOrderHistoryTable, quindi ho scelto di usarlo (con un nome leggermente più generico) come punto centrale della strategia. Gli passo $ questo, perché sta chiamando un metodo su questo, con $ ordine e una stringa diversa per strategia.

Quindi, fondamentalmente, immagino che ognuno di questi assomigli a questo:

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

Dove $ order è un membro privato della strategia (ricordate che ho detto che dovrebbe concludere l'ordine) e il secondo argomento è diverso in ogni classe. Ancora una volta, questo potrebbe essere del tutto inappropriato. Potresti voler spostare insertRecordInOrderHistoryTable in una classe di strategia di base e non passare la classe di autorizzazione. Oppure potresti voler fare qualcosa di completamente diverso, era solo un esempio.

Allo stesso modo, ho limitato il resto del codice diverso ai metodi pre e postProcess. Questo non è quasi certamente il massimo che puoi farci. Dagli nomi più appropriati. Dividilo in più metodi. Qualunque cosa renda più leggibile il codice chiamante.

Si potrebbe preferire di fare questo:

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

E alcune delle tue strategie implementano metodi vuoti per i metodi "Se necessario".

Qualunque cosa renda più leggibile il codice chiamante.


Grazie per la tua risposta, ma potresti per favore approfondire il tuo codice. Quello che ho capito è che getOrderStrategyè un metodo di fabbrica che restituisce un strategyoggetto in base allo stato dell'ordine, ma quali sono le funzioni preProcess()e preProcess(). Anche perché sei passato $thisa updateOrderHistory($this)?
Songo,

1
@Songo: spero che la modifica sopra sia di aiuto.
pdr

Aha! Penso di averlo capito adesso. Sicuramente un voto positivo da parte mia :)
Songo,

+1, puoi, puoi elaborare, indipendentemente dal fatto che la riga var $ strategy = $ this.getOrderStrategy ($ order); avrebbe una custodia per identificare la strategia.
Naveen Kumar,

2

Il modello di strategia è un buon suggerimento se vuoi veramente decentralizzare la tua logica, ma sembra un eccesso di direzione indiretta per esempi piccoli come i tuoi. Personalmente, impiegherei il modello "Scrivi funzioni più piccole", come:

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

Quando inizi ad avere un sacco di istruzioni if ​​/ then / else per gestire uno stato, considera il modello di stato .

C'era una domanda su un modo particolare di usarlo: questa implementazione del modello di stato ha senso?

Sono nuovo di questo scalpiccio, ma ho comunque messo la risposta per assicurarmi di capire quando usarlo (evitare il "tutti i problemi sembrano chiodi in un martello").


0

Come ho detto nei miei commenti, la logica complessa non cambia nulla.

Vuoi elaborare un ordine contestato. Esistono diversi modi per farlo. Il tipo di ordine contestato può essere 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();
   }
}

Ci sono molti modi per farlo. Si può avere gerarchia di ereditarietà di Order, DisputedOrder, DisputedOrderLessThan7Days, DisputedOrderCanceled, ecc Questo non è bello, ma sarebbe anche il lavoro.

Nel mio esempio sopra guardo il tipo di ordine e ottengo una strategia pertinente per questo. Potresti incapsulare quel processo in una fabbrica:

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

Questo esaminerebbe il tipo di ordine e ti darebbe una strategia corretta per quel tipo di ordine.

Potresti finire con qualcosa sulle righe di:

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

Risposta originale, non più pertinente come pensavo tu stessi cercando qualcosa di più semplice:

Vedo qui le seguenti preoccupazioni:

  • Verifica l'IP vietato. Verifica se l'IP dell'utente si trova in un intervallo IP vietato. Lo eseguirai, qualunque cosa accada.
  • Controlla se le prove hanno superato. Verifica se l'utente ha superato i propri tentativi di accesso. Lo eseguirai, qualunque cosa accada.
  • Autenticare l'utente. Tentativo di autenticare l'utente.

Vorrei fare quanto segue:

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

Attualmente il tuo esempio ha troppe responsabilità. Tutto ciò che ho fatto è stato incapsulare quelle responsabilità all'interno dei metodi. Il codice sembra più pulito e non hai dichiarazioni di condizioni ovunque.

La fabbrica incapsula la costruzione di oggetti. Non è necessario incapsulare la costruzione di nulla nel tuo esempio, tutto ciò che devi fare è separare le tue preoccupazioni.


Grazie per la tua risposta, ma i miei gestori per ogni stato di risposta possono essere davvero complessi. si prega di consultare l'aggiornamento alla domanda.
Songo,

Non cambia nulla. La responsabilità è di elaborare l'ordine contestato utilizzando una strategia. La strategia varierebbe con il tipo di controversia.
CodeART

Si prega di vedere un aggiornamento. Per una logica più complessa potresti utilizzare la fabbrica per costruire le tue strategie di ordine contestato.
CodeART

1
+1 Grazie per l'aggiornamento. Ora è molto più chiaro.
Songo,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.