关于命令模式的问题(PHP)

时间:2010-07-09 03:51:58

标签: php oop design-patterns

在阅读完它之后,我在PHP中做了一个简约的Command Pattern示例。我有几个问题......

我想知道我做的是对的吗?或者可能太小,从而减少了命令模式的重点

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需要实例化一个对象?

1 个答案:

答案 0 :(得分:34)

命令模式的要点是能够将不同的功能隔离到一个对象(命令)中,因此它可以在多个其他对象(指挥官)中重复使用。通常,指挥官也会将接收者传递给指挥部,例如命令所针对的对象。例如:

$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是接收器,程序是实际的命令。当然我可以在CarWash中使用方法doStandardWash()并使每个命令成为CarWash中的一个方法,但这样做的可扩展性较差。每当我想添加新程序时,我都必须添加一个新方法和命令。使用命令模式,我可以简单地传入新命令(想想回调)并轻松创建新组合:

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

当然,你也可以使用PHP的闭包或仿函数,但是在这个例子中我们坚持使用OOP。命令派上用场的另一件事是,当你有多个需要命令功能的Commander时,例如

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

如果我们将洗涤逻辑硬编码到CarWash中,我们现在必须复制Dude中的所有代码。由于一个家伙可以做很多事情(因为他是人类),他可以做的任务清单将导致一个可怕的长课。

通常,Commander本身也是一个命令,因此您可以创建一个命令组合并将它们堆叠到树中。命令通常也提供撤销方法。

现在,回顾一下你的LoginCommand,我会说这样做是没有多大意义的。您没有Command对象(它是全局范围),并且您的Command没有Receiver。相反,它返回到指挥官(使全球范围成为接收者)。因此,您的Command实际上并不在Receiver上运行。当登录只在一个地方进行时,你也不太可能需要抽象到命令中。在这种情况下,我同意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';
};

你可以做得更像Command:

$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,那么您的用户无论如何都会知道数据库,然后您可以简单地转储上述所有内容并将完整的验证代码写入用户。

正如您所看到的,它归结为您如何设置应用程序。这就把我们带到了最重要的一点:设计模式为常见问题提供了解决方案,让我们可以在不必先定义大量术语的情况下谈论这些问题。这很酷,但通常,您必须修改模式以使它们解决您的具体问题。您可以花几个小时来理解架构和使用哪些模式,而您不会编写单个代码。不要过多考虑模式是否与建议的定义完全一致。确保您的问题得到解决。