Паттерн Intercepting Filter

Intercepting Filter

Содержание

1 Ситуация
2 Задача
3 Требования
4 Решение
    4.1 Структура
    4.2 Участники и обязательства
        4.2.1 FilterManager
        4.2.2 FilterChain
        4.2.3 FilterOne, FilterTwo, FilterThree
        4.2.4 Target
    4.3 Стратегии
        4.3.1 Стратегия Custom Filter
        4.3.2 Стратегия Standard Filter
        4.3.3 Стратегия Base Filter
        4.3.4 Стратегия Template Filter
5 Результаты
6 Родственные паттерны

1. Ситуация

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

2. Задача

Первичная и последующая обработки Web-запроса и ответа клиенту являются необходимыми.

Прежде чем начать основную обработку запроса в Web-приложении его часто приходится подвергать различным предварительным тестам. Например:

  • Был ли клиент аутентифицирован?

  • Является ли действительной сессия клиента?

  • Проверен ли на надежность IP-адрес клиента?

  • Не нарушает ли установленные ограничения путь запроса?

  • В какой кодировке клиент отправляет данные?

  • Поддерживается ли тип броузера, используемый клиентом?

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

В классическом решении содержится набор проверок условий, причем при невыполнении любого из них работа с запросом прерывается. При решении задач более сложного уровня можно использовать стандартную методику вложенности условий, однако это делает код более уязвимым, так как поток фильтров и само действие этих фильтров компилируются в приложении.

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

3. Требования

  • Общая обработка, такая как проверка схемы кодировки данных или ведение журнала записей, должна выполняться при каждом запросе.

  • Желательно централизовать общую логику.

  • Службы должны легко добавляться и удаляться без воздействия при этом на существующие компоненты, что позволяет использовать их во множестве различных комбинаций. Например:

    • Вход в систему и аутентификация

    • Отладка и преобразование исходящих данных для отдельного клиента

    • Декомпрессия и конвертирование схемы кодировки входящих данных

4. Решение

Для обработки общих служб создайте сменные фильтры стандартным образом, не требуя при этом изменений в основном коде обработки запроса. Фильтры перехватывают входящие и исходящие потоки, гарантируя для них предварительную и последующую обработку. Подобные фильтры можно беспрепятственно добавлять и удалять без всякого взаимодействия с существующим кодом.

Мы можем дополнить основную обработку множеством различных общих служб, таких как безопасность, вход в систему и отладка. Данные фильтры являются компонентами, независящими от основного кода приложения, и могут добавляться и удаляться посредством деклараций. Например, файл конфигурации размещения можно изменить таким образом, чтобы он запускал цепочку фильтров. При помощи этого файла можно устанавливать соответствие между цепочкой фильтров и определенными URL-адресами. При запросе клиентом ресурса, входящего в список таких адресов, перед вызовом данного целевого ресурса в обязательном порядке будут вызваны все фильтры цепочки.

4.1 Структура

На рисунке 7.1 представлен паттерн Intercepting Filter.

Рисунок 7.1 Классовая диаграмма паттерна Intercepting Filter

4.2 Участники и обязательства

На рисунке 7.2 представлен паттерн Intercepting Filter.

Рисунок 7.2 Циклограмма паттерна Intercepting Filter

4.2.1 FilterManager

FilterManager управляет обработкой файлов. Он создает объект FilterChain с фильтрами в установленном порядке, а затем инициализирует обработку.

4.2.2 FilterChain

FilterChain является упорядоченной коллекцией независящих друг от друга фильтров.

4.2.3 FilterOne, FilterTwo, FilterThree

Отдельные фильтры, отображаемые на целевой объект. FilterChain координирует обработку.

4.2.4 Target

Target является ресурсом, запрашиваемым клиентом.

4.3 Стратегии

4.3.1 Стратегия Custom Filter

Фильтр реализуется посредством заказной стратегии, задаваемой разработчиком. Она является менее гибкой и мощной, чем предпочитаемая стратегия Standard Filter (описана в следующем разделе), и доступна только в контейнерах, поддерживающих спецификацию Servlet 2.3. Стратегия Custom Filter является менее мощной, так как не может обеспечить упаковку объектов запросов и ответов стандартным переносным способом. Кроме того, объект запроса нельзя изменить, и если фильтр отвечает за исходящий поток, то необходимо ввести некий буферный механизм. Реализуя стратегию Custom Filter для упаковки фильтров вокруг центральной логики обработки запросов, разработчик может использовать паттерн Decorator [GoF]. Например, для упаковки фильтра аутентификации можно использовать фильтр отладки. В примерах 7.1 и 7.2 показано, как программно реализовать данный механизм.

Пример 7.1 Реализация фильтра Debugging Filter

public class DebuggingFilter implements Processor {
 
private Processor target;

 
public DebuggingFilter(Processor myTarget) {
   
target = myTarget;
 
}

 
public void execute(ServletRequest req, ServletResponse res)
     
throws IOException, ServletException {
   
// Обработка фильтра,
    // например, отображение параметров запроса
   
target.execute(req, res);
 
}
}

Пример 7.2 Реализация фильтра Core Processor

public class CoreProcessor implements Processor {
 
private Processor target;

 
public CoreProcessor() {
   
this(null);
 
}

 
public CoreProcessor(Processor myTarget) {
   
target = myTarget;
 
}

 
public void execute(ServletRequest req, ServletResponse res)
     
throws IOException, ServletException {
   
// Основная обработка
 
}
}

В контроллере сервлета при обработке входящих запросов все полномочия передаются методу processRequest, как показано в примере 7.3.

Пример 7.3 Обработка запросов

public void processRequest(ServletRequest req, ServletResponse res)
           
throws IOException, ServletException {
 
Processor processors = new DebuggingFilter(
   
new AuthenticationFilter(new CoreProcessor()));
  processors.execute
(req, res);

 
// Отправка запроса следующему ресурсу, которым,
  // вероятно, является View
 
dispatcher.dispatch(req, res);
}

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

Пример 7.4 Сообщения, записываемые в стандартный исходящий поток

Debugging filter preprocessing completed...
Authentication filter processing completed...
Core processing completed...
Debugging filter post-processing completed...

Цепочка из узлов обработки выполняется в определенном порядке. Каждый такой узел в цепочке, кроме последнего, рассматривается как фильтр. В последнем обрабатывающем компоненте инкапсулируется основная обработка, которую требуется выполнить для всех запросов. При таком подходе для изменения обработки запросов требуется изменить код класса CoreProcessor и всех классов фильтров.

На рисунке 7.3 представлена циклограмма, описывающая поток управления, полученный путем использования фильтров из примеров 7.1, 7.2 и 7.3.

Рисунок 7.3 Циклограмма для стратегии Custom Filter, реализация decorator

Обратите внимание, что при использовании реализации decorator каждый фильтр вызывает следующий напрямую, несмотря на использование общего интерфейса. С другой стороны, данная стратегия может быть реализована при помощи FilterManager и FilterChain. В этом случае компоненты координируют фильтры и управляют их обработкой, причем отдельные фильтры не взаимодействуют друг с другом напрямую. Хоть данная модель и приближается к той, которая используется для реализации, совместимой с сервлетами версии 2.3, она все же считается заказной стратегией. В примере 7.5 представлен листинг класса FilterManager, который создает FilterChain из примера 7.6. FilterChain добавляет в цепочку фильтры в определенном порядке (для краткости это было сделано в конструкторе FilterChain, но правильнее будет вставлять код вместо соответствующих комментариев), обрабатывает их и, наконец, обрабатывает целевой ресурс. На рисунке 7.4 представлена циклограмма этого кода.

Рисунок 7.4 Циклограмма стратегии Custom Filter, реализация nondecorator

Пример 7.5 FilterManager – Стратегия Custom Filter

public class FilterManager {
 
public void processFilter(Filter target,
      javax.servlet.http.HttpServletRequest request,
      javax.servlet.http.HttpServletResponse response
)
     
throws javax.servlet.ServletException, java.io.IOException {
   
FilterChain filterChain = new FilterChain();

   
// Менеджер фильтров создает здесь
    // при необходимости цепочку фильтров

    // Поток запросов через цепочку фильтров
   
filterChain.processFilter(request, response);

   
// Обработка целевого ресурса
   
target.execute(request, response);
 
}
}

Пример 7.6 FilterChain – Стратегия Custom Filter

public class FilterChain {
 
// Цепочка фильтров
 
private Vector myFilters = new Vector();

 
// Создание нового FilterChain
 
public FilterChain() {
   
// В качестве примера встроить службы
    // фильтров, используемые по-умолчанию
    // Обычно это делается в FilterManager,
    // однако здесь показано для примера
   
addFilter(new DebugFilter());
    addFilter
(new LoginFilter());
    addFilter
(new AuditFilter());
 
}

 
public void processFilter(javax.servlet.http.HttpServletRequest request,
      javax.servlet.http.HttpServletResponse response
)
     
throws javax.servlet.ServletException, java.io.IOException {
   
Filter filter;

   
// применить фильтры
   
Iterator filters = myFilters.iterator();
   
while (filters.hasNext()) {
     
filter = (Filter) filters.next();
     
// пропустить запрос и ответ через
      // различные фильтры
     
filter.execute(request, response);
   
}
  }

 
public void addFilter(Filter filter) {
   
myFilters.add(filter);
 
}
}

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

Стратегия Standard Filter позволяет решить поставленные задачи, совершенствуя характеристики спецификации Servlet 2.3, которая обеспечивает стандартное решение дилеммы фильтров.

ПРИМЕЧАНИЕ:

Спецификация Servlet 2.3 находится сейчас в черновом варианте.

4.3.2 Стратегия Standard Filter

Управление фильтрами ведется посредством деклараций, используя при этом дескриптор размещения (это описано в спецификации Servlet 2.3). Спецификация Servlet 2.3 включает в себя стандартный механизм для создания цепочек фильтров, а также для простого добавления и удаления фильтров из таких цепочек. Фильтры строятся вокруг интерфейсов и добавляются или удаляются путем редактирования деклараций в дескрипторе размещения Web-приложения.

В качестве примера данной стратегии мы создадим фильтр, обрабатывающий запросы любого типа кодирования, при этом каждый запрос должен обрабатываться подобным образом в основном коде обработки запросов. Для чего это нужно? HTML-формы, которые включают в себя загрузку файлов, используют отличный от других форм тип кодирования. Таким образом, доступ к данным форм, сопровождающих загрузку, получить путем простого вызова метода getParameter() нельзя. Для этого создается два фильтра, обрабатывающие запрос путем трансляции всех типов кодирования в один совместимый формат. В выбранном формате все данные форм должны присутствовать в виде атрибутов запроса.

Один фильтр обрабатывает стандартную форму кодирования типа application/ x-www-form-urlencoded, остальные – менее распространенный тип кодирования multipart/form-data, используемый в формах, которые содержат загрузку файлов. Фильтры транслируют все данные форм в атрибуты запроса, поэтому основной механизм обработки запросов может обрабатывать каждый запрос одинаково, не прибегая к обработке других кодировок.

В примере 7.8 представлен фильтр, транслирующий запрос при помощи общей схемы кодирования формы приложения. В примере 7.9 содержится фильтр, обрабатывающий трансляцию запросов, которые используют схему кодирования формы с множеством элементов. Код для этих фильтров базируется на последней версии спецификации Servlet 2.3. Здесь используется базовый фильтр, от которого наследуются два данных фильтра (смотрите раздел «Стратегия Base Filter»). Базовый фильтр, представленный в примере 7.7, обеспечивает поведение, установленное по умолчанию, для стандартных методов обратного вызова фильтра.

Пример 7.7 Base Filter – Стратегия Standard Filter

public class BaseEncodeFilter implements javax.servlet.Filter {
 
private javax.servlet.FilterConfig myFilterConfig;

 
public BaseEncodeFilter() {
  }

 
public void doFilter(javax.servlet.ServletRequest servletRequest,
      javax.servlet.ServletResponse servletResponse,
      javax.servlet.FilterChain filterChain
) throws java.io.IOException,
      javax.servlet.ServletException
{
   
filterChain.doFilter(servletRequest, servletResponse);
 
}

 
public javax.servlet.FilterConfig getFilterConfig() {
   
return myFilterConfig;
 
}

 
public void setFilterConfig(javax.servlet.FilterConfig filterConfig) {
   
myFilterConfig = filterConfig;
 
}
}

Пример 7.8 StandardEncodeFilter – Стратегия Standard Filter

public class StandardEncodeFilter extends BaseEncodeFilter {
 
// Создает новый StandardEncodeFilter
 
public StandardEncodeFilter() {
  }

 
public void doFilter(javax.servlet.ServletRequest servletRequest,
      javax.servlet.ServletResponse servletResponse,
      javax.servlet.FilterChain filterChain
) throws java.io.IOException,
      javax.servlet.ServletException
{

   
String contentType = servletRequest.getContentType();
   
if ((contentType == null)
       
|| contentType
            .equalsIgnoreCase
("application/x-www-form-urlencoded")) {
     
translateParamsToAttributes(servletRequest, servletResponse);
   
}

   
filterChain.doFilter(servletRequest, servletResponse);
 
}

 
private void translateParamsToAttributes(ServletRequest request,
      ServletResponse response
) {
   
Enumeration paramNames = request.getParameterNames();

   
while (paramNames.hasMoreElements()) {
     
String paramName = (String) paramNames.nextElement();

      String
[] values;

      values = request.getParameterValues
(paramName);
      System.err.println
("paramName = " + paramName);
     
if (values.length == 1)
       
request.setAttribute(paramName, values[0]);
     
else
       
request.setAttribute(paramName, values);
   
}
  }
}

Пример 7.9 MultipartEncodeFilter – Стратегия Standard Filter

public class MultipartEncodeFilter extends BaseEncodeFilter {
 
public MultipartEncodeFilter() {
  }

 
public void doFilter(javax.servlet.ServletRequest servletRequest,
      javax.servlet.ServletResponse servletResponse,
      javax.servlet.FilterChain filterChain
) throws java.io.IOException,
      javax.servlet.ServletException
{
   
String contentType = servletRequest.getContentType();
   
// Если в запросе используется множественная кодировка,
    // то он только фильтруется
   
if (contentType.startsWith("multipart/form-data")) {
     
try {
       
String uploadFolder = getFilterConfig().getInitParameter(
           
"UploadFolder");
       
if (uploadFolder == null)
         
uploadFolder = ".";

       
/** Класс MultipartRequest:
         * Copyright (C) 2001, автор Jason Hunter
         *
<jhunter@servlets.com>. Все права защищены.
         **/
       
MultipartRequest multi = new MultipartRequest(servletRequest,
            uploadFolder,
1 * 1024 * 1024);
        Enumeration params = multi.getParameterNames
();
       
while (params.hasMoreElements()) {
         
String name = (String) params.nextElement();
          String value = multi.getParameter
(name);
          servletRequest.setAttribute
(name, value);
       
}

       
Enumeration files = multi.getFileNames();
       
while (files.hasMoreElements()) {
         
String name = (String) files.nextElement();
          String filename = multi.getFilesystemName
(name);
          String type = multi.getContentType
(name);
          File f = multi.getFile
(name);
         
// В этом месте кода сделайте
          // с файлом все, что необходимо
       
}
      }
catch (IOException e) {
       
LogManager.logMessage("error reading or saving file" + e);
     
}
    }
   
// конец цикла if
   
filterChain.doFilter(servletRequest, servletResponse);
 
}
 
// конец метода doFilter()
}

Примером 7.10 является выдержка из дескриптора размещения Web-приложения. В коде показано, как регистрировать описанные фильтры и отображать их на ресурс, которым в данном случае является простой тестовый сервлет. Циклограмму этого примера можно увидеть на рисунке 7.5.

Пример 7.10 Deployment Descriptor – Стратегия Standard Filter

.
.
.
<filter>
    <filter-name>StandardEncodeFilter</filter-name>
    <display-name>StandardEncodeFilter</display-name>
    <description></description>
    <filter-class> corepatterns.filters.encodefilter.
            StandardEncodeFilter</filter-class>
  </filter>
  <filter>
    <filter-name>MultipartEncodeFilter</filter-name>
    <display-name>MultipartEncodeFilter</display-name>
    <description></description>
    <filter-class>corepatterns.filters.encodefilter.
            MultipartEncodeFilter</filter-class>
    <init-param>
      <param-name>UploadFolder</param-name>
      <param-value>/home/files</param-value>
    </init-param>
 </filter>
.
.
.
<filter-mapping>
    <filter-name>StandardEncodeFilter</filter-name>
    <url-pattern>/EncodeTestServlet</url-pattern>
  </filter-mapping>
  <filter-mapping>
    <filter-name>MultipartEncodeFilter</filter-name>
    <url-pattern>/EncodeTestServlet</url-pattern>
  </filter-mapping>
.
.
.

Рисунок 7.5 Циклограмма Intercepting Filter, Стратегия Standard Filter – пример конвертирования кодировки

При запросе клиентом сервлета-контроллера StandardEncodeFilter и MultiPartEncodeFilter перехватывают управление. Контейнер выполняет роль менеджера фильтров и направляет контроль к этим фильтрам путем вызова их методов doFilter. После завершения обработки каждый фильтр передает управление содержащему его компоненту FilterChain, которому указывается вызвать следующий фильтр. Если оба фильтра получили, а потом отдали управление, то следующим компонентом, получающим управление будет целевой ресурс, в данном случае – сервлет-контроллер.

Фильтры, как указано в спецификации Servlet 2.3, поддерживают упаковку объектов запросов и ответов. Данное свойство обеспечивает гораздо более мощный механизм по сравнению с тем, который имеет место в случае использования заказной реализации, предложенной стратегией Custom Filter. Объединение двух стратегий считается заказным, следовательно, при этом теряются возможности стратегии Standard Filter, поддерживаемые спецификацией сервлета.

4.3.3 Стратегия Base Filter

Базовый фильтр можно назвать общим суперклассом для всех фильтров. Общие свойства могут быть инкапсулированы в базовом фильтре и распределены среди всех остальных фильтров. Например, удобным будет включить в базовый фильтр поведение, установленное по умолчанию, для методов обратного вызова контейнера в стратегии Declared Filter. Это проиллюстрировано в примере 7.11.

Пример 7.11 Стратегия Base Filter

public class BaseEncodeFilter implements javax.servlet.Filter {
 
private javax.servlet.FilterConfig myFilterConfig;

 
public BaseEncodeFilter() {
  }

 
public void doFilter(javax.servlet.ServletRequest servletRequest,
      javax.servlet.ServletResponse servletResponse,
      javax.servlet.FilterChain filterChain
) throws java.io.IOException,
      javax.servlet.ServletException
{

   
filterChain.doFilter(servletRequest, servletResponse);
 
}

 
public javax.servlet.FilterConfig getFilterConfig() {
   
return myFilterConfig;
 
}

 
public void setFilterConfig(javax.servlet.FilterConfig filterConfig) {
   
myFilterConfig = filterConfig;
 
}
}

4.3.4 Стратегия Template Filter

Использование базового фильтра, от которого наследуются все остальные (см. раздел «стратегия Base Filter»), позволяет базовым классам обеспечить функциональность шаблонного метода [Gof]. В этом случае базовый фильтр используется для указания общих действий, обязательных для каждого фильтра, а конкретное описание того, как их выполнять, содержится в подклассах фильтров. Обычно базовые методы просто налагают ограниченную структуру на каждый шаблон. Подобная стратегия совместима со всеми остальными. В листингах из примеров 7.12 и 7.13 показано использование данной стратегии вместе с Declared Filter.

В примере 7.12 показан базовый фильтр TemplateFilter.

Пример 7.12 Использование стратегии Template Filter

public abstract class TemplateFilter implements javax.servlet.Filter {
 
private FilterConfig filterConfig;

 
public void setFilterConfig(FilterConfig fc) {
   
filterConfig = fc;
 
}

 
public FilterConfig getFilterConfig() {
   
return filterConfig;
 
}

 
public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain chain
) throws IOException, ServletException {
   
// Общая обработка для всех фильтров
   
doPreProcessing(request, response, chain);

   
// Общая обработка для всех фильтров
   
doMainProcessing(request, response, chain);

   
// Общая обработка для всех фильтров
   
doPostProcessing(request, response, chain);

   
// Общая обработка для всех фильтров

    // Передача управления следующему фильтру цепочки
    // или целевому ресурсу
   
chain.doFilter(request, response);
 
}

 
public void doPreProcessing(ServletRequest request,
      ServletResponse response, FilterChain chain
) {
  }

 
public void doPostProcessing(ServletRequest request,
      ServletResponse response, FilterChain chain
) {
  }

 
public abstract void doMainProcessing(ServletRequest request,
      ServletResponse response, FilterChain chain
);
}

При заданном определении класса для TemplateFilter каждый фильтр может быть реализован как подкласс, который должен лишь реализовывать метод doMainProcessing. Не смотря на реализацию при необходимости всех трех методов, у данных подклассов есть альтернатива. Код из примера 7.13 является подклассом фильтра, реализующим один обязательный метод (продиктованный шаблонным фильтром) и один необязательный метод для первичной обработки. Циклограмма этой стратегии представлена на рисунке 7.6.

Пример 7.13 Debugging Filter

public class DebuggingFilter extends TemplateFilter {
 
public void doPreProcessing(ServletRequest req, ServletResponse res,
      FilterChain chain
) {
   
// первичная обработка
 
}

 
public void doMainProcessing(ServletRequest req, ServletResponse res,
      FilterChain chain
) {
   
//
    // основная обработка
 
}
}

Рисунок 7.6 Intercepting Filter, циклограмма стратегии Template Filter

В циклограмме на рисунке 7.6 подклассы фильтров (такие как DebuggingFilter) задают специальную обработку путем переопределения абстрактного метода doMainProcessing и, при необходимости, методов doPreProcessing и doPostProcessing. Таким образом, шаблонный фильтр налагает структуру на обработку каждого фильтра, а также позволяет инкапсулировать общий для всех фильтров код.

5. Результаты

  • Централизуется управление со слабосвязанными обработчиками

    Фильтры обеспечивают общее пространство для управления обработкой всех запросов, как это делает контроллер. Они больше подходят для отправки запросов и ответов с последующей их обработкой при помощи целевого ресурса, например, контроллера. Кроме этого, контроллер часто соединяет управление большим количеством разрозненных общих служб, таких как авторизация, вход в систему и шифрование. Использование фильтров позволяет комбинировать в разных вариантах еще более слабосвязанные обработчики.

  • Улучшается возможность повторного использования

    Фильтры способствуют более чистому разделению программ и поддерживают их повторное использование. Данные встраиваемые перехватчики прозрачно добавляются или удаляются из существующего кода. Благодаря своему стандартному интерфейсу они работают в любой комбинации и могут повторно использоваться для различных презентаций.

  • Декларативная и гибкая конфигурация

    Службы можно комбинировать в различных вариациях перестановок, при этом повторная компиляция основного кода не требуется.

  • Распределение информации является неэффективным

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

6 Родственные паттерны

  • Front Controller

    Контроллер решает похожие проблемы, однако больше подходит для управления основной обработкой.

  • Decorator [GoF]

    Паттерн Intercepting Filter является родственным для паттерна Decorator, обеспечивающего динамически встраиваемые упаковщики.

  • Template Method [GoF]

    Паттерн Template Method используется для реализации стратегии Template Filter.

  • Interceptor [POSA2]

    Паттерн Intercepting Filter связан с паттерном Interceptor, позволяющим прозрачно добавлять, а также автоматически инициировать службы.

  • Pipes and Filters [POSA1]

    Паттерн Intercepting Filter связан с паттернами Pipes and Filters.