Yanıt işlemek için tasarım deseni


10

Çoğu zaman belirli bir işlev çağrısı için yanıt işleme bazı kod yazarken aşağıdaki kod yapısı olsun:

örnek: Bu, bir giriş sistemi için kimlik doğrulamasını yapacak bir işlevdir

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

Sorun

  1. Gördüğünüz gibi if/elseyeni bir başarısızlık durumu açık Kapalı Prensibi'ninelse if ihlali olan bir ifade eklemem gerektiği anlamına gelir .
  2. Bir işleyicide oturum açma denemeleri sayacını artırabildiğimden, ancak başka bir işte daha ciddi şeyler yapabileceğimden, işlevin farklı soyutlama katmanlarına sahip olduğunu hissediyorum.
  3. Örneğin bazı işlevler tekrarlanır increase the login trials.

Katları if/elsebir fabrika modeline dönüştürmeyi düşündüm , ancak sadece davranışları değiştirmeyen nesneleri oluşturmak için fabrikayı kullandım. Bunun için daha iyi bir çözümü olan var mı?

Not:

Bu sadece bir giriş sistemi kullanan bir örnektir. İyi yapılandırılmış bir OO deseni kullanarak bu davranışa genel bir çözüm istiyorum. Bu tür if/elseişleyiciler kodumda çok fazla yerde görünüyor ve ben sadece giriş sistemini açıklamak kolay bir örnek olarak kullandım. Benim gerçek kullanım durumları çok burada yayınlamak için karmaşıktır. : D

Lütfen yanıtınızı PHP koduyla sınırlamayın ve tercih ettiğiniz dili kullanmaktan çekinmeyin.


GÜNCELLEME

Sorumu açıklığa kavuşturmak için bir başka daha karmaşık kod örneği:

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

fonksiyon amacı:

  • Ebay'de oyun satıyorum.
  • Bir müşteri siparişini iptal etmek istiyor ve parasını geri alırsa (yani geri ödeme) önce ebay'de bir "Anlaşmazlık" açmalıyım.
  • Bir anlaşmazlık açıldığında, müşterinin geri ödemeyi kabul ettiğini onaylaması için beklemeliyim (aptallık bana geri ödemeyi söyleyen kişidir, ancak ebay'da bu şekilde çalışır).
  • Bu işlevler benim açtığım tüm anlaşmazlıkları alır ve müşterinin anlaşmazlığa yanıt verip vermediğini görmek için durumlarını düzenli olarak kontrol eder.
  • Müşteri kabul edebilir (sonra iade ederim) veya reddedebilir (sonra geri alırım) veya 7 gün boyunca yanıt vermeyebilir (anlaşmazlığı kendim kapatıp geri ödeme yaparım).

Yanıtlar:


15

Bu, Strateji modeli için en önemli adaydır .

Örneğin, bu kod:

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
}

İçin azaltılabilir

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

burada getOrderStrategy, her biri verilen durumu nasıl ele alacağını bilen bir DisputeAcceptedStrategy, DisputeCancelledStrategy, DisputeOlderThan7DaysStrategy vb.

Yorumlarda soruyu cevaplamak için düzenleyin.

Lütfen kodunuzla ilgili daha ayrıntılı bilgi verebilir misiniz? Ne anladım getOrderStrategy sipariş durumuna bağlı olarak bir strateji nesnesi, ancak preProcess () ve preProcess () işlevleri olan bir fabrika yöntemidir. Ayrıca, updateOrderHistory'ye ($ this) neden $ $ iletdiniz?

Davanız için tamamen uygun olmayan örneğe odaklanıyorsunuz. En iyi uygulamadan emin olmak için yeterli detayım yok, bu yüzden belirsiz bir örnek buldum.

Sahip olduğunuz ortak kod parçası insertRecordInOrderHistoryTable, bu yüzden stratejinin merkezi noktası olarak (biraz daha genel bir adla) kullanmayı seçtim. $ Order ve strateji başına farklı bir dize ile, bu konuda bir yöntem çağırıyor çünkü $ bu geçmek.

Yani, temelde, şuna benzeyenlerin her birini hayal ediyorum:

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

$ Order, Stratejinin özel bir üyesidir (siparişi sarması gerektiğini söyledim) ve ikinci argüman her sınıfta farklıdır. Yine, bu tamamen uygunsuz olabilir. İnsertRecordInOrderHistoryTable öğesini temel bir Strateji sınıfına taşımak ve Yetkilendirme sınıfını iletmek istemeyebilirsiniz. Ya da tamamen farklı bir şey yapmak isteyebilirsiniz, bu sadece bir örnektir.

Benzer şekilde, farklı kodun geri kalanını işlem öncesi ve sonrası yöntemlerle sınırlandırdım. Bu neredeyse kesinlikle onunla yapabileceğiniz en iyi değil. Daha uygun isimler verin. Birden çok yönteme bölün. Arama kodunu daha okunaklı yapan ne olursa olsun.

Sen belki bunu yapmak için tercih:

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

Ve bazı Stratejileriniz "IfNecessary" yöntemleri için boş yöntemler uygulamanızı isteyin.

Arama kodunu daha okunaklı yapan ne olursa olsun.


Cevabınız için teşekkürler, ancak kodunuz hakkında daha fazla bilgi verebilir misiniz? Anladığım şey , sipariş durumuna bağlı olarak getOrderStrategybir strategynesneyi döndüren bir fabrika yöntemidir , ancak preProcess()ve preProcess()işlevleri nelerdir . Neden Ayrıca geçtin $thisiçin updateOrderHistory($this)?
Songo

1
@Songo: Umarız yukarıdaki düzenleme yardımcı olur.
pdr

Aha! Sanırım şimdi anladım. Kesinlikle benden yukarı oy :)
Songo

+1, satırı ayrıntılı olarak açıklayabilir misiniz, var $ strateji = $ this.getOrderStrategy ($ order); stratejiyi tanımlamak için bir geçiş vakası olacaktır.
Naveen Kumar

2

Eğer gerçekten mantığınızı merkezden uzaklaştırmak istiyorsanız strateji modeli iyi bir öneridir, ancak sizinki kadar küçük örnekler için dolaylı bir overkill gibi görünüyor. Şahsen ben, "daha küçük fonksiyonlar yaz" desenini, örneğin:

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

Bir durumu işlemek için if / then / else ifadelerinden oluşan bir demet almaya başladığınızda, Durum Modelini göz önünde bulundurun .

Bunu kullanmanın belirli bir yolu hakkında bir soru vardı: Devlet modelinin bu uygulaması mantıklı mı?

Bu bilmece yeniyim, ama ne zaman kullanacağımı anladığımdan emin olmak için yine de cevabı koydum ("tüm sorunlar bir çekiçe çivi gibi görünmekten kaçının.").


0

Yorumlarımda söylediğim gibi, karmaşık mantık gerçekten hiçbir şeyi değiştirmez.

Tartışmalı bir siparişi işlemek istiyorsunuz. Bunu yapmanın birçok yolu vardır. Tartışmalı sipariş türü şunlar olabilir 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();
   }
}

Bunu yapmanın birçok yolu vardır. Sen miras hiyerarşisi bulunabilir Order, DisputedOrder, DisputedOrderLessThan7Days, DisputedOrderCanceled, vb Bu güzel değil, ama bir işe de olur.

Yukarıdaki örneğimde sipariş türüne bakıyorum ve bunun için ilgili stratejiyi alıyorum. Bu işlemi bir fabrikada kapsülleyebilirsiniz:

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

Bu, sipariş türüne bakar ve size bu tür sipariş için doğru bir strateji verir.

Sonunda şu satırlarda bir şey olabilir:

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

Orijinal Cevap, daha basit bir şeyin peşinde olduğunuzu düşündüğümden artık alakalı değil:

Burada aşağıdaki endişeleri görüyorum:

  • Yasaklanmış IP olup olmadığını kontrol edin. Kullanıcının IP'sinin yasaklı bir IP aralığında olup olmadığını kontrol eder. Ne olursa olsun bunu uygulayacaksın.
  • Denemelerin aşılıp aşılmadığını kontrol edin. Kullanıcının giriş denemelerini aşıp aşmadığını kontrol eder. Ne olursa olsun bunu uygulayacaksın.
  • Kullanıcının kimliğini doğrula. Kullanıcının kimliğini doğrulamayı deneyin.

Aşağıdakileri yaparım:

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

Şu anda örneğin çok fazla sorumluluğu var. Tüm yaptığım, bu sorumlulukları yöntemler içinde kapsüllemekti. Kod daha temiz görünüyor ve her yerde koşul ifadeleri yok.

Fabrika nesnelerin yapımını kapsamaktadır. Örneğinizde hiçbir şeyin yapısını kapsüllemenize gerek yoktur, tek yapmanız gereken endişelerinizi ayırmaktır.


Cevabınız için teşekkürler, ancak her bir yanıt durumu için işleyicilerim gerçekten karmaşık olabilir. lütfen soru güncellemesine bakın.
Songo

Hiçbir şeyi değiştirmez. Sorumluluk, tartışmalı düzeni bir strateji kullanarak işlemektir. Strateji, anlaşmazlığın türüne göre değişir.
CodeART

Lütfen bir güncellemeye bakın. Daha karmaşık mantık için tartışmalı sipariş stratejilerinizi oluşturmak için fabrikadan yararlanabilirsiniz.
CodeART

1
+1 Güncelleme için teşekkürler. Şimdi çok daha net.
Songo
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.