Реализация шаблона Command

Опубликовал read-php в 28.08.2012 Категория: Выполнение задач и представление результатов в PHP

Интерфейс для командного объекта вряд ли может быть еще проще! Он требует реализовать только один метод — execute ( ).

На рис.  я представил класс command в виде абстрактного класса. На этом уровне простоты его можно было бы определить как интерфейс. Но я склонен использовать абстрактный класс в данном случае, потому что базовый класс также может предоставить полезные общие функции для своих производных объектов.

В шаблоне Command может быть до трех других участников: клиент, который создает экземпляр командного объекта; вызывающий участник, который использует этот объект: и получатель, на который действует команда.

Получатель может быть передан командному объекту клиентским кодом через конструктор или запрошен от какого-либо объекта-фабрики. Я предпочитаю второй подход, чтобы у конструктора не было аргументов. Затем можно создать экземпляры всех объектов Command точно таким же способом. Давайте построим конкретный класс Command.

abstract class Command {

abstract function execute( CommandContext $context );

}

class LoginCommand extends Command {

function execute( CommandContext $context ) {

$manager = Registry::getAccessManager();

$user = $context->get( ‘username’ ) ;

$pass = $context->get ( ‘pass’ );

$user_obj = $manager->login( $user, $pass );

if ( is_null( $user_obj ) ) {

$context->setError( $manager->getError() );

return false;

}

$context->addParam( «user», $user_obj );

return true;

}

}

Класс LoginCommand предназначен для работы с объектом типа AccessManager. AccessManager — это воображаемый класс, задача которого — управлять механизмом входа пользователей в систему. Обратите внимание, что нашему методу Command : : execute () требуется передать объект CommandContext . Это механизм, посредством которого данные запроса могут быть переданы объектам Command, а ответы — отправлены назад на уровень представления. Использовать объект таким способом полезно, потому что мы можем передать различные параметры командам, не нарушая интерфейс. Класс CommandContext — это, по сути, объект-оболочка для ассоциативного массива переменных, хотя его часто расширяют для выполнения дополнительных полезных задач. Вот пример простой реализации CommandContext.

class CommandContext {

private $params = arrayf);

private $error = » » ;

function _construct() {

$this->params = $ REQUEST;

}

function addParam( $key, $val ) {

$this->params[$key]=$val;

}

function get( $key ) {

return $this->paramst$key];

}

function setError( {error ) {

$this->error = $error;

}

function getErrorO { return

$this->error;

}

}

Итак, «вооруженный» объектом CommandContext, класс LoginCommand может получить доступ к полученным данным запроса: имени пользователя и паролю. Мы используем простой класс Registry со статическими методами для генерации общих объектов. Он возвращает объект типа AccessManager, с которым будет работать класс LoginCommand. Если при выполнении метода login () объекта AccessManager происходит ошибка, то сообщение об ошибке сохраняется в объекте CommandContext, чтобы его можно было отобразить на уровне представления, возвращается значение false («ложь»). Если все в порядке, то метод execute () объекта LoginCommand просто возвращает значение true («истина»). Обратите внимание, что объекты Command сами выполняют мало логических операций. Они проверяют входные данные, обрабатывают ошибки и сохраняют данные, а также вызывают другие методы объектов для выполнения операций, о которых они должны отчитаться. Если вы обнаружили, что логика приложения «вкралась» в ваши командные классы, значит, вам следует подумать о том, как это исправить. Такой код способствует дублированию, поскольку его неизбежно копируют и вставляют из одной команды в другую. Вы должны, по меньшей мере, посмотреть, к чему относятся эти функции. Возможно, их лучше всего переместить в объекты логики приложения или на фасадный уровень. В этом примере нам еще не хватает клиента — класса, который генерирует командные объекты, и вызывающего участника — класса, который работает со сгенерированной командой. Самый простой способ выбора того, экземпляры каких команд требуется создавать в веб-проекте, — использовать параметр в самом запросе. Вот пример упрощенного клиента.

class CommandNotFoundException extends Exception {}

class CommandFactory {

private static Sdir = ‘commands’;

static function getCommand( $action=’Default’ ) {

if ( preg_match( ‘/\W/’, Section ) ) {

throw new Exception(«Недопустимые символы в команде»);

}

$class = UCFirst(strtolower(Saction)) . «Command»;

$file = self : : $dir . DIRECT0RYJ3EPARAT0R . » {Sclass} .php» ;

if ( ! file_exists( $file ) ) {

throw new CommandNotFoundException( «Файл ‘Sfile’ не найден» );

}

require_once( $file );

if ( ! class_exists( $class ) ) {

throw new CommandNotFoundException( «Класс ‘Sclass’ необнаружен» );

}

$cmd = new Sclass(); return Semd;

}

}

Класс CommandFactory просто ищет в каталоге commands определенный файл класса. Это имя файла конструируется с помощью параметра Saction объекта CommandContext, который, в свою очередь, был передан системе из запроса. Если файл найден и класс существует, то он возвращается вызывающему объекту. Сюда можно добавить еще больше операций проверки ошибок, чтобы убедиться, что найденный класс принадлежит семейству Command и что конструктор не ожидает аргументов, но данный вариант полностью подходит для наших целей. Преимущество данного подхода в том, что вы можете добавить новый объект Command в каталог команд в любое время, и система сразу станет поддерживать его. Вызывающий объект теперь — сама простота.

class Controller {

private Scontext;

function _construct() {

$this->context = new CommandContext();

}

function getContext() {

return Sthis-»context;

}

function process() {

$cmd = CommandFactory::getCommand( $this-»context->get(‘action’) );

if ( ! $cmd-»execute( $this-»context ) ) {

// Обработка ошибки } else {

// Все прошло успешно

// Теперь отобразим результаты }

}

}

$controller = new Controller();

// Эмулируем запрос пользователя

$context = $controller->getContext();

$context->addParam(‘action’, ‘login’ );

$context->addParam(‘username’, ‘bob’ );

$context->addParam(‘pass», ‘tiddles’ );

$controller->process ();

Прежде чем вызвать метод Controller:: process () , мы имитируем веб-запрос, определяя параметры объекта CommandContext, экземпляр которого создан в конструкторе контроллера. Метод process () делегирует создание экземпляров объектов объекту CommandFactory. Затем он вызывает метод execute () для возвращенной команды. Обратите внимание, что.контроллер не имеет представления о внутреннем содержании команды. Именно эта независимость от деталей выполнения команды дает нам возможность добавлять новые классы Command без воздействия на всю систему в целом. Давайте создадим еще один класс типа Command.

class FeedbackCommand extends Command {

function execute( CommandContext {context ) {

$msgSystem = Registry::getMessageSystem();

$email = {context->get( ‘email’ );

$msg = {context->get( ‘msg’ );

$topic = {context->get( ‘topic’ );

$result = {msgSystem->send( $email, $msg, $topic );

if ( ! $result ) {

$context->setError( $msgSystem->getError() );

return false;

}

return true;

}

}

При условии помещения этого класса в файл FeedbackCommand.php и сохранения его в папке commands, он будет вызываться в ответ на получение из запроса строки «feedback». Причем не понадобится вносить никакие изменения в контроллер или классы CommandFactory.

На рис. ниже показаны все участники шаблона Command.

P.S. А тем, кто интернируется более глубоко таким языком программирования как php, будет интересно посетить сайт по ссылке http://www.cyberforum.ru/php/ где есть много специалистов по этому языку, готовых вам помочь в решении любых задач связанных с программированием.

Комментариев нет

Добавить комментарий