Я сделал минималистичный пример Command Pattern в 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
интересно, есть ли какая-либо разница от простого LoginCommand
этого LoginCommand
в простую функцию в классе Users
?
если LoginCommand
лучше подходит для класса, не лучше ли это, если бы это был статический класс, поэтому я могу просто вызвать LoginCommand::execute()
против необходимости создания объекта 1-го?
Точка командного шаблона может выделять различные функции в объект (команда), поэтому его можно использовать повторно для нескольких других объектов (командиров). Обычно Командующий также передает Получателю команду, например объект, на который нацелена команда. Например:
$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 является Командиром. Автомобиль – это приемник, а программа – это настоящие команды. Конечно, у меня мог бы быть метод doStandardWash () в CarWash и каждый из них использовал метод в CarWash, но это менее расширяемо. Мне нужно было бы добавить новый метод и команду всякий раз, когда я захочу добавить новые программы. С помощью шаблона команды я могу просто передать новые команды (подумайте об обратном вызове) и легко создать новые комбинации:
$carWash->addProgramme('motorwash', new CarSimpleWashCommand, new CarMotorWashCommand, new CarDryCommand, new CarWaxCommand);
Конечно, для этого также можно использовать закрытие или функционал PHP, но давайте придерживаться ООП для этого примера. Другое дело, когда команды пригодились, – это когда у вас более одного Командующего, которому требуется командная функция, например
$dude = new Dude; $dude->assignTask('washMyCarPlease', new CarSimpleWashCommand); $dude->do('washMyCarPlease', new Car);
Если бы мы зафиксировали логику стирки в CarWash, нам пришлось бы дублировать весь код в чуваке. И поскольку Чувак может делать много вещей (потому что он человек), список задач, которые он может сделать, приведет к ужасному длинному классу.
Часто сам Commander также является Command, поэтому вы можете создавать Composite of Commands и складывать их в дерево. Команды часто также предоставляют метод отмены.
Теперь, оглядываясь назад на ваш LoginCommand, я бы сказал, что это не имеет смысла делать это таким образом. У вас нет объекта Command (это глобальная область), и ваша команда не имеет приемника. Вместо этого он возвращается в Commander (что делает глобальную область получателем). Таким образом, ваша команда не работает на приемнике. Также маловероятно, что вам понадобится абстракция в Команду, когда регистрация будет только когда-либо сделана в одном месте. В этом случае я бы согласился, что 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-like:
$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, тогда ваш пользователь будет знать о базе данных в любом случае, а затем вы можете просто сбросить все вышеперечисленное и записать весь код аутентификации пользователю.
Как вы можете видеть, это сводится к тому, как вы настраиваете свое приложение. И это подводит нас к самому важному моменту: «Шаблоны проектирования» предлагают решения общих проблем, и они позволяют нам говорить об этом без необходимости определять тонны терминов в первую очередь. Это круто, но часто вам придется модифицировать шаблоны, чтобы они могли решить вашу конкретную проблему. Вы можете потратить часы на теоретизацию архитектуры и какие шаблоны использовать, и вы не написали бы один код. Не думайте слишком много о том, если шаблон на 100% соответствует предложенному определению. Убедитесь, что ваша проблема решена.