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

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

Описание

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

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

Пример

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

<?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
// ...
function get_user_object(){
    if (!isset($_SESSION['user']) || !($_SESSION['user'] instanceof User))
        $_SESSION['user'] = new Guest_User();
    return $_SESSION['user'];
}

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

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