Fork me on GitHub

Special Case (особый случай)

Сегодня мы обсудим паттерн Special Case. Данный паттерн представляет собой гибкий способ обхода ограничений возникающих при появлении, т.н. специальных случаев.

Описание

При проектировании объектно-ориентированной системы часто возникают ситуации, когда нужно обработать какой-либо особый случай, который выбивается из обычного цикла работы. При этом возникает необходимость проверять возникновение такой ситуации по всей системе, что само-собой приводит к неприятным последствиям. Примером такого случая может быть возвращение null вместо объекта при отсутствии или некорректности каких-либо данных. При этом приходится после получения ссылки на такой объект проверять равенство его нулю и отдельным образом обрабатывать каждый такой случай. Это очевидно приводит к жуткому дублированию кода, что неминуемо будет приводить к копи-пасту и соответственно множеству ошибок и ограничений.

Как вы наверное уже догадались справится с данной проблемой нам поможет паттерн Special Case. Суть данного паттерна заключается в замене таких особых случаев на специальный объект, имеющий тот же тип и возврат его вызывающему коду. Полученный объект будет иметь точно такой же интерфейс, как и у обычного класса и соответственно нам не потребуется выполнять никаких скучных проверок. Заменив возврат особых случаев на специализированный объект мы значительно улучшаем архитектуру приложения, т.к. вызывающий код не будет заботится о том каким образом создаётся объект и от чего зависит его нормальная инициализация.

Пример

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

:::php
<?php
// ...
class User{
    protected $user_info;

    public function __construct($id_user) {
        // Вытягиваем информацию о пользователе из БД
    }

    public function getName(){
        return $this->user_info['name'];
    }

    public function getEmail(){
        return $this->user_info['name'];
    }

    public function getStatus(){
        return $this->user_info['status'];
    }

    /**
     * Проверка прав доступа пользователя
     * @param string $action
     * @return bool 
     */
    public function checkAccessRights($action){
        if (!array_key_exists($action, $this->user_info['access_rights']))
            return false;
        return (bool)  $this->user_info['access_rights'][$action];
    }
}

class Guest_User extends User {
    public function __construct() {
        // Стандартные значения для гостя
        $this->user_info['name'] = 'Uknown user';
        $this->user_info['email'] = '';
        $this->user_info['status'] = 'guest';
    }

    /**
     * Гость может только читать
     * @param string $action
     * @return bool 
     */
    public function checkAccessRights($action) {
        if ('read' == $action)
            return true;
        return false;
    }
}

У нас есть два класса: User и Guest_User. Класс Guest_User является реализацией шаблона особый случай. Если нет возможности идентифицировать пользователя, то мы просто возвращаем экземпляр класса Guest_User, который имеет такой же интерфейс, как и класс User. Вызывающий код спокойно продолжает работать не замечая подмены объекта. Далее приведу пример использования данных классов:

:::php
<?php
// ...
function get_user_object(){
    if (!isset($_SESSION['user']) || !($_SESSION['user'] instanceof User))
        $_SESSION['user'] = new Guest_User();
    return $_SESSION['user'];
}

Эта простая функция проверяет наличие объекта в сессии и если он не найден или не корректен, то создает нашу реализацию Special Case, иначе - значение из сессии.

На этом всё. Не забывайте подписываться на rss и оставлять свои комментарии!

ссылки

онлайн