2010-07-09 94 views
8

在閱讀完PHP後,我在PHP中做了一個簡約的命令模式示例。我有幾個問題...有關命令模式(PHP)的問題

我想知道我做的是對的嗎?或者也許是太微乎其微,從而降低了命令模式

interface ICommand { 
    function execute($params); 
} 
class LoginCommand implements ICommand { 
    function execute($params) { 
    echo "Logging in : $params[user]/$params[pass] <br />"; 
    $user = array($params["user"], $params["pass"]); 
    // faked users data 
    $users = array(
     array("user1", "pass1"), 
     array("user2", "pass2") 
    ); 

    if (in_array($user, $users)) { 
     return true; 
    } else { 
     return false; 
    } 
    } 
} 

$loginCommand = new LoginCommand(); 
// $tries simulate multiple user postbacks with various inputs 
$tries = array(
    array("user" => "user1", "pass" => "pass1"), 
    array("user" => "user2", "pass" => "pass1"), 
    array("user" => "user2", "pass" => "PaSs2") 
); 

foreach ($tries as $params) { 
    echo $loginCommand->execute($params) ? " - Login succeeded!" : " - Login FAILED!"; 
    echo " <br />"; 
} 

我想知道是否有從簡單地把這個LoginCommand成一個簡單的函數說,在Users類中的任何差異的意義呢?

如果LoginCommand更適合一個類,如果它是一個靜態類,那麼它會不會更好,所以我可以簡單地調用LoginCommand::execute() vs需要實例化一個對象first?

回答

32

命令模式的要點是能夠將不同的功能隔離爲一個對象(命令),因此它可以在多個其他對象(指揮官)之間重複使用。通常,指揮官還將接收者傳遞給命令,例如,該命令針對的對象。例如:

$car = new Car; 
echo $car->getStatus(); // Dirty as Hell 
$carWash = new CarWash; 
$carWash->addProgramme('standard', 
         new CarSimpleWashCommand, 
         new CarDryCommand, 
         new CarWaxCommand); 
$carWash->wash(); 
echo $car->getStatus(); // Washed, Dry and Waxed 

在上面的例子中,CarWash是指揮官。 Car是Receiver,程序是實際的命令。當然,我可以在CarWash中使用doStandardWash()方法,並在CarWash中爲每個命令提供了一種方法,但這種方法的可擴展性較差。每當我想添加新程序時,我都必須添加一個新的方法和命令。隨着命令的模式,我可以簡單地通過在新的命令(認爲回調),並輕鬆地創建新的組合:

$carWash->addProgramme('motorwash', 
         new CarSimpleWashCommand, 
         new CarMotorWashCommand, 
         new CarDryCommand, 
         new CarWaxCommand); 

當然,你可以使用PHP的閉包或者仿函數這個太,但讓我們堅持到OOP此例。命令派上用場的另一件事是當你有多個需要Command功能的Commander時,例如

$dude = new Dude; 
$dude->assignTask('washMyCarPlease', new CarSimpleWashCommand); 
$dude->do('washMyCarPlease', new Car); 

如果我們硬編碼洗滌邏輯到洗車場,我們現在可以複製所有代碼的老兄。而且由於老兄可以做很多事情(因爲他是人類),他能做的任務清單將導致可怕的長班。

通常,Commander本身也是一個Command,因此您可以創建Commands of Commands並將它們堆疊到樹中。命令通常也提供撤銷方法。

現在,回頭看看你的LoginCommand,我認爲這樣做沒有什麼意義。你沒有Command對象(它是全局作用域),你的Command沒有接收者。相反,它會返回到Commander(它使全球範圍成爲Receiver)。所以你的命令在Receiver上並不真正運行。在進行登錄操作只能在一個地方完成的情況下,你也不太可能需要抽象到Command中。在這種情況下,我會同意的LoginCommand更好放入認證適配器,可能與一個策略模式:

interface IAuthAdapter { public function authenticate($username, $password); } 
class DbAuth implements IAuthAdapter { /* authenticate against database */ } 
class MockAuth implements IAuthAdapter { /* for UnitTesting */ } 

$service = new AuthService(); 
$service->setAdapter(new DbAuth); 
if($service->authenticate('JohnDoe', 'thx1183')) { 
    echo 'Successfully Logged in'; 
}; 

你可以做到這一點有些多個命令,如:

$service = new LoginCommander; 
$service->setAdapter(new DbAuth); 
$service->authenticate(new User('JohnDoe', 'thx1138')); 
if($user->isAuthenticated()) { /* ... */} 

你可以當然,將authenticate方法添加到用戶,但是您必須將數據庫適配器設置爲用戶才能執行身份驗證,例如

$user = new User('JohnDoe', 'thx1138', new DbAuth); 
if ($user->authenticate()) { /* ... */ } 

這也是可能的,但個人而言,我不明白爲什麼用戶應該有一個身份驗證適配器。聽起來不像用戶應該擁有的東西。用戶擁有認證適配器所需的憑證,但不是適配器本身。通過適配器連接到用戶的authenticate方法將是一種選擇,但:,

$user = new User('JohnDoe', 'thx1138'); 
if ($user->authenticateAgainst($someAuthAdapter)) { /* ... */ } 

話又說回來,如果你使用的是ActiveRecord的,那麼你的用戶就會知道有關數據庫無論如何,然後你可以簡單地轉儲上述所有寫將整個認證碼輸入用戶。你可以看到,它歸結爲你如何設置你的應用程序。這使我們想到了最重要的一點:設計模式爲常見問題提供瞭解決方案,他們讓我們允許對這些問題進行討論,而無需首先定義大量術語。這很酷,但通常情況下,您將不得不修改模式以使其解決具體問題。你可以花幾個小時理論化架構,使用哪種模式,而且你不會編寫單一的代碼。不要過多考慮一個模式是否與建議的定義完全相符。確保你的問題得到解決。

+3

你解釋得很好,我需要一段時間來消化它。謝謝! – 2010-07-09 11:07:28

+1

「接收器」的概念總是被我發現的其他解釋所遺漏。我認爲現在我們不強調接收器在同一塊代碼中,因爲我們強調了異步性。甚至4年後的好回答。 – JoshuaDavid 2014-11-03 04:42:18