Эта статья является частью нашего курса Академии под названием « Шаблоны проектирования Java» .
В этом курсе вы изучите огромное количество шаблонов проектирования и увидите, как они реализуются и используются в Java. Вы поймете причины, почему шаблоны так важны, и узнаете, когда и как применять каждый из них. Проверьте это здесь !
Содержание
1. Введение
В этом уроке мы поговорим об очень интересном шаблоне проектирования, Composite Pattern. Английское значение слова Composite состоит из сложных и взаимосвязанных частей. «Композит» означает «собирать вместе», и в этом суть этой модели проектирования.
Есть моменты, когда вы чувствуете необходимость древовидной структуры данных в вашем коде. Существует много вариантов структуры данных дерева, но иногда возникает необходимость в дереве, в котором обе ветви, а также листья дерева должны рассматриваться как единообразные.
Составной шаблон позволяет вам объединять объекты в древовидную структуру для представления иерархии части-целого, что означает, что вы можете создать дерево объектов, состоящее из разных частей, но которое можно рассматривать как одно целое. Composite позволяет клиентам обрабатывать отдельные объекты и композиции объектов равномерно, что является целью Composite Pattern.
Может быть много практических примеров составного паттерна. Система файловых каталогов, представление html в java, синтаксический анализатор XML — все это хорошо управляемые композиты, и все они могут быть легко представлены с использованием Composite Pattern. Но прежде чем углубляться в детали примера, давайте посмотрим на некоторые детали, касающиеся составного паттерна.
2. Что такое составной шаблон
Формальное определение Composite Pattern говорит о том, что он позволяет объединять объекты в древовидные структуры для представления иерархий части-целого. Композитный позволяет клиентам обрабатывать отдельные объекты и композиции объектов равномерно.
Если вы знакомы с древовидной структурой данных, вы будете знать, что у дерева есть родители и их дети. Родителю может быть несколько детей, но только один родитель на ребенка. В составном шаблоне элементы с дочерними элементами называются узлами, а элементы без дочерних элементов называются листами.
Составной шаблон позволяет нам строить структуры объектов в форме деревьев, которые содержат как композицию объектов, так и отдельные объекты в виде узлов. Используя составную структуру, мы можем применять одинаковые операции как к составным, так и к отдельным объектам. Другими словами, в большинстве случаев мы можем игнорировать различия между композициями объектов и отдельными объектами.
Составной шаблон имеет четыре участника:
- Составная часть
- лист
- композитный
- клиент
На следующем рисунке показана типичная структура составного объекта. Как вы можете видеть, у одного родителя может быть много дочерних, то есть составных, но только один родитель на каждого ребенка.
Компонент на диаграмме классов ниже определяет интерфейс для всех объектов в композиции, как составных, так и конечных узлов. Компонент может реализовывать поведение по умолчанию для универсальных методов.
Роль Composite состоит в том, чтобы определить поведение компонентов, имеющих дочерние элементы, и сохранить дочерние компоненты. Composite также реализует операции, связанные с Leaf. Эти операции могут иметь или не иметь никакого смысла; это зависит от функциональности, реализующей использование шаблона.
Лист определяет поведение элементов в композиции. Это достигается путем реализации операций, которые поддерживает Компонент. Лист также наследует методы, которые не обязательно имеют большой смысл для узла листа.
Клиент управляет объектами в композиции через интерфейс Компонента.
3. Пример составного паттерна
Составной шаблон может быть реализован везде, где у вас есть иерархическая природа системы или подсистемы, и вы хотите обрабатывать отдельные объекты и составы объектов единообразно. Файловая система, XML, Html или иерархия офиса (от президента до сотрудников) могут быть реализованы с использованием составного шаблона.
Давайте посмотрим на простой пример, где мы реализуем представление HTML в Java с использованием Composite Pattern. HTML является иерархическим по своей природе, он начинается с тега <html>, который является родительским или корневым тегом, и содержит другие теги, которые могут быть родительским или дочерним тегом.
Составной шаблон в Java может быть реализован с использованием класса Component в качестве абстрактного класса или интерфейса. В этом примере мы будем использовать абстрактный класс, который содержит все важные методы, используемые в составном классе и листовом классе.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package com.javacodegeeks.patterns.compositepattern; import java.util.List; public abstract class HtmlTag { public abstract String getTagName(); public abstract void setStartTag(String tag); public abstract void setEndTag(String tag); public void setTagBody(String tagBody){ throw new UnsupportedOperationException( "Current operation is not support for this object" ); } public void addChildTag(HtmlTag htmlTag){ throw new UnsupportedOperationException( "Current operation is not support for this object" ); } public void removeChildTag(HtmlTag htmlTag){ throw new UnsupportedOperationException( "Current operation is not support for this object" ); } public List<HtmlTag>getChildren(){ throw new UnsupportedOperationException( "Current operation is not support for this object" ); } public abstract void generateHtml(); } |
Класс HtmlTag
является классом компонента, который определяет все методы, используемые композитом и листовым классом. Есть несколько методов, которые должны быть общими в обоих расширенных классах; следовательно, эти методы хранятся абстрактно в вышеприведенном классе, чтобы обеспечить их реализацию в дочерних классах.
getTagName()
просто возвращает имя тега и должна использоваться обоими дочерними классами, т. getTagName()
классом и листовым классом.
Каждый элемент html должен иметь начальный тег и конечный тег, методы setStartTag
и setEndTag
используются для установки начального и конечного тега HTML-элемента и должны быть реализованы обоими дочерними классами, поэтому они сохраняются абстрактными в вышеприведенном классе. ,
Существуют методы, которые полезны только для составного класса и бесполезны для конечного класса. Просто предоставьте реализацию этих методов по умолчанию, и исключение является хорошей реализацией этих методов, чтобы избежать случайного вызова этих методов объектом, который не должен их поддерживать.
Метод generatHtml()
— это операция, которая должна поддерживаться обоими расширенными классами. Для простоты он просто печатает тег на консоль.
Теперь давайте посмотрим на класс Composite.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
package com.javacodegeeks.patterns.compositepattern; import java.util.ArrayList; import java.util.List; public class HtmlParentElement extends HtmlTag { private String tagName; private String startTag; private String endTag; private List<HtmlTag>childrenTag; public HtmlParentElement(String tagName){ this .tagName = tagName; this .startTag = "" ; this .endTag = "" ; this .childrenTag = new ArrayList<>(); } @Override public String getTagName() { return tagName; } @Override public void setStartTag(String tag) { this .startTag = tag; } @Override public void setEndTag(String tag) { this .endTag = tag; } @Override public void addChildTag(HtmlTag htmlTag){ childrenTag.add(htmlTag); } @Override public void removeChildTag(HtmlTag htmlTag){ childrenTag.remove(htmlTag); } @Override public List<HtmlTag>getChildren(){ return childrenTag; } @Override public void generateHtml() { System.out.println(startTag); for (HtmlTag tag : childrenTag){ tag.generateHtml(); } System.out.println(endTag); } } |
Класс HtmlParentElement
является составным классом, который реализует методы, такие как addChildTag
, removeChildTag
, getChildren
которые должны быть реализованы классом, чтобы стать составной структурой. Методом операции здесь является generateHtml
, который печатает тег текущего класса, а также перебирает его дочерние элементы и также вызывает их метод generateHtml
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
package com.javacodegeeks.patterns.compositepattern; public class HtmlElement extends HtmlTag{ private String tagName; private String startTag; private String endTag; private String tagBody; public HtmlElement(String tagName){ this .tagName = tagName; this .tagBody = "" ; this .startTag = "" ; this .endTag = "" ; } @Override public String getTagName() { return tagName; } @Override public void setStartTag(String tag) { this .startTag = tag; } @Override public void setEndTag(String tag) { this .endTag = tag; } @Override public void setTagBody(String tagBody){ this .tagBody = tagBody; } @Override public void generateHtml() { System.out.println(startTag+ "" +tagBody+ "" +endTag); } } |
HtmlElement
является листовым классом, и его основная задача заключается в реализации метода операции, который в этом примере является методом generateHtml
. Он печатает startTag
, необязательно tagBody, если есть, и endTag
дочернего элемента.
Давайте проверим этот пример.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package com.javacodegeeks.patterns.compositepattern; public class TestCompositePattern { public static void main(String[] args) { HtmlTag parentTag = new HtmlParentElement( "<html>" ); parentTag.setStartTag( "<html>" ); parentTag.setEndTag( "</html>" ); HtmlTag p1 = new HtmlParentElement( "<body>" ); p1.setStartTag( "<body>" ); p1.setEndTag( "</body>" ); parentTag.addChildTag(p1); HtmlTag child1 = new HtmlElement( "<p>" ); child1.setStartTag( "<p>" ); child1.setEndTag( "</p>" ); child1.setTagBody( "Testing html tag library" ); p1.addChildTag(child1); child1 = new HtmlElement( "<p>" ); child1.setStartTag( "<p>" ); child1.setEndTag( "</p>" ); child1.setTagBody( "Paragraph 2" ); p1.addChildTag(child1); parentTag.generateHtml(); } } |
Приведенный выше код приведет к следующему выводу:
1
2
3
4
5
6
|
< html > < body > < p >Testing html tag library</ p > < p >Paragraph 2</ p > </ body > </ html > |
В приведенном выше примере сначала мы создали родительский тег (<html>), затем добавляем к нему дочерний элемент, который является другим составного типа (<body>), и этот объект содержит два дочерних элемента (<p>).
Обратите внимание, что вышеприведенная структура представлена как иерархия части-целого, и вызов метода generateHtml()
для родительского тега позволяет клиенту одинаково обрабатывать композиции объектов. Как он генерирует HTML объекта и всех его дочерних элементов.
4. Когда использовать Composite Pattern
- Когда вы хотите представить частично-целые иерархии объектов.
- Когда вы хотите, чтобы клиенты могли игнорировать разницу между композициями объектов и отдельными объектами. Клиенты будут одинаково относиться ко всем объектам в составной структуре.
5. Загрузите исходный код
Это был урок по составному шаблону. Вы можете скачать исходный код здесь: CompositePattern-Project