Шаблоны проектирования | Комбинированные шаблоны и типичные приложения

Java Шаблоны проектирования контейнер SQL

Основное содержание этой статьи:

  • Знакомство с комбинационными шаблонами
  • Пример
  • Резюме шаблонов комбинаций
  • Типичное применение комбинированного режима анализа исходного кода
    • Шаблоны комбинаций в java.awt
    • Шаблоны композиции в коллекциях Java
    • Комбинированный шаблон в Mybatis SqlNode

Больше материалов можно найти в моем личном блоге:laijianfeng.org

关注【小旋锋】微信公众号

Рекомендуемое чтение

Шаблоны проектирования | Простые фабричные шаблоны и типовые приложения
Шаблоны проектирования | Шаблоны фабричных методов и типичные приложения
Шаблоны проектирования | Абстрактные фабричные шаблоны и типовые приложения
Шаблоны проектирования | Шаблоны построителей и типичные приложения
Шаблоны проектирования | Шаблоны прототипов и типовые приложения
Шаблоны проектирования | Шаблоны внешнего вида и типичные приложения
Шаблоны проектирования | Шаблоны декораторов и типичные приложения
Шаблоны проектирования | Шаблоны адаптеров и типичные приложения
Шаблоны проектирования | Легковесные шаблоны и типичные приложения

Комбинированный режим

Древовидная структура является очень распространенной структурой как в жизни, так и в разработке.Объект-контейнер (например, папка) может хранить множество различных конечных объектов или объектов-контейнеров, и различия в атрибутах между объектами-контейнерами и конечными объектами различны.Возможно, очень большой.

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

一个简化的Linux目录树

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

Составной узор: объединяет несколько объектов в древовидную структуру для представления иерархии с отношением «целое-часть». Шаблон композиции согласован в использовании одиночных объектов (т. е. объектов-листов) и составных объектов (т. е. объектов-контейнеров).

Из-за существования большого количества древовидных структур в разработке программного обеспечения шаблон композиции является часто используемым структурным шаблоном проектирования.Дизайн пакетов AWT и Swing в Java SE основан на шаблоне композиции.

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

Роль

Компонент (абстрактный компонент): это может быть интерфейс или абстрактный класс, он объявляет интерфейсы для конечных компонентов и объектов компонентов-контейнеров и может включать объявления и реализации поведения, общего для всех подклассов в этой роли. Методы доступа и управления его подкомпонентами определяются в абстрактном компоненте, например, добавление подкомпонентов, удаление подкомпонентов, получение подкомпонентов и так далее.

Лист: он представляет объект конечного узла в составной структуре, листовой узел не имеет дочерних узлов и реализует поведение, определенное в абстрактном компоненте. Для этих методов доступа к подкомпонентам и управления ими можно использовать исключения.

Композит (контейнерный компонент): он представляет объект узла-контейнера в составной структуре. Узел-контейнер содержит дочерние узлы. Дочерние узлы могут быть конечными узлами или узлами-контейнерами. Он предоставляет коллекцию для хранения дочерних узлов и реализует поведение, определенное в абстрактном компоненте. эти методы для доступа к подкомпонентам и управления ими, а в его бизнес-методах можно рекурсивно вызывать бизнес-методы его дочерних узлов.

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

Пример

Давайте реализуем простое дерево каталогов, которое имеет два типа папок и файлов.Во-первых, нам нужен абстрактный класс компонента, который объявляет методы, требуемые классом папки и классом файла.

public abstract class Component {

    public String getName() {
        throw new UnsupportedOperationException("不支持获取名称操作");
    }

    public void add(Component component) {
        throw new UnsupportedOperationException("不支持添加操作");
    }

    public void remove(Component component) {
        throw new UnsupportedOperationException("不支持删除操作");
    }

    public void print() {
        throw new UnsupportedOperationException("不支持打印操作");
    }

    public String getContent() {
        throw new UnsupportedOperationException("不支持获取内容操作");
    }
}

Реализуйте класс папки Folder, наследуйте Component и определитеList<Component>Свойство componentList типа используется для хранения файлов и подпапок в папке и реализации таких методов, как getName, add, remove, print и т. д.

public class Folder extends Component {
    private String name;
    private List<Component> componentList = new ArrayList<Component>();

    public Folder(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void add(Component component) {
        this.componentList.add(component);
    }

    @Override
    public void remove(Component component) {
        this.componentList.remove(component);
    }

    @Override
    public void print() {
        System.out.println(this.getName());
        for (Component component : this.componentList) {
            component.print();
        }
    }
}

Файловый класс File наследует родительский класс Component и реализует такие методы, как getName, print и getContent.

public class File extends Component {
    private String name;
    private String content;

    public File(String name, String content) {
        this.name = name;
        this.content = content;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void print() {
        System.out.println(this.getName());
    }

    @Override
    public String getContent() {
        return this.content;
    }
}

Давайте проверим это

public class Test {
    public static void main(String[] args) {
        Folder DSFolder = new Folder("设计模式资料");
        File note1 = new File("组合模式笔记.md", "组合模式组合多个对象形成树形结构以表示具有 \"整体—部分\" 关系的层次结构");
        File note2 = new File("工厂方法模式.md", "工厂方法模式定义一个用于创建对象的接口,让子类决定将哪一个类实例化。");
        DSFolder.add(note1);
        DSFolder.add(note2);

        Folder codeFolder = new Folder("样例代码");
        File readme = new File("README.md", "# 设计模式示例代码项目");
        Folder srcFolder = new Folder("src");
        File code1 = new File("组合模式示例.java", "这是组合模式的示例代码");

        srcFolder.add(code1);
        codeFolder.add(readme);
        codeFolder.add(srcFolder);
        DSFolder.add(codeFolder);

        DSFolder.print();
    }
}

выходной результат

设计模式资料
组合模式笔记.md
工厂方法模式.md
样例代码
README.md
src
组合模式示例.java

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

public class Folder extends Component {
    private String name;
    private List<Component> componentList = new ArrayList<Component>();
    public Integer level;

    public Folder(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void add(Component component) {
        this.componentList.add(component);
    }

    @Override
    public void remove(Component component) {
        this.componentList.remove(component);
    }

    @Override
    public void print() {
        System.out.println(this.getName());
        if (this.level == null) {
            this.level = 1;
        }
        String prefix = "";
        for (int i = 0; i < this.level; i++) {
            prefix += "\t- ";
        }
        for (Component component : this.componentList) {
            if (component instanceof Folder){
                ((Folder)component).level = this.level + 1;
            }
            System.out.print(prefix);
            component.print();
        }
        this.level = null;
    }
}

Вывод теперь имеет соответствующую иерархию

设计模式资料
	- 组合模式笔记.md
	- 工厂方法模式.md
	- 样例代码
	- 	- README.md
	- 	- src
	- 	- 	- 组合模式示例.java

Мы можем нарисовать диаграмму классов между ними

示例.组合模式类图

родительский класс здесьComponentэто абстрактный класс компонентов,Folderкласс — это класс компонента контейнера,FileЭто конечный класс компонентов Папка и файл наследуют компонент, а папка и компонент имеют отношения агрегации.

Прозрачность и безопасность

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

Прозрачный комбинированный режим

В режиме прозрачной композиции все методы, используемые для управления объектами-членами, объявляются в роли абстрактного компонента, как в примереComponentобъявленadd,removeметод, преимущество этого заключается в том, что все классы компонентов имеют одинаковый интерфейс. Прозрачный режим композиции также является стандартной формой режима композиции.

Недостаток режима прозрачного комбинирования в том, что он недостаточно безопасен, так как листовой объект и объект-контейнер существенно различаются, и листовой объект не может иметь следующий уровень объектов, то есть невозможно содержать объекты-члены, поэтому он обеспечиваетadd(),remove()и т. д. бессмысленны, что не приведет к ошибкам во время компиляции, но может привести к ошибкам во время выполнения, если вы вызовете эти методы (если не предоставлен соответствующий код обработки ошибок)

Безопасный комбинированный режим

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

安全组合模式模式图

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

в практическом примененииjava.awtиswingКомбинированный режим является безопасным комбинированным режимом.

Резюме шаблонов комбинаций

комбинированный режимглавное преимуществоследующее:

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

комбинированный режимглавный недостатокследующее:

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

Применимая сцена:

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

Типичное применение комбинированного режима анализа исходного кода

Шаблоны комбинаций в java.awt

Существует два типа графического интерфейса Java:

  • AWT (Abstract Window Toolkit): набор инструментов для работы с абстрактными окнами — это первое поколение компонентов графического пользовательского интерфейса Java. Рисование зависит от базовой операционной системы. Базовая библиотека AWT обрабатывает элементы пользовательского интерфейса, делегируя создание и поведение этих элементов собственным инструментам графического интерфейса пользователя на каждой целевой платформе (Windows, Unix, Macintosh и т. д.).

  • Swing, независимо от низкоуровневых деталей, является легковесным компонентом. Сейчас он в основном разработан на основе Swing.

Давайте рассмотрим простой пример AWT:

Примечание. Для нормального отображения китайского языка он должен быть в IDEA.Edit Configurations -> VM Optionsустановить параметры в-Dfile.encoding=GB18030

import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

public class MyFrame extends Frame {

    public MyFrame(String title) {
        super(title);
    }

    public static void main(String[] args) {
        MyFrame frame = new MyFrame("这是一个 Frame");

        // 定义三个构件,添加到Frame中去
        Button button = new Button("按钮 A");
        Label label = new Label("这是一个 AWT Label!");
        TextField textField = new TextField("这是一个 AWT TextField!");

        frame.add(button, BorderLayout.EAST);
        frame.add(label, BorderLayout.SOUTH);
        frame.add(textField, BorderLayout.NORTH);

        // 定义一个 Panel,在Panel中添加三个构件,然后再把Panel添加到Frame中去
        Panel panel = new Panel();
        panel.setBackground(Color.pink);

        Label lable1 = new Label("用户名");
        TextField textField1 = new TextField("请输入用户名:", 20);
        Button button1 = new Button("确定");
        panel.add(lable1);
        panel.add(textField1);
        panel.add(button1);

        frame.add(panel, BorderLayout.CENTER);

        // 设置Frame的属性
        frame.setSize(500, 300);
        frame.setBackground(Color.orange);
        // 设置点击关闭事件
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        frame.setVisible(true);
    }
}

После запуска форма выглядит следующим образом

示例.AWT绘制窗体

Мы добавили три разных виджета в контейнер Frame.Button,Label,TextField, также добавивPanelконтейнер,Panelдобавлено в контейнерButton,Label,TextFieldТри компонента, почему именно контейнерыFrameиPanelКак насчет добавления виджетов и контейнеров разных типов?

Давайте сначала посмотрим на диаграмму классов компонента AWT.

AWT Component类图

Компоненты GUI можно разделить на два типа в зависимости от их функций: базовые компоненты и компоненты-контейнеры.

  • Базовые компоненты, также известные как компоненты, представляют собой элементы графического интерфейса, такие как кнопки и текстовые поля.
  • Контейнер — это особый тип компонента, который может содержать другие компоненты, такие как окна, диалоговые окна и т. д. Все классы контейнеровjava.awt.Containerпрямые или косвенные подклассы

Родительский класс контейнераContainerЧасть кода выглядит следующим образом

public class Container extends Component {
    /**
     * The components in this container.
     * @see #add
     * @see #getComponents
     */
    private java.util.List<Component> component = new ArrayList<>();
    
    public Component add(Component comp) {
        addImpl(comp, null, -1);
        return comp;
    }
    // 省略...
}

Родительский класс контейнераContainerВнутренне определяет коллекцию для храненияComponentобъекты, а компоненты контейнераContainerи основные компоненты, такие какButton,Label,TextFieldподождите всеComponentПодкласс , так что вы можете ясно видеть, что здесь применяется шаблон композиции

ComponentКласс инкапсулирует общие методы и свойства компонента, такие как объект компонента, размер, положение отображения, цвет переднего плана и фона, граница, видимость и т. д. графики, поэтому многие классы компонентов также наследуютComponentМетоды-члены и переменные-члены класса, соответствующие методы-члены включают:

&emsp;&emsp;&emsp;getComponentAt(int x, int y)
&emsp;&emsp;&emsp;getFont()
&emsp;&emsp;&emsp;getForeground()
&emsp;&emsp;&emsp;getName()
&emsp;&emsp;&emsp;getSize()
&emsp;&emsp;&emsp;paint(Graphics g)
&emsp;&emsp;&emsp;repaint()
&emsp;&emsp;&emsp;update()
&emsp;&emsp;&emsp;setVisible(boolean b)
&emsp;&emsp;&emsp;setSize(Dimension d)
&emsp;&emsp;&emsp;setName(String name)

Шаблоны композиции в коллекциях Java

HashMapпоставкаputAllметод, вы можете преобразовать другойMapОбъект помещается в собственное пространство для хранения.Если есть такое же значение ключа, он перезапишет значение значения, соответствующее предыдущему значению ключа.

public class Test {
    public static void main(String[] args) {
        Map<String, Integer> map1 = new HashMap<String, Integer>();
        map1.put("aa", 1);
        map1.put("bb", 2);
        map1.put("cc", 3);
        System.out.println("map1: " + map1);

        Map<String, Integer> map2 = new LinkedMap();
        map2.put("cc", 4);
        map2.put("dd", 5);
        System.out.println("map2: " + map2);

        map1.putAll(map2);
        System.out.println("map1.putAll(map2): " + map1);
    }
}

выходной результат

map1: {aa=1, bb=2, cc=3}
map2: {cc=4, dd=5}
map1.putAll(map2): {aa=1, bb=2, cc=4, dd=5}

ПроверятьputAllисходный код

    public void putAll(Map<? extends K, ? extends V> m) {
        putMapEntries(m, true);
    }

putAllПолученный параметр является родительским классомMapтипа, такHashMapэто контейнерный класс,MapПодкласс является конечным классом, конечно, еслиMapДругие подклассыputAllметод, то они являются классами-контейнерами и конечными классами.

Так же,ArrayListсерединаaddAll(Collection<? extends E> c)Этот метод также является применением комбинированного шаблона, который здесь не обсуждается.

Комбинированный шаблон в Mybatis SqlNode

Одной из мощных функций MyBatis является динамический SQL, которыйif, choose, when, otherwise, trim, where, set, foreachТеги, которые можно комбинировать в очень гибкие операторы SQL, повышая эффективность разработчика.

Вот несколько официальных примеров:

Динамический SQL -- ЕСЛИ

<select id="findActiveBlogLike"  resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’ 
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

Динамический SQL - выберите, когда, иначе

<select id="findActiveBlogLike"  resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

Динамический SQL -- где

<select id="findActiveBlogLike"  resultType="Blog">
  SELECT * FROM BLOG 
  <where> 
    <if test="state != null">
         state = #{state}
    </if> 
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>

Динамический SQL -- foreach

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT * FROM POST P WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

Когда Mybatis обрабатывает динамические узлы SQL, он применяет комбинированный режим проектирования.Mybatis анализирует узлы динамического SQL и текстовые узлы, определенные в файле конфигурации сопоставления, в соответствующие реализации SqlNode и формирует древовидную структуру.

SQLNodeДиаграмма классов показана ниже

Mybatis SqlNode 类图

нужно знать сначалаDynamicContextРоль класса: в основном используется для записи фрагментов операторов SQL, сгенерированных после разбора динамических операторов SQL, его можно рассматривать как контейнер для записи результатов разбора динамических операторов SQL.

Абстрактный компонентSqlNodeИнтерфейс, исходный код выглядит следующим образом

public interface SqlNode {
  boolean apply(DynamicContext context);
}

applyдаSQLNodeЕдинственный метод, определенный в интерфейсе, этот метод анализирует динамический узел SQL, записанный SQLNode, в соответствии с фактическими параметрами, переданными пользователем, и вызываетDynamicContext.appendSql()метод добавляет проанализированный фрагмент SQL кDynamicContext.sqlBuilderсохраняется в узле SQL, когда всеSqlNodeПосле разбора мы можем получитьDynamicContextПолучите динамически созданный и полный оператор SQL в

Тогда см.MixedSqlNodeисходный код класса

public class MixedSqlNode implements SqlNode {
  private List<SqlNode> contents;

  public MixedSqlNode(List<SqlNode> contents) {
    this.contents = contents;
  }

  @Override
  public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }
}

MixedSqlNodeподдерживалList<SqlNode>список типов для храненияSqlNodeобъект,applyметод переданfor循环перебирает содержимое и вызывает объектapplyметод, здесь тот же, что и в нашем примереFolderв классеprintМетод очень похож, очевидноMixedSqlNodeиграет роль компонента-контейнера

Для функциональности других подклассов SqlNode краткое описание выглядит следующим образом:

  • TextSqlNode: значит включает${}Узел динамического SQL-заполнителя, метод применения которого будет использоватьGenericTokenParserРазобрать${}заполнитель и напрямую замените его фактическим значением параметра, заданным пользователем.
  • IfSqlNode: соответствует узлу динамического SQL<If>узел, чей метод применения сначала проходит черезExpressionEvaluator.evaluateBoolean()Метод определяет, истинно ли его тестовое выражение, а затем решает, следует ли выполнять метод apply() своих дочерних узлов в соответствии с результатом тестового выражения.
  • TrimSqlNode: соответствующий префикс или суффикс будет добавлен или удален в соответствии с результатом синтаксического анализа дочернего узла.
  • WhereSqlNodeиSetSqlNodeунаследовалиTrimSqlNode
  • ForeachSqlNode:соответствовать<foreach>теги, перебирает коллекцию
  • в динамическом SQL<choose>,<when>,<otherwise>анализируется вChooseSqlNode,IfSqlNode,MixedSqlNode

Подводить итоги,SqlNodeИнтерфейс имеет несколько классов реализации, каждый класс реализации соответствует динамическому узлу SQL, гдеSqlNodeиграть роль абстрактного компонента,MixedSqlNodeИграют роль компонента контейнера, другие обычно являются листовыми компонентами.

Ссылаться на:
Лю Вэй: Шаблоны проектирования Java Edition
MOOC Java Design Patterns Интенсивный метод отладки + анализ памяти
Основы Java AWT и управление компоновкой
HashMap.putAll() [исходный код java серии «Один пояс, один путь»]
Сюй Цзюньмин: Внутри Mybatis Technology 3.2 SqlNode&SqlSource
Документация Mybatis 3.4.7: Динамический SQL