Pola desain untuk menangani respons

10

Sebagian besar waktu ketika saya sedang menulis beberapa kode yang menangani respons untuk panggilan fungsi tertentu saya mendapatkan struktur kode berikut:

contoh: Ini adalah fungsi yang akan menangani otentikasi untuk sistem 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.
}    
}

Masalah

  1. Seperti yang Anda lihat, kode tersebut dibuat berdasarkan if/elsestruktur yang berarti status kegagalan baru akan berarti bahwa saya perlu menambahkan else ifpernyataan yang merupakan pelanggaran terhadap Prinsip Terbuka Terbuka .
  2. Saya merasa bahwa fungsi ini memiliki lapisan abstraksi yang berbeda karena saya hanya dapat meningkatkan penghitung percobaan masuk di satu handler, tetapi melakukan hal-hal yang lebih serius di yang lain.
  3. Beberapa fungsi diulang increase the login trialsmisalnya.

Saya berpikir untuk mengubah banyak if/elseke pola pabrik, tapi saya hanya menggunakan pabrik untuk membuat objek tidak mengubah perilaku. Adakah yang punya solusi yang lebih baik untuk ini?

catatan:

Ini hanya contoh menggunakan sistem login. Saya meminta solusi umum untuk perilaku ini menggunakan pola OO yang dibangun dengan baik. Semacam ini if/elsepenangan muncul di terlalu banyak tempat di kode saya dan saya hanya menggunakan sistem login sebagai sederhana mudah untuk menjelaskan contoh. Kasing asli saya sangat rumit untuk dikirim di sini. : D

Tolong jangan membatasi jawaban Anda ke kode PHP dan jangan ragu untuk menggunakan bahasa yang Anda inginkan.


MEMPERBARUI

Contoh kode lain yang lebih rumit hanya untuk memperjelas pertanyaan saya:

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

tujuan fungsi:

  • Saya menjual game di ebay.
  • Jika seorang pelanggan ingin membatalkan pesanannya dan mendapatkan kembali uangnya (yaitu Pengembalian Uang), saya harus membuka "Perselisihan" di ebay terlebih dahulu.
  • Setelah perselisihan dibuka, saya harus menunggu pelanggan untuk mengkonfirmasi bahwa dia setuju untuk pengembalian dana (konyol karena dia orang yang mengatakan kepada saya untuk mengembalikan uang, tetapi begitulah cara kerjanya di ebay).
  • Fungsi ini membuat semua sengketa dibuka oleh saya dan memeriksa status mereka secara berkala untuk melihat apakah pelanggan telah menjawab perselisihan atau tidak.
  • Pelanggan dapat menyetujui (lalu saya mengembalikan) atau menolak (kemudian saya kembalikan) atau mungkin tidak menanggapi selama 7 hari (saya menutup sendiri perselisihan lalu mengembalikan uang).
Songo
sumber

Jawaban:

15

Ini adalah kandidat utama untuk pola Strategi .

Misalnya, kode ini:

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
}

Bisa dikurangi menjadi

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

di mana getOrderStrategy membungkus pesanan dalam DisputeAcceptedStrategy, DisputeCancelledStrategy, DisputeOlderThan7DaysStrategy, dll. yang masing-masing tahu cara menangani situasi yang diberikan.

Edit, untuk menjawab pertanyaan dalam komentar.

bisa tolong jelaskan lebih lanjut tentang kode Anda. Apa yang saya pahami adalah getOrderStrategy adalah metode pabrik yang mengembalikan objek strategi tergantung pada status pesanan, tetapi apa fungsi preProcess () dan preProcess (). Juga mengapa Anda memberikan $ this untuk memperbaruiOrderHistory ($ this)?

Anda memfokuskan pada contoh, yang mungkin benar-benar tidak sesuai untuk kasus Anda. Saya tidak memiliki cukup detail untuk memastikan implementasi terbaik, jadi saya memberikan contoh yang tidak jelas.

Sepotong kode yang umum Anda miliki adalah insertRecordInOrderHistoryTable, jadi saya memilih untuk menggunakannya (dengan nama yang sedikit lebih umum) sebagai titik pusat strategi. Saya meneruskan $ this ke sana, karena memanggil metode untuk ini, dengan $ order dan string berbeda per strategi.

Jadi, pada dasarnya, saya membayangkan setiap orang yang terlihat seperti ini:

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

Di mana $ order adalah anggota pribadi dari Strategi (ingat saya katakan itu harus membungkus pesanan) dan argumen kedua berbeda di setiap kelas. Sekali lagi, ini mungkin sepenuhnya tidak pantas. Anda mungkin ingin memindahkan insertRecordInOrderHistoryTable ke kelas Strategi dasar dan tidak meneruskan kelas Otorisasi. Atau Anda mungkin ingin melakukan sesuatu yang sama sekali berbeda, itu hanya sebuah contoh.

Demikian juga, saya membatasi sisa kode yang berbeda untuk metode pra dan pasca proses. Ini hampir pasti bukan yang terbaik yang dapat Anda lakukan dengannya. Berikan nama yang lebih tepat. Membaginya menjadi beberapa metode. Apa pun yang membuat kode panggilan lebih mudah dibaca.

Anda mungkin lebih suka melakukan ini:

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

Dan mintalah beberapa Strategi Anda menerapkan metode kosong untuk metode "IfN Diperlukan".

Apa pun yang membuat kode panggilan lebih mudah dibaca.

pdr
sumber
Terima kasih atas balasan Anda, tetapi bisakah Anda menjelaskan lebih lanjut tentang kode Anda. Apa yang saya pahami adalah itu getOrderStrategyadalah metode pabrik yang mengembalikan strategyobjek tergantung pada status pesanan, tetapi apa saja preProcess()dan preProcess()fungsinya. Juga mengapa Anda lulus $thiske updateOrderHistory($this)?
Songo
1
@Songo: Semoga hasil edit di atas membantu.
pdr
Aha! Saya rasa saya mengerti sekarang. Jelas suara dari saya :)
Songo
+1, Bisa, Anda uraikan, apakah baris, var $ strategy = $ this.getOrderStrategy ($ order); akan memiliki saklar untuk mengidentifikasi strategi.
Naveen Kumar
2

Pola strategi adalah saran yang baik jika Anda benar-benar ingin mendesentralisasikan logika Anda, tetapi sepertinya tipuan berlebihan untuk contoh sekecil milik Anda. Secara pribadi, saya akan menggunakan pola "tulis fungsi yang lebih kecil", seperti:

if($result=='wrong password')
   wrongPassword();
else if($result=='wrong username')
   wrongUsername();
else if($result=='login trials exceeded')
   excessiveTries();
else if($result=='banned ip')
   bannedIp();
Karl Bielefeldt
sumber
1

Ketika Anda mulai memiliki banyak pernyataan if / then / else untuk menangani status, pertimbangkan Pola Negara .

Ada pertanyaan tentang cara tertentu untuk menggunakannya: Apakah penerapan pola negara ini masuk akal?

Saya baru mengenal derai ini, tetapi saya tetap memberikan jawaban untuk memastikan saya mengerti kapan harus menggunakannya (Hindari "semua masalah terlihat seperti paku pada palu.").

JeffO
sumber
0

Seperti yang saya katakan di komentar saya, logika kompleks tidak benar-benar mengubah apa pun.

Anda ingin memproses pesanan yang disengketakan. Ada banyak cara untuk melakukan itu. Jenis pesanan yang disengketakan dapat 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();
   }
}

Ada banyak cara untuk melakukan ini. Anda dapat memiliki hirarki warisan dari Order, DisputedOrder, DisputedOrderLessThan7Days, DisputedOrderCanceled, dll Hal ini tidak baik, tetapi juga akan bekerja.

Dalam contoh saya di atas saya melihat jenis pesanan dan mendapatkan strategi yang relevan untuk itu. Anda bisa merangkum proses itu menjadi sebuah pabrik:

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

Ini akan melihat jenis pesanan dan memberi Anda strategi yang benar untuk jenis pesanan itu.

Anda mungkin berakhir dengan sesuatu di baris:

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

Jawaban Asli, tidak lagi relevan karena saya pikir Anda mencari sesuatu yang lebih sederhana:

Saya melihat kekhawatiran berikut di sini:

  • Periksa IP yang diblokir. Cek apakah IP pengguna berada dalam kisaran IP yang dilarang. Anda akan melakukan ini apa pun yang terjadi.
  • Periksa apakah uji coba terlampaui. Cek apakah pengguna telah melampaui upaya login mereka. Anda akan melakukan ini apa pun yang terjadi.
  • Otentikasi pengguna. Mencoba mengotentikasi pengguna.

Saya akan melakukan hal berikut:

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

Saat ini teladan Anda memiliki terlalu banyak tanggung jawab. Yang saya lakukan, adalah merangkum tanggung jawab itu dalam metode. Kode terlihat lebih bersih dan Anda tidak memiliki pernyataan kondisi di mana-mana.

Pabrik merangkum konstruksi benda. Anda tidak perlu merangkum konstruksi apa pun dalam contoh Anda, yang perlu Anda lakukan adalah memisahkan kekhawatiran Anda.

CodeART
sumber
Terima kasih atas balasan Anda, tetapi penangan saya untuk setiap status respons bisa sangat kompleks. silakan lihat pembaruan untuk pertanyaan itu.
Songo
Itu tidak mengubah apa pun. Tanggung jawab adalah memproses pesanan yang disengketakan menggunakan beberapa strategi. Strategi akan bervariasi sesuai dengan jenis perselisihan.
CodeART
Silakan lihat pembaruan. Untuk logika yang lebih kompleks, Anda dapat menggunakan pabrik untuk membangun strategi pesanan yang disengketakan Anda.
CodeART
1
+1 Terima kasih atas pembaruannya. Jauh lebih jelas sekarang.
Songo