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

Jie*_*eng 9 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 />";
}
Run Code Online (Sandbox Code Playgroud)

我想知道是否有任何区别只是简单地把它LoginCommand放入一个简单的函数说在Users课堂上?

如果LoginCommand它更适合一个类,如果它是一个静态类不是更好,所以我可以简单地调用LoginCommand::execute()vs需要实例化一个对象吗?

Gor*_*don 34

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

$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
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,CarWash是指挥官.Car是接收器,程序是实际的命令.当然我可以在CarWash中使用方法doStandardWash()并使每个命令成为CarWash中的一个方法,但这样做的可扩展性较差.每当我想添加新程序时,我都必须添加一个新方法和命令.使用命令模式,我可以简单地传入新命令(想想回调)并轻松创建新组合:

$carWash->addProgramme('motorwash',
                        new CarSimpleWashCommand, 
                        new CarMotorWashCommand,
                        new CarDryCommand, 
                        new CarWaxCommand);
Run Code Online (Sandbox Code Playgroud)

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

$dude = new Dude;
$dude->assignTask('washMyCarPlease', new CarSimpleWashCommand);
$dude->do('washMyCarPlease', new Car);
Run Code Online (Sandbox Code Playgroud)

如果我们将洗涤逻辑硬编码到CarWash中,我们现在必须复制Dude中的所有代码.而且由于一个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';
};
Run Code Online (Sandbox Code Playgroud)

你可以做一些类似命令:

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

您当然可以将该authenticate方法添加到用户,但是您必须将数据库适配器设置为用户才能进行身份验证,例如

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

这也是可能的,但就个人而言,我不明白为什么用户应该拥有身份验证适配器.它听起来不像用户应该拥有的东西.用户具有身份验证适配器所需的凭据,但不是适配器本身.将适配器传递给用户的authenticate方法将是一个选项:

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

然后,如果您使用的是ActiveRecord,那么您的用户无论如何都会知道数据库,然后您可以简单地转储上述所有内容并将完整的验证代码写入用户.

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

  • 你解释得很好,我需要一段时间来消化它.谢谢! (3认同)
  • 你的第一个例子似乎没有任何意义,$car 什么也没发生。 (2认同)