Всем привет! В данной статье рассмотрим паттерн проектирования Composite ("Компоновщик"). Начнем немного с теории.  Паттерн Composite, исходя уже с самого названия, позволяет скомпоновать объекты в структуры по типу "дерева", для предоставления иерархии от частного к целому, а также позволяя клиентам единообразно трактовать отдельные и составные объекты этого дерева. Данный паттерн используется для группировки мелких компонентов в более крупные, которые в свою очередь, могут стать основой для еще более крупных структур. Рассмотрим структуру классов данного паттерна. Центральное место в структуре данного паттерна занимает интерфейс или абстрактный класс Component - он содержит ряд методов и может реализовывать поведение по умолчанию или содержать методы без реализации для того чтобы конкретную реализацию задавали классы наследники. Класс Composite - это составной элемент дерева, он является наследником Component. Он содержит набор дочерних компонентов children: Componenntss[]. Это могут быть как простые компоненты-листья, так и другие компоненты-контейнеры. Методы данного класса переадресовывают основную работу своим дочерним компонентам, но и могут что-то добавлять своё к результату. Класс Leaf - конечный элемент дерева, он является наследником Componenta. Данный класс не содержит дочерних элементов. Он также реализует методы, полученные по наследству от класса Component. Некоторые методы могут не иметь смысла в данном классе (например метод add()), в таких случаях генерируется исключение. Класс Client использует общий интерфейс или абстрактный класс Composite для работы с объектами. Благодаря этому, клиенту не важно, что перед ним находится - простой или составной компонент дерева. Давайте рассмотрим данный паттерн на двух примерах.  Пример первый. Допустим мы хотим распечатать содержание книги. В содержании, как раз и просматривается древовидная структура. Каждая глава книги содержит в себе составные части — параграфы. Приступим к написанию кода. Вначале нам понадобится основной элемент — абстрактный класс ContentsComponent. В нем мы напишем несколько методов. public void add(ContentsComponent contentsComponent) — в данном методе в классах наследниках мы переопределим логику добавления элементов нашего содержания. public String getName() — этот метод мы будем использовать для получения названия главы или параграфа. public Integer getPages() — этот метод мы будем использовать для получения номера страницы. public void print() — переопределим в классах наследника непосредственно вывод на экран глав и параграфов. Далее напишем класс Contents и унаследуем его от ContentsComponent. Этот класс будет выполнять роль наших глав, соответственно он должен содержать список уже более мелких элементов (параграфов) — поэтому добавляем ArrayList <СontentsComponents> contentsComponents. Также добавляем поля name и pages, которые будут отвечать за название главы и номера страницы. Переопределим метод public void print() — он будет выводить название главы и страницы, а также перебирать все элементы из contentsComponents и вызывать у них свой метод print(). Пришло время написать еще один класс — ContentsItem. Этот класс также унаследуем от ContentsComponent и он будет отвечать за наши параграфы. В данном классе также есть поля name и pages(название параграфа и номер страницы), но уже нету списка новых элементов, так как он будет у нас как лист на дереве — не будет содержать в себе больше элементов. Также для него переопределим метод public void print() — где выведем название параграфа и номер страницы. Напишем еще один класс Book, который будет содержать ContentsComponent contents, то есть наше содержание и напишем метод public void printContents(), который будет выводить содержание на печать. Осталось протестировать весь наш код, напишем класс TestDrive, в котором создадим содержание для книги. Вначале создадим само содержание, потом главы и потом каждой главе добавим параграфы.  Вот такой должен получиться вывод на экран. Пример второй. Допустим у нас есть такая задача как распечатать текст, но печатать можно только символы (сами буквы и знаки препинания). То есть предварительно надо текст поделить на абзацы, потом абзацы — на предложения, предложения на — слова, и слова — на символы и уже символы выводить на экран. Давайте приступим. Вначале, как и в предыдущем примере, создадим основной абстрактный класс TextComponent. Он будет содержать три метода: public void add(TextComponent textComponent) — где мы в классах наследниках переопределим добавление новых элементов, строя тем самым древовидную структуру. public String getText() — получение текста. public void print() — вывод на экран. Начнем с класса Text — самого главного класса. В данном классе мы создадим список абзацев ArrayList <TextComponent>paragraphs, потому что текст состоит из абзацев. Напишем метод public void setItemComponents() — в нем текст будем делить на абзацы и добавлять в список абзацев. Также переопределим метод public void print() — здесь тоже все просто вызываем метод setItemComponents() — делим текст на абзацы и потом для каждого элемента (то есть абзаца) вызываем метод print(). Следующий класс Paragraph — абзац. Он очень похож на предыдущий класс. Здесь у нас уже есть список предложений ArrayList<TextComponent>sentences, а также другая логика разделения каждого абзаца на предложения (метод public void setItemComponents()). Также как и в предыдущем классе метод public void print() делит текст на предложения и потом для каждого элемента (то есть предложения) вызываем метод print(). Следующие два класса Sentence и World комментировать не буду — они аналогичны предыдущим. И наконец последний класс Symbol (символ) — самый маленький элемент. В нем в методе public void print() мы наконец выводим на экран символ. Протестируем наш весь код. Напишем класс TestDriveSecond, в котором создадим рандомный текст и вызовем метод print() у объекта класса Text.  В результате должны получить обычный вывод на экран. Вот мы и рассмотрели на двух примерах паттерн проектирования Composite. Спасибо всем, кто дочитал до конца. Всем пока!