รูปแบบการออกแบบสำหรับการจัดการการตอบสนอง


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 และอย่าลังเลที่จะใช้ภาษาที่คุณต้องการ


UPDATE

อีกตัวอย่างรหัสที่ซับซ้อนมากขึ้นเพียงเพื่อชี้แจงคำถามของฉัน:

  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
  • หากลูกค้าต้องการยกเลิกคำสั่งซื้อและรับเงินคืน (เช่นการคืนเงิน) ฉันต้องเปิด "ข้อพิพาท" บนอีเบย์ก่อน
  • เมื่อข้อพิพาทเปิดขึ้นฉันจะต้องรอให้ลูกค้ายืนยันว่าเขาตกลงที่จะคืนเงิน (โง่เพราะเขาเป็นคนที่บอกให้ฉันคืนเงิน แต่นั่นเป็นวิธีการทำงานบนอีเบย์)
  • ฟังก์ชั่นนี้ได้รับข้อพิพาททั้งหมดที่เปิดโดยฉันและตรวจสอบสถานะของพวกเขาเป็นระยะเพื่อดูว่าลูกค้าได้ตอบข้อพิพาทหรือไม่
  • ลูกค้าอาจเห็นด้วย (จากนั้นฉันจะคืนเงิน) หรือปฏิเสธ (จากนั้นฉันจะย้อนกลับ) หรืออาจไม่ตอบกลับเป็นเวลา 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 เพื่ออัปเดต OrderHistory ($ this)

คุณเพ่งความสนใจไปที่ตัวอย่างซึ่งอาจไม่เหมาะสมอย่างยิ่งสำหรับกรณีของคุณ ฉันไม่มีรายละเอียดเพียงพอที่จะตรวจสอบการใช้งานที่ดีที่สุดดังนั้นฉันจึงพบตัวอย่างที่คลุมเครือ

ส่วนหนึ่งของรหัสทั่วไปที่คุณมีคือ insertRecordInOrderHistoryTable ดังนั้นฉันเลือกใช้ (ที่มีชื่อสามัญมากกว่าเล็กน้อย) เป็นจุดศูนย์กลางของกลยุทธ์ ฉันส่ง $ นี่ให้กับมันเพราะมันกำลังเรียกวิธีการนี้โดยใช้ $ order และสตริงต่อกลยุทธ์ที่แตกต่างกัน

โดยพื้นฐานแล้วฉันจินตนาการว่าแต่ละคนจะมีลักษณะเช่นนี้:

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

โดยที่ $ order เป็นสมาชิกส่วนตัวของกลยุทธ์ (โปรดจำไว้ว่าฉันควรจะเรียงลำดับ) และอาร์กิวเมนต์ที่สองนั้นแตกต่างกันในแต่ละชั้นเรียน อีกครั้งนี่อาจไม่เหมาะสมทั้งหมด คุณอาจต้องการย้าย insertRecordInOrderHistoryTable ไปยังคลาส Strategy พื้นฐานและไม่ผ่านคลาส Authorization หรือคุณอาจต้องการทำบางสิ่งที่แตกต่างอย่างสิ้นเชิงมันเป็นเพียงตัวอย่าง

ในทำนองเดียวกันฉัน จำกัด ส่วนที่เหลือของรหัสที่แตกต่างให้กับวิธีก่อนและหลังกระบวนการ นี่เป็นสิ่งที่ดีที่สุดที่คุณสามารถทำได้ ตั้งชื่อให้เหมาะสมยิ่งขึ้น แบ่งออกเป็นหลายวิธี สิ่งที่ทำให้รหัสการโทรสามารถอ่านได้มากขึ้น

คุณอาจต้องการทำสิ่งนี้:

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

และให้บางส่วนของกลยุทธ์ของคุณใช้วิธีการที่ว่างเปล่าสำหรับวิธีการ "ถ้าจำเป็น"

สิ่งที่ทำให้รหัสการโทรสามารถอ่านได้มากขึ้น


ขอบคุณสำหรับการตอบกลับของคุณ แต่คุณช่วยกรุณาอธิบายเพิ่มเติมเกี่ยวกับรหัสของคุณได้ไหม สิ่งที่ฉันเข้าใจคือนั่นgetOrderStrategyคือวิธีการจากโรงงานที่ส่งคืนstrategyวัตถุโดยขึ้นอยู่กับสถานะการสั่งซื้อ แต่สิ่งที่preProcess()และpreProcess()ฟังก์ชั่น นอกจากนี้ยังไม่ให้คุณผ่านทำไม$thisเพื่อupdateOrderHistory($this)?
Songo

1
@Songo: หวังว่าการแก้ไขข้างต้นจะช่วยได้
สาธารณรัฐประชาธิปไตยประชาชนลาว

Aha! ฉันคิดว่าฉันเข้าใจแล้ว แน่นอนขึ้นโหวตจากฉัน :)
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

เมื่อคุณเริ่มมีพวงของหาก / แล้ว / งบอื่นที่จะจัดการกับสถานะการพิจารณารูปแบบของรัฐ

มีคำถามเกี่ยวกับวิธีการใช้งานเฉพาะ: การใช้รูปแบบของรัฐนี้สมเหตุสมผลหรือไม่?

ฉันใหม่สำหรับลายนี้ แต่ฉันตอบคำตอบต่อไปเพื่อให้แน่ใจว่าฉันเข้าใจเมื่อใช้ (หลีกเลี่ยง "ปัญหาทั้งหมดดูเหมือนเล็บตอกตะปู")


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 ขอบคุณสำหรับการอัปเดต ตอนนี้ชัดเจนยิ่งขึ้น
Songo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.