在阅读完它之后,我在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需要实例化一个对象?
答案 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,那么您的用户无论如何都会知道数据库,然后您可以简单地转储上述所有内容并将完整的验证代码写入用户。
正如您所看到的,它归结为您如何设置应用程序。这就把我们带到了最重要的一点:设计模式为常见问题提供了解决方案,让我们可以在不必先定义大量术语的情况下谈论这些问题。这很酷,但通常,您必须修改模式以使它们解决您的具体问题。您可以花几个小时来理解架构和使用哪些模式,而您不会编写单个代码。不要过多考虑模式是否与建议的定义完全一致。确保您的问题得到解决。