Практическое обобщение (Rating: +2)
[/color]
[color=#0000ff]Введение
Как часто вас преследует чувство дежавю в программировании? Многие программисты не догадываются насколько важно это чувство. Оно свидетельствует о том, что пришло время обобщать классы для повторного использования кода!
Толчком, побудившим меня к написанию этой статьи, стала сегодняшняя ночь, а именно то самое чувство.
Предыстория
Несколько недель назад мне понадобилось написать простенький модуль аутентификации клиента. Я не стал использовать сложные алгоритмы безопасности и включать множество полей, таких как IP или логин пользователя, просто в этом не было никакой необходимости. Все клиенты делились на: неавторизированных пользователей и администраторов - а значит, достаточно было
использовать только два поля: идентификатор и пароль. Когда администратор регистрируется, он передает в систему пароль, на что система отвечает ему идентификатором. В будущем администратору необходимо ввести идентификатор и пароль, что позволит ему войти в систему и получить необходимые полномочия.
Архитектура, как и логика модуля, проста до невозможности, и я даже представить не мог, что ее можно еще больше упростить.
Первые шаги
И так я взялся за дело. Сначала реализовал сущность, представляющую пользователя. Все очень просто и лаконично:
Менеджер, содержащий логику аутентификации пользователя, был несколько сложнее, но по сравнению с другими менеджерами системы, он был тривиален. Приведу его
краткий интерфейс:
Дежавю
Сегодня ночью, скачивая кое-что из известного файлового обменника, я задумался о механизме, позволяющем предоставлять клиентам файлы только по паролю. Мне стало интересно, как это проще всего реализовать, и тут ба-бах - Дежавю! Я понял, что написанный мною ранее модуль аутентификации использует тот же механизм!
Посмотрим внимательно на механизм ограничения доступа к файлам по паролю: доступ к файлу предоставляется только тогда, когда клиент предоставит как информацию о запрашиваемом файле, так и пароль к этому файлу. Модуль аутентификации работает аналогично: доступ к сессии предоставляется только тогда, когда клиент предоставит как информацию о сессии, так и пароль к ней. Замечаете связь?
В эту ночь у меня не было никаких причин переписывать модуль аутентификации, но программист во мне требовал чистоты кода! Тогда я решил прибегнуть к "Обобщению классов".
Обобщение
Для начала я выделил все то общее, что было между моим модулем и механизмом файлового обменника:
1. Идентификатор;
2. Пароль;
3. Доступ только при правильной ключевой паре (идентификатор + пароль).
Казалось бы, что загвоздка в идентификаторе, в моем случае это ID пользователя, а в случае файла, может показаться, что его физический адрес, но это не так! Достаточно вспомнить, что в ООП все является объектом, как становится ясно, что идентификатором файла может являться не только физический адрес, но и ID, как у пользователя:
Теперь, чтобы обобщить общее этих двух механизмов, мне достаточно использовать один-два класса, инкапсулирующих те общие части, что присутствуют в обоих механизмах. Отмечу сразу, что модуль аутентификации у меня отличается от механизма ограничения доступа к файлам тем, что он добавляет логику работы с сессией, для сохранения состояния сеанса после аутентификации пользователя.
Пришло время обобщать!
Запомните, часто класс может вообще не иметь тела, но само его наличие создает в системе новую сущность.
Заключение
При использовании ООП, важно помнить одно золотое правило - если вам кажется, что этот код вы уже когда то писали - настало время обобщать!
Added: Артур
02.08.2012 / 06:24[color=#0000ff]Введение
Как часто вас преследует чувство дежавю в программировании? Многие программисты не догадываются насколько важно это чувство. Оно свидетельствует о том, что пришло время обобщать классы для повторного использования кода!
Толчком, побудившим меня к написанию этой статьи, стала сегодняшняя ночь, а именно то самое чувство.
Предыстория
Несколько недель назад мне понадобилось написать простенький модуль аутентификации клиента. Я не стал использовать сложные алгоритмы безопасности и включать множество полей, таких как IP или логин пользователя, просто в этом не было никакой необходимости. Все клиенты делились на: неавторизированных пользователей и администраторов - а значит, достаточно было
использовать только два поля: идентификатор и пароль. Когда администратор регистрируется, он передает в систему пароль, на что система отвечает ему идентификатором. В будущем администратору необходимо ввести идентификатор и пароль, что позволит ему войти в систему и получить необходимые полномочия.
Архитектура, как и логика модуля, проста до невозможности, и я даже представить не мог, что ее можно еще больше упростить.
Первые шаги
И так я взялся за дело. Сначала реализовал сущность, представляющую пользователя. Все очень просто и лаконично:
<?php calss User extends LongObject{ protected $pass; function getPass(){...} function setPass($pass){...} }Родительский класс LongObject используется здесь потому, что все его дочерние классы могут быть сохранены в БД. Этот класс и содержит идентификатор OID.
Менеджер, содержащий логику аутентификации пользователя, был несколько сложнее, но по сравнению с другими менеджерами системы, он был тривиален. Приведу его
краткий интерфейс:
<?php interface AuthManager{ /* Метод возвращает объект User, восстановленный из сессии. То есть, если пользователь был уже аутентифицирован, то метод вернет его состояние*/ function identify(); // Метод выполняется при отключении администратора от системы function closeSession(); // Метод регистрирует нового администратора, возвращая ему идентификатор function register(); /* Метод пытается аутентифицировать клиента по полученной от него ключевой паре - идентификатору и паролю */ function authenticate($OID, $pass); }Все предельно просто. За пару часов я полностью реализовал модуль, написал под него тесты (в действительности на написание тестов ушло больше времени, чем на разработку и реализацию модуля ), документацию и интерфейс. Все работало прекрасно.
Дежавю
Сегодня ночью, скачивая кое-что из известного файлового обменника, я задумался о механизме, позволяющем предоставлять клиентам файлы только по паролю. Мне стало интересно, как это проще всего реализовать, и тут ба-бах - Дежавю! Я понял, что написанный мною ранее модуль аутентификации использует тот же механизм!
Посмотрим внимательно на механизм ограничения доступа к файлам по паролю: доступ к файлу предоставляется только тогда, когда клиент предоставит как информацию о запрашиваемом файле, так и пароль к этому файлу. Модуль аутентификации работает аналогично: доступ к сессии предоставляется только тогда, когда клиент предоставит как информацию о сессии, так и пароль к ней. Замечаете связь?
В эту ночь у меня не было никаких причин переписывать модуль аутентификации, но программист во мне требовал чистоты кода! Тогда я решил прибегнуть к "Обобщению классов".
Обобщение
Для начала я выделил все то общее, что было между моим модулем и механизмом файлового обменника:
1. Идентификатор;
2. Пароль;
3. Доступ только при правильной ключевой паре (идентификатор + пароль).
Казалось бы, что загвоздка в идентификаторе, в моем случае это ID пользователя, а в случае файла, может показаться, что его физический адрес, но это не так! Достаточно вспомнить, что в ООП все является объектом, как становится ясно, что идентификатором файла может являться не только физический адрес, но и ID, как у пользователя:
<?php class File extends LongObject{ protected $address; ... }То есть поиск в БД будет произведен по OID, а после восстановления файла из БД можно найти его по физическому адресу ($address).
Теперь, чтобы обобщить общее этих двух механизмов, мне достаточно использовать один-два класса, инкапсулирующих те общие части, что присутствуют в обоих механизмах. Отмечу сразу, что модуль аутентификации у меня отличается от механизма ограничения доступа к файлам тем, что он добавляет логику работы с сессией, для сохранения состояния сеанса после аутентификации пользователя.
Пришло время обобщать!
<?php /* Класс представляет аутентифицируемую сущность. Все дочерние классы могут быть восстановлены из БД только если клиент предоставит правильный идентификатор и пароль. Так как все аутентифицируемые сущности работают с базой данных, правильным решением было вынести уровень наследования от LongObject в данный класс. */ abstract class AuthenticatedEntity extends LongObject{ protected $password; // Общее для всех сущностей свойство - пароль // Setters и Getters ... }Так же можно обобщить и менеджер, реализующий логику аутентификации:
<?php class AuthenticationManager implements Singleton{ protected $db; // Интерфейс взаимодействия с БД // Всяческие вспомогательные методы ... /* Общий метод, позволяющий определить, верна ли ключевая пара Если ключевая пара верна, сущность автоматически восстанавливается из БД а метод возвращает true */ public function authenticate(AuthenticatedEntity &$entity){ try{ // Попытка восстановления сущности из БД по ключевой паре $this->dataMapper->recoverFinding($entity, ['OID' => $OID, 'password' => $password]); // Если исключений не выброшено, значит, аутентификация успешна return true; } /* Выброс данного исключения свидетельствует о том, что сущность не восстановлена, а значит, аутентификация не пройдена */ catch(UncertaintyException $e){ return false; } } }Как видно, классы получились довольно общими, и использовать их без наследования и частных сущностей не представляется возможным, но зато они позволяют повторно использовать код. Ниже пример обновленного модуля аутентификации с использованием обобщенных классов:
<?php /* Наследование свидетельствует о том, что User может восстановить свое состояние из БД только при правильной ключевой паре */ class User extends AuthenticatedEntity{ }Как видно, все внутренности User наследуются от AuthenticatedEntity.
Запомните, часто класс может вообще не иметь тела, но само его наличие создает в системе новую сущность.
<?php class SessionManager implements Singleton{ /* Класс использует обобщенный менеджер аутентификация для делегирования ему части обязанностей */ protected $authManager; /* Класс использует менеджер сессий для делегирования ему части обязанностей */ protected $sessionProvider; function identify(){} function closeSession(){} function register(){} function authenticate($OID, $pass){ $user = new User(); $user->setOID($OID); $user->setPassword($password); // Использование обобщенного менеджера аутентификации if($this->authManager->authenticate($user)){ // Аутентификация успешна // Запись данных в сессию $this->sessionProvider->start(); $this->sessionProvider->set('SessionManager::OID', $user->getOID()); return $user; } else{ // Аутентификация неудачна throw new AuthentifyException( 'Аутентификация не пройдена.', 1); } } }Как видно, интерфейс класса совершенно не поменялся, но теперь часть его логики вынесена в обобщенный менеджер аутентификации, и все схожие классы, такие как менеджер доступа к файлам, могут использовать тот же принцип без дублирования кода!
Заключение
При использовании ООП, важно помнить одно золотое правило - если вам кажется, что этот код вы уже когда то писали - настало время обобщать!
Rating:
+2
Views: 1528Comments (10) »