Статьи

Простой пример для иллюстрации шаблона проектирования цепочки ответственности

Книга шаблонов GoF Design заявляет намерение шаблона Цепочки ответственности как:

Избегайте связывания отправителя запроса с получателем, предоставляя более чем одному объекту возможность обработать запрос. Цепочка получения объектов и передачи запроса по цепочке, пока объект не обрабатывает его.

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

В этом кратком введении я хотел бы привести очень простой пример, иллюстрирующий эту модель. В этом примере мы создаем цепочку синтаксических анализаторов файлов, так что в зависимости от формата файла, передаваемого синтаксическому анализатору, анализатор должен решить, собирается ли он проанализировать файл или передать запрос своему синтаксическому анализатору-преемнику для принятия действий. Синтаксический анализатор, который мы бы включили в цепочку: простой анализатор текстовых файлов, анализатор JSON-файлов, анализатор CSV-файлов и анализатор XML-файлов. Логика синтаксического анализа в каждом из этих синтаксических анализаторов не анализирует какой-либо файл, вместо этого она просто выводит сообщение о том, кто передает запрос на какой файл. Затем мы заполняем имена файлов разных форматов в список, а затем перебираем их, передавая имя файла первому анализатору в списке.

Давайте определим класс Parser, сначала позвольте мне показать диаграмму классов для класса Parser:

Код Java для того же:

public class Parser {
   
  private Parser successor;
   
  public void parse(String fileName){
    if ( getSuccessor() != null ){
      getSuccessor().parse(fileName);
    }
    else{
      System.out.println("Unable to find the correct parser for the file: "+fileName);
    }
  }
   
  protected boolean canHandleFile(String fileName, String format){
    return (fileName == null) || (fileName.endsWith(format));
         
  }
 
  Parser getSuccessor() {
    return successor;
  }
 
  void setSuccessor(Parser successor) {
    this.successor = successor;
  }
}

Теперь мы создадим разные обработчики для анализа различных форматов файлов, а именно: простой текстовый файл, файл JSON, файл CSV, файл XML, и они расширяются из класса Parser и переопределяют метод parse. Я сохранил простую реализацию различных парсеров, и эти методы оценивают, имеет ли файл тот формат, который они ищут. Если конкретный обработчик не может обработать запрос, т.е. формат файла не соответствует тому, что он ищет, то родительский метод обрабатывает такие запросы. Метод-обработчик в родительском классе просто вызывает тот же метод в обработчике-преемнике.

Простой текстовый парсер:

public class TextParser extends Parser{
 
  public TextParser(Parser successor){
    this.setSuccessor(successor);
  }
   
  @Override
  public void parse(String fileName) {
    if ( canHandleFile(fileName, ".txt")){
      System.out.println("A text parser is handling the file: "+fileName);
    }
    else{
      super.parse(fileName);
    }
     
  }
 
}

Парсер JSON:

public class JsonParser extends Parser {
 
  public JsonParser(Parser successor){
    this.setSuccessor(successor);
  }
   
  @Override
  public void parse(String fileName) {
    if ( canHandleFile(fileName, ".json")){
      System.out.println("A JSON parser is handling the file: "+fileName);
    }
    else{
      super.parse(fileName);
    }
 
  }
 
}

Анализатор CSV:

public class CsvParser extends Parser {
 
  public CsvParser(Parser successor){
    this.setSuccessor(successor);
  }
   
  @Override
  public void parse(String fileName) {
    if ( canHandleFile(fileName, ".csv")){
      System.out.println("A CSV parser is handling the file: "+fileName);
    }
    else{
      super.parse(fileName);
    }
  }
 
}

Анализатор XML:

public class XmlParser extends Parser {
   
  @Override
  public void parse(String fileName) {
    if ( canHandleFile(fileName, ".xml")){
      System.out.println("A XML parser is handling the file: "+fileName);
    }
    else{
      super.parse(fileName);
    }
  }
 
}

Теперь, когда у нас есть все настройки обработчиков, нам нужно создать цепочку обработчиков. В этом примере мы создаем цепочку: TextParser -> JsonParser -> CsvParser -> XmlParser. И если XmlParser не может обработать запрос, класс Parser выдает сообщение о том, что запрос не был обработан. Давайте посмотрим код для клиентского класса, который создает список имен файлов, а затем создает цепочку, которую я только что описал.

import java.util.List;
import java.util.ArrayList;
 
public class ChainOfResponsibilityDemo {
 
  /**
   * @param args
   */
  public static void main(String[] args) {
     
    //List of file names to parse.
    List<String> fileList = populateFiles();
     
    //No successor for this handler because this is the last in chain.
    Parser xmlParser = new XmlParser();
 
    //XmlParser is the successor of CsvParser.
    Parser csvParser = new CsvParser(xmlParser);
     
    //CsvParser is the successor of JsonParser.
    Parser jsonParser = new JsonParser(csvParser);
     
    //JsonParser is the successor of TextParser.
    //TextParser is the start of the chain.
    Parser textParser = new TextParser(jsonParser);
     
    //Pass the file name to the first handler in the chain.
    for ( String fileName : fileList){
      textParser.parse(fileName);
    }
 
  }
   
  private static List<String> populateFiles(){
     
    List<String> fileList = new ArrayList<>();
    fileList.add("someFile.txt");
    fileList.add("otherFile.json");
    fileList.add("xmlFile.xml");
    fileList.add("csvFile.csv");
    fileList.add("csvFile.doc");
     
    return fileList;
  }
 
}

В приведенном выше списке имен файлов я специально добавил имя файла, для которого не создан обработчик. Запуск приведенного выше кода дает нам вывод:

A text parser is handling the file: someFile.txt
A JSON parser is handling the file: otherFile.json
A XML parser is handling the file: xmlFile.xml
A CSV parser is handling the file: csvFile.csv
Unable to find the correct parser for the file: csvFile.doc