Цепочка обязанностей (Chain of Responsibility) — шаблон проектирования
28 января 2020
Шаблон предлагает схему для выполнения действия с помощью цепочки обработчиков. Если первый обработчик из цепочки не смог выполнить действие (обязанность), то выполнение переходит ко второму обработчику. Если второй не смог, то выполнение переходит к третьему. И так до конца цепочки или пока действие не будет успешно выполнено.
Код реализации паттерна на языке PHP. Скачать исходники (ZIP, 2 Кб)
Общий смысл решения задачи сводится к тому, что у нас есть классы для проверки текста на недопустимые выражения. Каждый класс реализует собственный цензурный фильтр. Объекты этих классов связаны между собой в цепочку с помощью ссылок-указателей. Далее клиентский код отправляет текст на проверку в первый цензурный фильтр. Если фильтр не находит в тексте ничего запрещённого, то считается, что фильтр отработал неудачно и затем он передаёт текст на проверку следующему фильтру по цепочке. В случае, когда по ходу обработок фильтр что-то обнаружит, то он завершит дальнейшую обработку и вернёт в клиентский код соответствующий результат. В нашем примере фильтр вернёт сообщение о нахождении нецензурных элементов – мат, почтовые адреса, ссылки на сайты.
Далее с помощью метода setNextFilter($nextFilter) фильтры устанавливаются в цепочку. Затем, используя метод checkText($text), по нашей цензурной цепочке прогоняется несколько текстов и результат проверки выводится на экран.
Результат работы скрипта test.php
Пример задачи: сделать многофильтровое цензурирование текста.
Код реализации паттерна на языке PHP. Скачать исходники (ZIP, 2 Кб)
Общий смысл решения задачи сводится к тому, что у нас есть классы для проверки текста на недопустимые выражения. Каждый класс реализует собственный цензурный фильтр. Объекты этих классов связаны между собой в цепочку с помощью ссылок-указателей. Далее клиентский код отправляет текст на проверку в первый цензурный фильтр. Если фильтр не находит в тексте ничего запрещённого, то считается, что фильтр отработал неудачно и затем он передаёт текст на проверку следующему фильтру по цепочке. В случае, когда по ходу обработок фильтр что-то обнаружит, то он завершит дальнейшую обработку и вернёт в клиентский код соответствующий результат. В нашем примере фильтр вернёт сообщение о нахождении нецензурных элементов – мат, почтовые адреса, ссылки на сайты.

censorship_filters.classes.php
Содержит классы с цензурными фильтрами:- AbstractCensorshipFilter – абстрактный класс цензурного фильтра, используется как основа для конечных классов цензурных фильтров, состоит из следующих элементов:
- Свойство $nextFilter – содержит указатель на объект следующего цензурного фильтра, который стоит в цепочке фильтров;
- Метод setNextFilter($nextFilter) – устанавливает в свойство $nextFilter указатель на следующий фильтр;
- Абстрактный метод checkText($text) – выполняет цензурную проверку текста. В данном абстрактом классе метод объявляется, а в конечных классах он определяется.
ВАЖНО! В методе конечных классов должна быть реализована логика, что в случае неуспешного результата, когда фильтр не смог найти недопустимые фразы, тогда он передаёт текст на проверку следующему фильтру, вызывая метод checkTextByNextFilter($text). А для случая успешного результата, когда проверка нашла недопустимые элементы, метод завершает дальнейшие обработки и возвращает символьный код, соответствующий типу недопустимого элемента. - Метод checkTextByNextFilter($text) – выполняет цензурную проверку текста следующим фильтром, вызывая у того метод checkText($text). Если следующего фильтра нету, то есть свойство $nextFilter не содержит указателя на объект, значит это является признаком конца цепочки и метод возвращает значение valid. Значение valid соответствует тому, что текст прошёл все цензурные фильтры и является допустимым.
- BadWordsCensorshipFilter – класс цензурного фильтра для поиска ругательных слов, наследник абстрактного класса AbstractCensorshipFilter. Классс содержит определение метода checkText($text);
- EmailCensorshipFilter – класс цензурного фильтра для поиска email-адресов. Наследник абстрактного класса AbstractCensorshipFilter, содержит собственное определение метода checkText($text);
- WebLinksCensorshipFilter – класс цензурного фильтра для поиска ссылок. Наследник абстрактного класса AbstractCensorshipFilter, содержит собственное определение метода checkText($text).
<?
/**
* Абстрактный класс цензурного фильтра
*/
abstract class AbstractCensorshipFilter
{
/**
* Ссылка на следующий цензурный фильтр
*
* @var AbstractCensorshipFilter
*/
private $nextFilter = null;
/**
* Устанавливает указатель на следующий цензурный фильтр. Используется для организации
* цепочки из нескольких фильтров.
*
* @param AbstractCensorshipFilter $nextFilter - объект следующего фильтра.
* @return AbstractCensorshipFilter - вернёт объект следующего фильтра. Возврат объекта испольузется
* для возможности в одной строчке кода строить цепочку фильтров.
*/
public function setNextFilter($nextFilter)
{
$this->nextFilter = $nextFilter;
return $this->nextFilter;
}
/**
* Выполняет цензурную проверку текста.
* @param string $text - исходный текст
* @return string - вернёт строку со статусом проверки. В случае, если текст проходит цензуру,
* метод вернёт значение 'valid', в противном случае вернёт символьный код ошибки.
*/
abstract public function checkText($text);
/**
* Выполняет цензурную проверку текста следующим фильтром.
* @return string - вернёт строку со статусом проверки. В случае, если текст проходит цензуру,
* метод вернёт значение 'valid', в противном случае вернёт символьный код ошибки.
* Если следующий фильтр не установлен, то метод вернёт значение 'valid'
*/
public function checkTextByNextFilter($text)
{
if ($this->nextFilter) {
return $this->nextFilter->checkText($text);
} else {
return 'valid';
}
}
}
/**
* Цензурный фильтр на матерные/ругательные слова
*/
class BadWordsCensorshipFilter extends AbstractCensorshipFilter
{
/**
* Выполняет цензурную проверку текста.
* @param string $text - исходный текст
* @return string - вернёт строку со статусом проверки. В случае, если текст проходит цензуру,
* метод вернёт значение 'valid', в противном случае вернёт символьный код ошибки.
*/
public function checkText($text)
{
$badWords = array(
'бля',
'сука',
'пидор',
'пидр',
'пизди',
'пезде',
'пизда',
'хуй',
'хуел',
'ёбла',
'ебла',
'ебать',
'ебан'
);
foreach($badWords as $z => $v) {
$badWords[$z] = '[\w\-\_]*'.preg_quote($v).'[\w\-\_]*';
}
/*
Ищем матерные слова.
Метасимвол \b обозначает границы слова
*/
$existBadWords = false;
$pattern = '/\b('.implode('|', $badWords).')\b/iu';
if (preg_match($pattern, $text)) {
$existBadWords = true;
}
if ($existBadWords == true) {
return 'error_bad_words_exist';
} else {
return $this->checkTextByNextFilter($text);
}
}
}
/**
* Цензурный фильтр на email-адреса
*/
class EmailCensorshipFilter extends AbstractCensorshipFilter
{
/**
* Выполняет цензурную проверку текста.
* @param string $text - исходный текст
* @return string - вернёт строку со статусом проверки. В случае, если текст проходит цензуру,
* метод вернёт значение 'valid', в противном случае вернёт символьный код ошибки.
*/
public function checkText($text)
{
$badWords = array();
//Ищем email-адреса
$existEmails = false;
$pattern = '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/iu';
if (preg_match($pattern, $text)) {
$existEmails = true;
}
if ($existEmails == true) {
return 'error_emails_exist';
} else {
return $this->checkTextByNextFilter($text);
}
}
}
/**
* Цензурный фильтр на интернет-ссылки
*/
class WebLinksCensorshipFilter extends AbstractCensorshipFilter
{
/**
* Выполняет цензурную проверку текста.
* @param string $text - исходный текст
* @return string - вернёт строку со статусом проверки. В случае, если текст проходит цензуру,
* метод вернёт значение 'valid', в противном случае вернёт символьный код ошибки.
*/
public function checkText($text)
{
$badWords = array();
//Ищем ссылки
$existLinks = false;
$pattern = '/[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*)/iu';
if (preg_match($pattern, $text)) {
$existLinks = true;
}
if ($existLinks == true) {
return 'error_web_links_exist';
} else {
return $this->checkTextByNextFilter($text);
}
}
}
?>
test.php
Содержит тестовый скрипт, в котором создаются объекты трёх цензурных фильтров:- $badWordsFilter – фильтр ругательных слов;
- $emailFilter – фильтр email-адресов;
- $webLinksFilter – фильтр ссылок.
Далее с помощью метода setNextFilter($nextFilter) фильтры устанавливаются в цепочку. Затем, используя метод checkText($text), по нашей цензурной цепочке прогоняется несколько текстов и результат проверки выводится на экран.
<?
//Подключаем классы для работы с цензурными фильтрами
require_once(__DIR__.'/censorship_filters.classes.php');
//Создаём объекты фильтров
$badWordsFilter = new BadWordsCensorshipFilter(); //Фильтр плохих слов
$emailFilter = new EmailCensorshipFilter(); //Фильтр email-адресов
$webLinksFilter = new WebLinksCensorshipFilter(); //Фильтр веб-ссылок
//Устанавливаем цепочку фильтров
$badWordsFilter->setNextFilter($emailFilter)->setNextFilter($webLinksFilter);
//Выполняем проверку на мат
$text = 'Он спиздил идею';
echo 'Результат проверки на мат: '.$badWordsFilter->checkText($text).'<br><br>';
//Выполняем проверку на email
$text = 'Напишите мне на mail-01@test-site.ru, пообщаемся';
echo 'Результат проверки на наличие email-адресов: '.$badWordsFilter->checkText($text).'<br><br>';
//Выполняем проверку на ссылки
$text = 'На test-site.ru можете увидеть что-то интересное';
echo 'Результат проверки на наличие ссылок: '.$badWordsFilter->checkText($text).'<br><br>';
//Выполняем проверку цензурного выражения
$text = 'Съешь ещё этих мягких французских булок, да выпей чаю';
echo 'Результат проверки цензурного выражения: '.$badWordsFilter->checkText($text).'<br><br>';
?>
Результат работы скрипта test.php
Результат проверки на мат: error_bad_words_exist
Результат проверки на наличие email-адресов: error_emails_exist
Результат проверки на наличие ссылок: error_web_links_exist
Результат проверки цензурного выражения: valid