Практическое обобщение

[/color]

[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);
}
Все предельно просто. За пару часов я полностью реализовал модуль, написал под него тесты (в действительности на написание тестов ушло больше времени, чем на разработку и реализацию модуля D ), документацию и интерфейс. Все работало прекрасно.

Дежавю
Сегодня ночью, скачивая кое-что из известного файлового обменника, я задумался о механизме, позволяющем предоставлять клиентам файлы только по паролю. Мне стало интересно, как это проще всего реализовать, и тут ба-бах - Дежавю! Я понял, что написанный мною ранее модуль аутентификации использует тот же механизм!
Посмотрим внимательно на механизм ограничения доступа к файлам по паролю: доступ к файлу предоставляется только тогда, когда клиент предоставит как информацию о запрашиваемом файле, так и пароль к этому файлу. Модуль аутентификации работает аналогично: доступ к сессии предоставляется только тогда, когда клиент предоставит как информацию о сессии, так и пароль к ней. Замечаете связь?
В эту ночь у меня не было никаких причин переписывать модуль аутентификации, но программист во мне требовал чистоты кода! Тогда я решил прибегнуть к "Обобщению классов".

Обобщение
Для начала я выделил все то общее, что было между моим модулем и механизмом файлового обменника:
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);
    }
  }
}
Как видно, интерфейс класса совершенно не поменялся, но теперь часть его логики вынесена в обобщенный менеджер аутентификации, и все схожие классы, такие как менеджер доступа к файлам, могут использовать тот же принцип без дублирования кода!

Заключение
При использовании ООП, важно помнить одно золотое правило - если вам кажется, что этот код вы уже когда то писали - настало время обобщать!

URL: https://visavi.net/articles/445