Mẫu thiết kế để xử lý một phản ứng


10

Hầu hết khi tôi viết một số mã xử lý phản hồi cho một lệnh gọi hàm nhất định, tôi nhận được cấu trúc mã sau:

Ví dụ: Đây là chức năng sẽ xử lý xác thực cho hệ thống đăng nhập

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

Vấn đề

  1. Như bạn có thể thấy mã được xây dựng trên một if/elsecấu trúc, điều đó có nghĩa là trạng thái lỗi mới sẽ có nghĩa là tôi cần thêm một else ifcâu lệnh vi phạm Nguyên tắc Đóng mở .
  2. Tôi có cảm giác rằng hàm này có các lớp trừu tượng khác nhau vì tôi có thể chỉ cần tăng bộ đếm thử nghiệm đăng nhập trong một trình xử lý, nhưng làm những thứ nghiêm trọng hơn trong một lớp khác.
  3. Một số chức năng được lặp lại increase the login trialschẳng hạn.

Tôi đã nghĩ về việc chuyển đổi nhiều if/elsethành một mô hình nhà máy, nhưng tôi chỉ sử dụng nhà máy để tạo ra các đối tượng không thay đổi hành vi. Có ai có một giải pháp tốt hơn cho điều này?

Ghi chú:

Đây chỉ là một ví dụ sử dụng một hệ thống đăng nhập. Tôi đang yêu cầu một giải pháp chung cho hành vi này bằng cách sử dụng mẫu OO được xây dựng tốt. Kiểu if/elsexử lý này xuất hiện ở quá nhiều vị trí trong mã của tôi và tôi chỉ sử dụng hệ thống đăng nhập như một ví dụ đơn giản dễ giải thích. Trường hợp sử dụng thực tế của tôi là rất nhiều phức tạp để gửi ở đây. : D

Vui lòng không giới hạn câu trả lời của bạn cho mã PHP và thoải mái sử dụng ngôn ngữ bạn thích.


CẬP NHẬT

Một ví dụ mã phức tạp hơn chỉ để làm rõ câu hỏi của tô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();
        }
    }

mục đích chức năng:

  • Tôi đang bán trò chơi trên ebay.
  • Nếu khách hàng muốn hủy đơn đặt hàng của mình và lấy lại tiền của mình (ví dụ: Hoàn tiền), trước tiên tôi phải mở "Tranh chấp" trên ebay.
  • Khi tranh chấp được mở, tôi phải đợi khách hàng xác nhận rằng anh ta đồng ý hoàn tiền (ngớ ngẩn vì anh ta là người bảo tôi hoàn lại tiền, nhưng đó là cách nó hoạt động trên ebay).
  • Chức năng này nhận được tất cả các tranh chấp do tôi mở và kiểm tra trạng thái của họ theo định kỳ để xem khách hàng có trả lời tranh chấp hay không.
  • Khách hàng có thể đồng ý (sau đó tôi hoàn lại tiền) hoặc từ chối (sau đó tôi quay lại) hoặc có thể không trả lời trong 7 ngày (tôi tự đóng tranh chấp sau đó hoàn lại tiền).

Câu trả lời:


15

Đây là một ứng cử viên chính cho mẫu Chiến lược .

Ví dụ: mã này:

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
}

Có thể được giảm xuống

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

trong đó getOrderStrargety kết thúc thứ tự trong một vụ tranh chấp, tranh chấp, tranh chấp, tranh chấp, tranh chấp, tranh chấp, v.v. mỗi người đều biết cách xử lý tình huống đã cho.

Chỉnh sửa, để trả lời câu hỏi trong ý kiến.

bạn có thể vui lòng giải thích thêm về mã của bạn Điều tôi hiểu là getOrderStrargety là một phương thức xuất xưởng trả về một đối tượng chiến lược tùy thuộc vào trạng thái đơn hàng, nhưng các hàm preProcess () và preProcess () là gì. Ngoài ra, tại sao bạn lại chuyển $ this để cập nhậtOrderHistory ($ this)?

Bạn đang tập trung vào ví dụ, có thể hoàn toàn không phù hợp với trường hợp của bạn. Tôi không có đủ chi tiết để chắc chắn về việc triển khai tốt nhất, vì vậy tôi đã đưa ra một ví dụ mơ hồ.

Một đoạn mã phổ biến mà bạn có là insertRecordInOrderHistoryTable, vì vậy tôi đã chọn sử dụng mã đó (với một tên chung hơn một chút) làm điểm trung tâm của chiến lược. Tôi chuyển $ này cho nó, bởi vì nó đang gọi một phương thức trên này, với thứ tự $ và một chuỗi khác nhau cho mỗi chiến lược.

Vì vậy, về cơ bản, tôi hình dung từng người trông như thế này:

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

Trong đó $ order là một thành viên riêng của Chiến lược (hãy nhớ tôi đã nói nó nên gói thứ tự) và đối số thứ hai là khác nhau ở mỗi lớp. Một lần nữa, điều này có thể hoàn toàn không phù hợp. Bạn có thể muốn di chuyển insertRecordInOrderHistoryTable sang một lớp Chiến lược cơ bản và không vượt qua lớp Ủy quyền. Hoặc bạn có thể muốn làm một cái gì đó hoàn toàn khác, đó chỉ là một ví dụ.

Tương tự, tôi đã giới hạn phần còn lại của mã khác nhau cho các phương thức trước và postProcess. Điều này gần như chắc chắn không phải là tốt nhất bạn có thể làm với nó. Đặt tên thích hợp hơn. Chia nó thành nhiều phương thức. Bất cứ điều gì làm cho mã gọi dễ đọc hơn.

Bạn có thể thích làm điều này:

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

Và có một số Chiến lược của bạn triển khai các phương thức trống cho các phương thức "Nếu cần thiết".

Bất cứ điều gì làm cho mã gọi dễ đọc hơn.


Cảm ơn bạn đã trả lời, nhưng bạn có thể vui lòng giải thích thêm về mã của bạn. Điều tôi hiểu là getOrderStrategymột phương thức xuất xưởng trả về một strategyđối tượng tùy thuộc vào trạng thái đơn hàng, nhưng chức năng preProcess()preProcess()chức năng là gì . Cũng tại sao bạn vượt qua $thisđể updateOrderHistory($this)?
Songo

1
@Songo: Hy vọng chỉnh sửa ở trên giúp.
pdr

Aha! Tôi nghĩ rằng tôi nhận được nó ngay bây giờ. Chắc chắn là một cuộc bỏ phiếu từ tôi :)
Songo

+1, Có thể, bạn giải thích chi tiết, cho dù dòng, var $ Strateg = $ this.getOrderStrargety ($ order); sẽ có một trường hợp chuyển đổi để xác định chiến lược.
Naveen Kumar

3

Mẫu chiến lược là một gợi ý tốt nếu bạn thực sự muốn phân cấp logic của mình, nhưng có vẻ như quá mức cần thiết cho các ví dụ nhỏ như của bạn. Cá nhân, tôi sẽ sử dụng mẫu "viết các hàm nhỏ hơn", như:

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

Khi bạn bắt đầu có một loạt các câu lệnh if / then / other để xử lý một trạng thái, hãy xem xét Mẫu trạng thái .

Có một câu hỏi về một cách sử dụng cụ thể: Việc triển khai mẫu trạng thái này có hợp lý không?

Tôi chưa quen với điều này, nhưng dù sao tôi cũng đưa ra câu trả lời để đảm bảo rằng tôi hiểu khi nào nên sử dụng nó (Tránh "tất cả các vấn đề trông giống như đinh đóng búa.").


0

Như tôi đã nói trong nhận xét của mình, logic phức tạp không thực sự thay đổi bất cứ điều gì.

Bạn muốn xử lý một đơn đặt hàng tranh chấp. Có nhiều cách để làm điều đó. Loại lệnh tranh chấp có thể là 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();
   }
}

Có nhiều cách để làm việc này. Bạn có thể có hệ thống phân cấp thừa kế Order, DisputedOrder, DisputedOrderLessThan7Days, DisputedOrderCanceled, vv Điều này là không tốt đẹp, nhưng nó cũng sẽ làm việc.

Trong ví dụ của tôi ở trên, tôi nhìn vào loại đơn đặt hàng và có được chiến lược phù hợp cho điều đó. Bạn có thể gói quá trình đó vào một nhà máy:

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

Điều này sẽ xem xét loại đơn đặt hàng và cung cấp cho bạn một chiến lược chính xác cho loại đơn đặt hàng đó.

Bạn có thể kết thúc với một cái gì đó trên dòng:

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

Câu trả lời gốc, không còn phù hợp như tôi nghĩ bạn đã làm sau một cái gì đó đơn giản hơn:

Tôi thấy những mối quan tâm sau đây:

  • Kiểm tra IP bị cấm. Kiểm tra xem IP của người dùng có nằm trong phạm vi IP bị cấm hay không. Bạn sẽ thực hiện điều này không có vấn đề gì.
  • Kiểm tra nếu vượt quá thử nghiệm. Kiểm tra xem người dùng đã vượt quá nỗ lực đăng nhập của họ. Bạn sẽ thực hiện điều này không có vấn đề gì.
  • Xác thực người dùng. Cố gắng xác thực người dùng.

Tôi sẽ làm như sau:

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

Hiện tại ví dụ của bạn có quá nhiều trách nhiệm. Tất cả những gì tôi đã làm, được gói gọn những trách nhiệm đó trong các phương pháp. Mã trông sạch hơn và bạn không có báo cáo điều kiện ở khắp mọi nơi.

Nhà máy đóng gói xây dựng các đối tượng. Bạn không cần phải đóng gói xây dựng bất cứ điều gì trong ví dụ của bạn, tất cả những gì bạn cần làm là tách biệt mối quan tâm của bạn.


Cảm ơn câu trả lời của bạn, nhưng trình xử lý của tôi cho mỗi trạng thái phản hồi có thể thực sự phức tạp. xin vui lòng xem cập nhật cho câu hỏi.
Songo

Nó không thay đổi bất cứ điều gì. Trách nhiệm là xử lý đơn đặt hàng tranh chấp bằng cách sử dụng một số chiến lược. Chiến lược sẽ thay đổi theo loại tranh chấp.
CodeART

Xin vui lòng xem một bản cập nhật. Đối với logic phức tạp hơn, bạn có thể sử dụng nhà máy để xây dựng các chiến lược đặt hàng đang tranh chấp.
CodeART

1
+1 Cảm ơn đã cập nhật. Bây giờ thì rõ ràng hơn nhiều.
Songo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.