В данной статье объясняется, почему важны шаблоны проекта, когда и почему их следует использовать и приводятся несколько примеров на PHP.
Что такое шаблон проекта?
Шаблон проекта — это оптимизированное, пригодное для многократного использования, решение задач программирования, с которыми разработчики сталкиваются каждый день. Шаблон проекта — это не класс или библиотека, которую можно просто подключить к разрабатываемой системе. Хороший шаблон проекта не зависит от языка программирования, на котором реализован проект. Очень важно понимать, что шаблон проекта похож на обоюдоострый меч — используемый неправильно в неподходящем месте он может стать источником больших проблем. Однако, его применение в нужном месте и в нужное время может стать вашим спасением.
Существует три основных вида шаблона проекта:
структурный
созидательный
поведенческий
Структурный шаблон предназначен для организации взаимодействия элементов системы, облегчая настройку работы всей системы в целом.
Созидательный шаблон обеспечивает реализацию механизма, который облегчает создание объектов наиболее подходящим для ситуации способом.
Поведенческий шаблон используется для организации обмена информацией между элементами системы .
Почему их следует использовать?
Шаблоны проекта, в принципе, - это хорошо продуманные решения задач программирования. Много программистов сталкивалось с подобными задачами до вас, и они уже использовали эти решения. Зачем изобретать велосипед, если вы можете использовать уже много раз проверенные и готовые ответы на возникающие типовые вопросы при реализации проекта?
Пример
Представим, что что вам нужно разработать способ объединения двух классов, которые выполняют две разных операции в зависимости от ситуации. Данные два класса активно используются в существующей системе в разных местах, что значительно усложняет их удаление и модификацию кода. Изменение кода требует проверки всех частей, которые подверглись модификации, что в системе, состоящей из различных взаимодействующих компонентов, может привести к выявлению новых ошибок и последующим исправлениям кода. Вместо того, чтобы связываться с огромным объёмом работы, вы можете применить варианты стратегического и адаптивного шаблонов, которые помогут легко обработать сценарий такого типа.
<?php
class StrategyAndAdapterExampleClass {
private $_class_one;
private $_class_two;
private $_context;
public function __construct( $context ) {
$this->_context = $context;
}
public function operation1() {
if( $this->_context == "context_for_class_one" ) {
$this->_class_one->operation1_in_class_one_context();
} else ( $this->_context == "context_for_class_two" ) {
$this->_class_two->operation1_in_class_two_context();
}
}
}
Просто? Давайте рассмотрим стратегический шаблон подробнее.
Стратегический шаблон
Стратегический шаблон - это поведенческий шаблон проекта, который позволяет вам решать, каким образом будет выполняться программа на основании определённого контекста в процессе выполнения программы. Вы можете инкапсулировать два различных алгоритма в два класса и в ходе выполнения программы решать, какую стратегию использовать для решения задачи.
В нашем примере выше выбор стратегии происходит на основании переменной $context, в зависимости от значения которой реализуется нужный класс. Если переменная содержит class_one, будет использоваться class_one, и наоборот.
Интересно, но где это можно использовать?
Представим, что происходит разработка класса, который либо изменяет либо создаёт новую запись пользователя. Для операций требуются одинаковые параметры (имя, адрес, номер телефона, и т.д.), но, в зависимости от ситуации, будут использоваться разные функции для изменения или создания записи. Конечно, вы можете использовать конструкцию if-else для решения задачи. Но что если вам нужно использовать класс в другом месте? В данном случае вам придётся переписать выражение if-else снова. Может быть легче просто специфицировать ваш контекст?
<?php
class User {
public function CreateOrUpdate($name, $address, $mobile, $userid = null)
{
if( is_null($userid) ) {
// it means the user doesn't exist yet, create a new record
} else {
// it means the user already exists, just update based on the given userid
}
}
}
“Обычный” стратегический шаблон включает инкапсуляцию вашего алгоритма в другом классе, но в данном случае дополнительный класс является лишним. Кроме того, нет необходимости точно следовать шаблону. Допускаются вариации при сохранении концепции.
Адаптивный шаблон — это структурный шаблон проекта, который позволяет адаптировать использование класса с различными интерфейсами, что даёт возможность использовать его в системах с различными методами вызова..
Такой подход также позволяет изменять ввод класса клиента, делая его совместимым с адаптивными функциями..
Как это можно использовать?
Другой термин, связанный с адаптивным классом — это wrapper (упаковщик), который позволяет “упаковывать” операции в класс и повторно использовать их в конкретных ситуациях. Классический пример — это создание основного класса для табличных классов. Вместо того, чтобы вызывать табличные классы и обращаться к их функциями одна за одной, вы можете инкапсулировать все методыклассы с помощью адаптивного класса. Такой способ не только позволит вам повторно использовать любую функцию, какую пожелаете, но и избавит вас от переписывания кода при возникновении необходимости использовать такие же операции в другом месте.
Сравните две реализации алгоритма.
Подход без адаптивного класса
<?php
$user = new User();
$user->CreateOrUpdate( //ввод );
$profile = new Profile();
$profile->CreateOrUpdate( //ввод );
Если потребуется использовать операции в другом месте, или даже просто повторно использовать данный код в другом проекте, нужно будет набивать все снова.
Более интересный вариант реализации
Можно реализовать решение задачи так:
<?php
$account_domain = new Account();
$account_domain->NewAccount( //ввод );
В данном примере используется класс «упаковщик» Account:
<?php
class Account()
{
public function NewAccount( //ввод )
{
$user = new User();
$user->CreateOrUpdate( //часть ввода );
$profile = new Profile();
$profile->CreateOrUpdate( //Часть ввода );
}
}
Таким образом, вы можете использовать класс Account снова, когда он понадобится. К тому же, можно упаковывать любой другой класс основной класс.
Шаблон метода фабрики
Шаблон метода фабрики — это созидательный шаблон проекта, который действует как фабрика реализации объектов.
Основная задача, которую решают использование такого шаблона — это инкапсулирование процедуры создания объектов, которая может охватывать несколько классов в единственной функции. Для обеспечения правильного контекста для метода фабрики, нужно, чтобы функция возвращала правильный объект.
Когда это можно использовать?
Самым подходящим моментом для использования шаблона метода фабрики является ситуация, когда нужно использовать множественные различные вариации одного и того же класса. Например, есть класс кнопки. Данный класс имеет различные вариации - ImageButton, InputButton и FlashButton. В зависимости от места может понадобиться создавать разные кнопки — подходящий момент для использования фабрики для создания кнопок.
Создадим три описанных класса:
<?php
abstract class Button {
protected $_html;
public function getHtml()
{
return $this->_html;
}
}
class ImageButton extends Button {
protected $_html = "..."; //Код для кнопки с изображением
}
class InputButton extends Button {
protected $_html = "..."; //Код для обычной кнопки (<input type="button"... />);
}
class FlashButton extends Button {
protected $_html = "..."; //Код для кнопки на основе Flash
}
Создаём класс фабрику:
<?php
class ButtonFactory
{
public static function createButton($type)
{
$baseClass = 'Button';
$targetClass = ucfirst($type).$baseClass;
if (class_exists($targetClass) && is_subclass_of($targetClass, $baseClass)) {
return new $targetClass;
} else {
throw new Exception("Кнопка типа '$type' не используется.");
}
}
}
Использовать данный код можно следующим образом:
$buttons = array('image','input','flash');
foreach($buttons as $b) {
echo ButtonFactory::createButton($b)->getHtml()
}
Скрипт должен выводить код HTML для всех типов кнопки. Таким образом, можно указывать, какая кнопка должна быть создана в определённой ситуации, и многократно использовать код.
Шаблон декорирования
Шаблон декорирования - структурный шаблон проекта, который позволяет добавлять новые или дополнительные функции объекту в процессе выполнения кода в зависимости от ситуации.
Задача, которую решает такой шаблон, является использование дополнительных функций для определённого объекта и, в то же время, сохранение возможности создавать новые объекты оригинального класса, которые не имеют в своём составе дополнительных новых функций. Также возможно комбинирование нескольких декораторов для одного объекта. Такой шаблон является альтернативой использования системы подклассов, которая строится на создании новых классов, наследующих функциональность об базового класса. В противоположность подклассам, которые добавляют новые функции в процессе компиляции, «декорирование» позволяет добавлять новые операции во время выполнения кода, если ситуация требует таких действий.
Для реализации шаблона декорирования можно выполнить следующие шаги:
Объявить от оригинального класса “Component” подкласс “Decorator”
В классе Decorator объявить указатель Component
Передать Component конструктору Decorator для инициализации указателя Component
В классе Decorator перенаправить все методы “Component” на указатель “Component” и
В классе Decorator перепишете все методы Component, которые должны быть модифицированы
Когда нужно использовать?
Наилучшим моментом использования шаблона декорирования является ситуация, когда у вас есть объект, которому нужно изменять поведение в зависимости от ситуации. Допустим, у вас есть элемент ссылка HTML (ссылка на выход из системы), которую надо немного модифицировать в зависимости от текущей страницы. Для решения задачи мы можем использовать шаблон декорирования.
Сначала определим какая «декорация» нам нужна.
Если мы находимся на главной странице и зарегистрированы в системе, ссылка обрамляется тегом h3
Если мы находимся на другой странице и зарегистрированы в системе, ссылка обрамляется тегом подчёркивания
Если мы вошли в систему, ссылка обрамляется тегом <strong>.
Теперь можно написать код.
<?php
class HtmlLinks {
//методы, которые доступны для всех ссылок html
}
class LogoutLink extends HtmlLinks {
protected $_html;
public function __construct() {
$this->_html = "<a href=\"logout.php\">Выйти</a>";
}
public function setHtml($html)
{
$this->_html = $html;
}
public function render()
{
echo $this->_html;
}
}
class LogoutLinkh3Decorator extends HtmlLinks {
protected $_logout_link;
public function __construct( $logout_link )
{
$this->_logout_link = $logout_link;
$this->setHtml("<h3>" . $this->_html . "</h3>");
}
public function __call( $name, $args )
{
$this->_logout_link->$name($args[0]);
}
}
class LogoutLinkUnderlineDecorator extends HtmlLinks {
protected $_logout_link;
public function __construct( $logout_link )
{
$this->_logout_link = $logout_link;
$this->setHtml("<u>" . $this->_html . "</u>");
}
public function __call( $name, $args )
{
$this->_logout_link->$name($args[0]);
}
}
class LogoutLinkStrongDecorator extends HtmlLinks {
protected $_logout_link;
public function __construct( $logout_link )
{
$this->_logout_link = $logout_link;
$this->setHtml("<strong>" . $this->_html . "</strong>");
}
public function __call( $name, $args )
{
$this->_logout_link->$name($args[0]);
}
}
Теперь можно использовать классы так:
$logout_link = new LogoutLink();
if( $is_logged_in ) {
$logout_link = new LogoutLinkStrongDecorator($logout_link);
}
if( $in_home_page ) {
$logout_link = new LogoutLinkh3Decorator($logout_link);
} else {
$logout_link = new LogoutLinkUnderlineDecorator($logout_link);
}
$logout_link->render();
В данном примере показано, как комбинировать множественные декорации, если они нужны. Так как все декораторы используют магический метод __call, то мы можем вызвать оригинальные методы. Если предположить, что мы находимся на главной странице и зарегистрированы в системе, то вывод HTML кода будет следующим:
<strong><h3><a href="logout.php">Выйти</a></h3></strong>
Шаблон единственного объекта
Шаблон единственного объекта - это созидательный шаблон проекта, который обеспечивает существование единственного объекта определённого класса на протяжении все времени выполнения и наличие глобальной точки доступа к нему.
Таким образом облегчается установка точки “ориентирования” для других объектов, которые используют единственный объект в качестве маяка, так как переменные такого объекта остаются одинаковыми для всех, кто бы их не вызывал.
Когда использовать?
Если вам нужно передать определённую информацию от одного объекта другом, то вы можете использовать шаблон единственного объекта, чтобы избежать передачу информации через конструкторы и аргументы. Допустим, существует класс Session , который имитирует глобальный массив $_SESSION. Так как объект такого класса нужен в единственном экземпляре, то мы можем использовать шаблон единственного объекта:
<?php
class Session
{
private static $instance;
public static function getInstance()
{
if( is_null(self::$instance) ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() { }
private function __clone() { }
// другие методы реализуются здесь
...
...
...
}
// получаем объект session
$session = Session::getInstance();
Таким образом мы можем получить доступ к объекту Session из любой части кода, даже из другого класса. Данные сохраняются для всех вызовов getInstance.
Заключение
Существует значительно больше шаблонов дизайна для изучения. В данной статье затронуты только самые известные, которые часто используются в процессе разработки приложений.
Внимание! При использовании шаблонов дизайна всегда нужно убедиться в том, что решается именно та задача, для которой предназначен шаблон. Иначе мощное средство может нанести вред разработчику.