Шаблоны проектирования также могут быть такими простыми

Java Шаблоны проектирования API продукт

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

Шаблоны проектирования — это высокоуровневые абстрактные сводки различных кодов, написанных в реальной работе.Gang of Four (GoF), они классифицируют шаблоны проектирования на 23 классических шаблона, которые можно разделить на три категории в зависимости от их использования, а именно шаблоны создания, структурные шаблоны и поведенческие шаблоны.

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

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

содержание

творческий режим

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

Простой заводской шаблон

Так же просто, как и название, очень просто, переходим непосредственно к коду:

public class FoodFactory {
    public static Food makeFood(String name) {
        if (name.equals("noodle")) {
            Food noodle = new LanZhouNoodle();
            noodle.addSpicy("more");
            return noodle;
        } else if (name.equals("chicken")) {
            Food chicken = new HuangMengChicken();
            chicken.addCondiment("potato");
            return chicken;
        } else {
            return null;
        }
    }
}

Среди них LanZhouNoodle и HuangMengChicken, унаследованные от Food.

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

мы подчеркиваемединственная ответственностьВ принципе, класс предоставляет только одну функцию, а функция FoodFactory заключается в том, чтобы отвечать только за производство всех видов еды.

заводской узор

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

public interface FoodFactory {
    Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory {
    @Override
    public Food makeFood(String name) {
        if (name.equals("A")) {
            return new ChineseFoodA();
        } else if (name.equals("B")) {
            return new ChineseFoodB();
        } else {
            return null;
        }
    }
}
public class AmericanFoodFactory implements FoodFactory {
    @Override
    public Food makeFood(String name) {
        if (name.equals("A")) {
            return new AmericanFoodA();
        } else if (name.equals("B")) {
            return new AmericanFoodB();
        } else {
            return null;
        }
    }
}

Среди них китайская еда A, китайская еда B, американская еда A, американская еда B — все они получены из еды.

Звонок клиента:

public class APP {
    public static void main(String[] args) {
        // 先选择一个具体的工厂
        FoodFactory factory = new ChineseFoodFactory();
        // 由第一步的工厂产生具体的对象,不同的工厂造出不一样的对象
        Food food = factory.makeFood("A");
    }
}

Хотя все они вызывают makeFood("A") для производства продуктов питания типа A, разные фабрики производят совершенно разные продукты.

На первом шаге нам нужно выбрать соответствующую фабрику, а затем второй шаг в основном такой же, как и простая фабрика.

Суть в том, что нам нужно выбрать нужную нам фабрику на первом шаге. Например, у нас есть интерфейс LogFactory, а классы реализации — FileLogFactory и KafkaLogFactory, которые соответствуют записи логов в файлы и Kafka соответственно.Очевидно, что на первом шаге нашего клиента нужно решить, создавать ли экземпляр FileLogFactory или KafkaLogFactory, который будет быть решено позже. всех операций.

Хоть это и просто, я тоже нарисовал все компоненты на одной картинке, чтобы читателю было понятно:

factory-1

Абстрактный заводской узор

когда дело доходит доСемейство продуктовКогда необходимо ввести абстрактный фабричный шаблон.

Классический пример — сборка компьютера. Давайте не будем вводить шаблон абстрактной фабрики и посмотрим, как его реализовать.

Поскольку компьютер состоит из многих компонентов, мы абстрагируем ЦП и материнскую плату, затем ЦП создается фабрикой CPUFactory, а материнская плата производится фабрикой MainBoardFactory, а затем мы объединяем ЦП и материнскую плату, как показано ниже:

factory-1

Вызов клиента в это время выглядит следующим образом:

// 得到 Intel 的 CPU
CPUFactory cpuFactory = new IntelCPUFactory();
CPU cpu = intelCPUFactory.makeCPU();
// 得到 AMD 的主板
MainBoardFactory mainBoardFactory = new AmdMainBoardFactory();
MainBoard mainBoard = mainBoardFactory.make();
// 组装 CPU 和主板
Computer computer = new Computer(cpu, mainBoard);

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

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

Вот что мы должны сказатьСемейство продуктов, который представляет собой набор аксессуаров, из которых состоит изделие:

abstract-factory-2

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

abstract-factory-3

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

public static void main(String[] args) {
    // 第一步就要选定一个“大厂”
    ComputerFactory cf = new AmdFactory();
    // 从这个大厂造 CPU
    CPU cpu = cf.makeCPU();
    // 从这个大厂造主板
    MainBoard board = cf.makeMainBoard();
  	// 从这个大厂造硬盘
  	HardDisk hardDisk = cf.makeHardDisk();
  
    // 将同一个厂子出来的 CPU、主板、硬盘组装在一起
    Computer result = new Computer(cpu, board, hardDisk);
}

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

одноэлементный шаблон

Одноэлементный шаблон используется чаще всего и чаще всего ошибочен.

Режим голодного человека самый простой:

public class Singleton {
    // 首先,将 new Singleton() 堵死
    private Singleton() {};
    // 创建私有静态实例,意味着这个类第一次使用的时候就会进行创建
    private static Singleton instance = new Singleton();
    
    public static Singleton getInstance() {
        return instance;
    }
    // 瞎写一个静态方法。这里想说的是,如果我们只是要调用 Singleton.getDate(...),
    // 本来是不想要生成 Singleton 实例的,不过没办法,已经生成了
    public static Date getDate(String mode) {return new Date();}
}

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

Полный режим человека наиболее подвержен ошибкам:

public class Singleton {
    // 首先,也是先堵死 new Singleton() 这条路
    private Singleton() {}
    // 和饿汉模式相比,这边不需要先实例化出来,注意这里的 volatile,它是必须的
    private static volatile Singleton instance = null;
    public static Singleton getInstance() {
        if (instance == null) {
            // 加锁
            synchronized (Singleton.class) {
                // 这一次判断也是必须的,不然会有并发问题
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

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

Многие люди не знают, как это написать, поэтому они просто добавляют synchronized в сигнатуру метода getInstance(), что не так уж много говорит.

Вложенные классы самые классические, мы будем использовать их в дальнейшем:

public class Singleton3 {
    private Singleton3() {}
    // 主要是使用了 嵌套类可以访问外部类的静态属性和静态方法 的特性
    private static class Holder {
        private static Singleton3 instance = new Singleton3();
    }
    public static Singleton3 getInstance() {
        return Holder.instance;
    }
}

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

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

режим строителя

Часто встречающиеся классы XxxBuilder обычно являются продуктом шаблона компоновщика. На самом деле существует много вариантов шаблона строителя, но для клиента мы обычно используем шаблон:

Food food = new FoodBuilder().a().b().c().build();
Food food = Food.builder().a().b().c().build();

Хитрость заключается в том, чтобы сначала создать новый Builder, затем вызвать кучу методов в цепочке и, наконец, снова вызвать метод build(), и нужный нам объект уже есть.

Приходите в достойный режим строителя:

class User {
    // 下面是“一堆”的属性
    private String name;
    private String password;
    private String nickName;
    private int age;
    // 构造方法私有化,不然客户端就会直接调用构造方法了
    private User(String name, String password, String nickName, int age) {
        this.name = name;
        this.password = password;
        this.nickName = nickName;
        this.age = age;
    }
	// 静态方法,用于生成一个 Builder,这个不一定要有,不过写这个方法是一个很好的习惯,
    // 有些代码要求别人写 new User.UserBuilder().a()...build() 看上去就没那么好
    public static UserBuilder builder() {
        return new UserBuilder();
    }
   
    public static class UserBuilder {
        // 下面是和 User 一模一样的一堆属性
        private String  name;
        private String password;
        private String nickName;
        private int age;
        private UserBuilder() {
        }
        // 链式调用设置各个属性值,返回 this,即 UserBuilder
        public UserBuilder name(String name) {
            this.name = name;
            return this;
        }
        public UserBuilder password(String password) {
            this.password = password;
            return this;
        }
        public UserBuilder nickName(String nickName) {
            this.nickName = nickName;
            return this;
        }
        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }
        // build() 方法负责将 UserBuilder 中设置好的属性“复制”到 User 中。
        // 当然,可以在 “复制” 之前做点检验
        public User build() {
            if (name == null || password == null) {
                throw new RuntimeException("用户名和密码必填");
            }
            if (age <= 0 || age >= 150) {
                throw new RuntimeException("年龄不合法");
            }
            // 还可以做赋予”默认值“的功能
          	if (nickName == null) {
                nickName = name;
            }
            return new User(name, password, nickName, age);
        }
    }
}

Суть такова: сначала установить все свойства в Builder, а затем в методе build() установить эти свойствакопироватьк фактическому сгенерированному объекту.

Взгляните на звонок клиента:

public class APP {
    public static void main(String[] args) {
        User d = User.builder()
                .name("foo")
                .password("pAss12345")
                .age(25)
                .build();
    }
}

Честно говоря, режим строителяцепьСпособ написания очень привлекательный, но после написания большого количества "бесполезного" кода компоновщика я чувствую, что этот режим бесполезен. Однако, когда атрибутов много, и некоторые из них обязательны, а некоторые необязательны, этот шаблон делает код намного чище. мы можемСтроитель конструкторПринуждение вызывающей стороны к предоставлению обязательных полей и проверка каждого параметра в методе build() более элегантны, чем проверка в конструкторе пользователя.

В качестве отступления читателю настоятельно рекомендуется использовать ломбок.После использования ломбока большая часть кода выше станет следующим:

@Builder
class User {
    private String  name;
    private String password;
    private String nickName;
    private int age;
}

Ну, а ты можешь заняться чем-нибудь другим в сэкономленное время?

Конечно, если вам нужен именно цепочный метод записи и не нужен режим билдера, есть очень простой способ: геттер-метод пользователя остается неизменным, а все сеттер-методы делают егоreturn thisВот так, тогда вы можете назвать это так:

User user = new User().setName("").setPassword("").setAge(20);

режим прототипа

Это последний шаблон проектирования, о котором я собираюсь рассказать.

Шаблон прототипа прост: есть прототиппример, на основе этого экземпляра-прототипа создается новый экземпляр, то есть "клон".

В классе Object есть метод clone(), который используется для создания нового объекта.Конечно, если мы хотим вызвать этот метод, java требует, чтобы наш класс сначалаРеализовать интерфейс Cloneable, этот интерфейс не определяет никаких методов, но если вы этого не сделаете, во время clone() будет сгенерировано исключение CloneNotSupportedException.

protected native Object clone() throws CloneNotSupportedException;

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

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

Резюме творческих шаблонов

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

Простой фабричный режим является самым простым; фабричный режим добавляет измерение выбора фабрик на основе простого фабричного режима, и на первом этапе необходимо выбрать соответствующую фабрику; абстрактный фабричный режим имеет концепцию семейства продуктов. , если есть проблема совместимости между каждым продуктом, используйте шаблон абстрактной фабрики. Одноэлементный режим обсуждаться не будет.Для обеспечения глобального использования одного и того же объекта, с одной стороны, из соображений безопасности, а с другой - для экономии ресурсов, режим Builder специально разработан для иметь дело с типом со многими атрибутами, чтобы сделать код более красивым; режим прототипа реже всего используется для понимания знаний, связанных с методом clone() в классе Object.

Структурный образец

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

прокси-режим

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

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

пониматьиграет рольЭто слово, эта модель на самом деле проста.

public interface FoodService {
    Food makeChicken();
    Food makeNoodle();
}
public class FoodServiceImpl implements FoodService {
    public Food makeChicken() {
      	Food f = new Chicken()
        f.setChicken("1kg");
      	f.setSpicy("1g");
      	f.setSalt("3g");
        return f;
    }
    public Food makeNoodle() {
        Food f = new Noodle();
        f.setNoodle("500g");
        f.setSalt("5g");
        return f;
    }
}
// 代理要表现得“就像是”真实实现类,所以需要实现 FoodService
public class FoodServiceProxy implements FoodService {
  
    // 内部一定要有一个真实的实现类,当然也可以通过构造方法注入
    private FoodService foodService = new FoodServiceImpl();
    
    public Food makeChicken() {
        System.out.println("我们马上要开始制作鸡肉了");
      
        // 如果我们定义这句为核心代码的话,那么,核心代码是真实实现类做的,
        // 代理只是在核心代码前后做些“无足轻重”的事情
        Food chicken = foodService.makeChicken();
      
        System.out.println("鸡肉制作完成啦,加点胡椒粉"); // 增强
      	chicken.addCondiment("pepper");
      
        return fruit;
    }
    public Food makeNoodle() {
        System.out.println("准备制作拉面~");
        Food noodle = foodService.makeNoodle();
        System.out.println("制作完成啦")
        return fruit;
    }
}

Клиентский вызов, обратите внимание, что мы используем прокси для создания экземпляра интерфейса:

// 这里用代理类来实例化
FruitService fruitService = new FruitServiceProxy();
fruitService.makeChicken();

proxy

Мы обнаружили, что нет, режим прокси просто сделать«Обертка метода»или сделать«Улучшение метода». В аспектно-ориентированном программировании забудьте об этом, не рекламируйте этот термин В АОП это фактически процесс динамического прокси. Например, в Spring мы сами не определяем прокси-классы, но Spring поможет нам определить прокси динамически, а затем динамически добавить логику кода, которую мы определили в @Before, @After, @Around, к прокси.

Когда дело доходит до динамического прокси, его можно расширить, чтобы сказать... В Spring существует два типа реализации динамического прокси: один из них заключается в том, что если наш класс определяет интерфейсы, такие как интерфейс UserService и реализация UserServiceImpl, то с использованием динамического прокси JDK, заинтересованные читатели могут взглянуть на исходный код класса java.lang.reflect.Proxy; во-вторых, мы не определяем интерфейс сами, Spring будет использовать CGLIB для динамического прокси, это пакет jar, и производительность не плохая.

режим адаптера

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

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

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

Режим адаптера по умолчанию

Для начала рассмотрим простейший шаблон адаптераРежим адаптера по умолчанию (адаптер по умолчанию)как это.

В качестве примера мы используем FileAlterationListener в пакете Appache commons-io. Этот интерфейс определяет множество методов для мониторинга файлов или папок. После выполнения соответствующей операции будет запущен соответствующий метод.

public interface FileAlterationListener {
    void onStart(final FileAlterationObserver observer);
    void onDirectoryCreate(final File directory);
    void onDirectoryChange(final File directory);
    void onDirectoryDelete(final File directory);
    void onFileCreate(final File file);
    void onFileChange(final File file);
    void onFileDelete(final File file);
    void onStop(final FileAlterationObserver observer);
}

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

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

public class FileAlterationListenerAdaptor implements FileAlterationListener {
    public void onStart(final FileAlterationObserver observer) {
    }
    public void onDirectoryCreate(final File directory) {
    }
    public void onDirectoryChange(final File directory) {
    }
    public void onDirectoryDelete(final File directory) {
    }
    public void onFileCreate(final File file) {
    }
    public void onFileChange(final File file) {
    }
    public void onFileDelete(final File file) {
    }
    public void onStop(final FileAlterationObserver observer) {
    }
}

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

public class FileMonitor extends FileAlterationListenerAdaptor {
    public void onFileCreate(final File file) {
        // 文件创建
        doSomething();
    }
    public void onFileDelete(final File file) {
        // 文件删除
        doSomething();
    }
}

Конечно, вышеописанное — лишь один из режимов адаптера, к тому же самый простой, так что много говорить не приходится. Далее представим"православный"Режим адаптера.

Шаблон адаптера объекта

Давайте рассмотрим пример из «Шаблоны проектирования Head First», который я немного модифицировал, чтобы увидеть, как адаптировать курицу к утке, чтобы курицу также можно было использовать как утку. Потому что теперь убираем этот интерфейс, у нас нет подходящего класса реализации, поэтому нам нужен адаптер.

public interface Duck {
    public void quack(); // 鸭的呱呱叫
  	public void fly(); // 飞
}
public interface Cock {
    public void gobble(); // 鸡的咕咕叫
  	public void fly(); // 飞
}
public class WildCock implements Cock {
    public void gobble() {
        System.out.println("咕咕叫");
    }
  	public void fly() {
        System.out.println("鸡也会飞哦");
    }
}

Интерфейс утки имеет два метода, fly() и Square().Если цыпленок-петух хочет притвориться уткой, метод fly() готов, но цыпленок не каркает, как утка, так что кряканья нет. () метод. В это время нужно адаптировать:

// 毫无疑问,首先,这个适配器肯定需要 implements Duck,这样才能当做鸭来用
public class CockAdapter implements Duck {
  
    Cock cock;
    // 构造方法中需要一个鸡的实例,此类就是将这只鸡适配成鸭来用
  	public CockAdapter(Cock cock) {
        this.cock = cock;
    }
  
    // 实现鸭的呱呱叫方法
  	@Override
  	public void quack() {
        // 内部其实是一只鸡的咕咕叫
        cock.gobble();
    }
  
  	@Override
  	public void fly() {
        cock.fly();
    }
}

Вызов клиента очень прост:

public static void main(String[] args) {
    // 有一只野鸡
  	Cock wildCock = new WildCock();
  	// 成功将野鸡适配成鸭
  	Duck duck = new CockAdapter(wildCock);
  	...
}

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

Кратко проиллюстрируем диаграммой:

adapter-1

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

шаблон адаптера класса

Без лишних слов, перейдем непосредственно к картинке:

adapter-1

Глядя на эту картинку, всем должно быть легко понять, через метод наследования адаптер автоматически получает большинство нужных ему методов. В настоящее время клиент проще в использовании и напрямуюTarget t = new SomeAdapter();Вот и все.

Сводка шаблонов адаптеров

  1. Сходства и различия между адаптацией класса и адаптацией объекта

    Один использует наследование, другой использует композицию;

    Адаптация класса относится к статической реализации, а адаптация объекта относится к динамической реализации композиции, адаптация объекта требует инстанцирования еще одного объекта.

    В целом больше используется предметная адаптация.

  2. Сходства и различия между режимом адаптера и режимом прокси

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

    adapter-5

режим моста

Чтобы понять шаблон моста, нужно понять абстракцию кода и развязку.

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

public interface DrawAPI {
   public void draw(int radius, int x, int y);
}

Затем ряд классов реализации:

public class RedPen implements DrawAPI {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用红色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class GreenPen implements DrawAPI {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用绿色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class BluePen implements DrawAPI {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用蓝色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}

Определите абстрактный класс, все классы реализации этого класса должны использовать DrawAPI:

public abstract class Shape {
   protected DrawAPI drawAPI;
   
   protected Shape(DrawAPI drawAPI){
      this.drawAPI = drawAPI;
   }
   public abstract void draw();	
}

Определите подкласс абстрактного класса:

// 圆形
public class Circle extends Shape {
   private int radius;
   public Circle(int radius, DrawAPI drawAPI) {
      super(drawAPI);
      this.radius = radius;
   }
   public void draw() {
      drawAPI.draw(radius, 0, 0);
   }
}
// 长方形
public class Rectangle extends Shape {
    private int x;
  	private int y;
  
  	public Rectangle(int x, int y, DrawAPI drawAPI) {
        super(drawAPI);
      	this.x = x;
      	this.y = y;
    }
  	public void draw() {
      drawAPI.draw(0, x, y);
   }
}

Наконец, давайте посмотрим на демонстрацию клиента:

public static void main(String[] args) {
    Shape greenCircle = new Circle(10, new GreenPen());
  	Shape redRectangle = new Rectangle(4, 8, new RedPen());
  
  	greenCircle.draw();
  	redRectangle.draw();
}

Может быть не очень понятно для всех, чтобы увидеть вышеописанное пошагово, я объединил все в одну картинку:

bridge-1

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

Этот раздел ссылаетсяздесьпример и модифицировал его.

орнамент

Нелегко четко объяснить узор украшения. Возможно, читатели знают, что некоторые классы в Java IO являются типичным применением режима оформления, но читатели могут не знать взаимосвязи между ними, возможно, они забывают после прочтения.Я надеюсь, что после прочтения этого раздела читатели смогут глубже понять его.

Прежде всего, давайте посмотрим на простую диаграмму.Глядя на эту диаграмму, вы можете понять иерархию:

decorator-1

Поговорим о начальной точке узора украшения.Как видно из рисунка, интерфейсComponentНа самом деле уже естьConcreteComponentAиConcreteComponentBдва класса реализации, но если мы хотимусилитьДля этих двух классов реализации мы можем использовать режим украшения и специальные декораторы дляУкраситьРеализация классов для расширенных целей.

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

Сначала разберемся с некоторыми простыми понятиями, из рисунка видно, что все бетонные декораторы ConcreteDecoratorОба могут использоваться как компоненты, потому что они реализуют все интерфейсы в компоненте. Они и Component реализуют класс ConcreteComponent.Разница в том, что они просто декораторы, начинаяУкраситьРоль, то есть пусть они и выглядят устрашающе, но они только в конкретной реализацииДобавлен слой кожи для украшенияВот и все.

Обратите внимание на Component и Decorator, смешанные в разных существительных в этом отрывке, не путайте их.

Давайте рассмотрим пример, сначала уточним режим оформления, а затем представим применение режима оформления в java io.

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

В этом примере черный чай, зеленый чай и кофе являются основными напитками, а другие, такие как кумкват, лимон, манго, жемчуг, кокос, карамель и т. д., являются декоративными. Конечно, в процессе разработки мы действительно можем развивать эти классы как магазин: LemonBlackTea, LemonGreenTea, MangoBlackTea, MangoLemonGreenTea... Однако вскоре мы обнаружили, что это определенно невозможно, что заставит нас объединить все возможные, и что если гостю нужно удвоить количество лимона в черном чае? Как насчет трех порций лимонов? На случай, если извращенец попросит четыре лимона, эта практика заключается в том, чтобы найти сверхурочное время для себя.

Не говорите глупостей, давайте кодировать.

Сначала определите абстрактный базовый класс напитка:

public abstract class Beverage {
  	// 返回描述
  	public abstract String getDescription();
  	// 返回价格
  	public abstract double cost();
}

Затем есть три основных класса реализации напитков: черный чай, зеленый чай и кофе:

public class BlackTea extends Beverage {
  	public String getDescription() {
        return "红茶";
    }
  	public double cost() {
        return 10;
    }
}
public class GreenTea extends Beverage {
    public String getDescription() {
        return "绿茶";
    }
  	public double cost() {
        return 11;
    }
}
...// 咖啡省略

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

// 调料
public abstract class Condiment extends Beverage {
    
}

Затем мы определяем конкретные приправы, такие как лимон и манго.Они принадлежат декораторам.Нет сомнения, что эти приправы должны наследовать класс Condiment:

public class Lemon extends Condiment {
    private Beverage bevarage;
  	// 这里很关键,需要传入具体的饮料,如需要传入没有被装饰的红茶或绿茶,
  	// 当然也可以传入已经装饰好的芒果绿茶,这样可以做芒果柠檬绿茶
  	public Lemon(Beverage bevarage) {
        this.bevarage = bevarage;
    }
  	public String getDescription() {
        // 装饰
        return bevarage.getDescription() + ", 加柠檬";
    }
  	public double cost() {
      	// 装饰
		return beverage.cost() + 2; // 加柠檬需要 2 元
    }
}
public class Mango extends Condiment {
    private Beverage bevarage;
  	public Mango(Beverage bevarage) {
        this.bevarage = bevarage;
    }
  	public String getDescription() {
        return bevarage.getDescription() + ", 加芒果";
    }
  	public double cost() {
		return beverage.cost() + 3; // 加芒果需要 3 元
    }
}
...// 给每一种调料都加一个类

Посмотрите на вызов клиента:

public static void main(String[] args) {
  	// 首先,我们需要一个基础饮料,红茶、绿茶或咖啡
    Beverage beverage = new GreenTea();
  	// 开始装饰
  	beverage = new Lemon(beverage); // 先加一份柠檬
  	beverage = new Mongo(beverage); // 再加一份芒果
  	
  	System.out.println(beverage.getDescription() + " 价格:¥" + beverage.cost());
  	//"绿茶, 加柠檬, 加芒果 价格:¥16"
}

Если нам нужен черный чай Mango Pearl Double Lemon Black Tea:

Beverage beverage = new Mongo(new Pearl(new Lemon(new Lemon(new BlackTea()))));

Это сильно извращено?

Возможно, будет понятнее взглянуть на изображение ниже:

decorator-2

К этому моменту вы уже должны знать режим украшения.

Далее поговорим о шаблоне оформления в java IO. Взгляните на некоторые классы, производные от InputStream, на следующем рисунке:

decorator-3

Мы знаем, что InputStream представляет поток ввода, а конкретными источниками ввода могут быть файлы (FileInputStream), конвейеры (PipedInputStream), массивы (ByteArrayInputStream) и т. д. Это похоже на черный чай и зеленый чай в предыдущем примере чая с молоком, которые относятся к основному входному потоку.

FilterInputStream наследует ключевые узлы режима оформления, а его класс реализации представляет собой набор декораторов. Например, BufferedInputStream представляет оформление с буферизацией, благодаря чему входной поток имеет функцию буферизации. LineNumberInputStream представляет оформление с номерами строк. можно получить номер строки, оформление DataInputStream позволяет нам преобразовать входной поток в значение базового типа в java.

Конечно, в java IO, если мы используем декораторы, это не подходит для интерфейсно-ориентированного программирования, такого как:

InputStream inputStream = new LineNumberInputStream(new BufferedInputStream(new FileInputStream("")));

В результате в InputStream по-прежнему нет функции чтения номеров строк, потому что метод чтения номеров строк определен в классе LineNumberInputStream.

Мы должны использовать это так:

DataInputStream is = new DataInputStream(
  							new BufferedInputStream(
                              	new FileInputStream("")));

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

узор фасада

Шаблон фасада (также называемый шаблоном внешнего вида, шаблоном фасада) используется во многих исходных кодах, например, slf4j можно понимать как применение шаблона фасада. Это простой шаблон проектирования, давайте сразу перейдем к коду.

Во-первых, мы определяем интерфейс:

public interface Shape {
   void draw();
}

Определите несколько классов реализации:

public class Circle implements Shape {
   @Override
   public void draw() {
      System.out.println("Circle::draw()");
   }
}
public class Rectangle implements Shape {
   @Override
   public void draw() {
      System.out.println("Rectangle::draw()");
   }
}

Звонок клиента:

public static void main(String[] args) {
    // 画一个圆形
  	Shape circle = new Circle();
  	circle.draw();
  
  	// 画一个长方形
  	Shape rectangle = new Rectangle();
  	rectangle.draw();
}

Выше приведен код, который мы часто пишем.Если нам нужно нарисовать круг, нам нужно сначала создать экземпляр круга.Чтобы нарисовать прямоугольник, нам нужно сначала создать экземпляр прямоугольника, а затем вызвать соответствующий метод draw().

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

Сначала мы определяем фасад:

public class ShapeMaker {
   private Shape circle;
   private Shape rectangle;
   private Shape square;
   public ShapeMaker() {
      circle = new Circle();
      rectangle = new Rectangle();
      square = new Square();
   }
  /**
   * 下面定义一堆方法,具体应该调用什么方法,由这个门面来决定
   */
  
   public void drawCircle(){
      circle.draw();
   }
   public void drawRectangle(){
      rectangle.draw();
   }
   public void drawSquare(){
      square.draw();
   }
}

Посмотрите, как теперь звонит клиент:

public static void main(String[] args) {
  ShapeMaker shapeMaker = new ShapeMaker();
  // 客户端调用现在更加清晰了
  shapeMaker.drawCircle();
  shapeMaker.drawRectangle();
  shapeMaker.drawSquare();		
}

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

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

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

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

public class Employee {
   private String name;
   private String dept;
   private int salary;
   private List<Employee> subordinates; // 下属
   public Employee(String name,String dept, int sal) {
      this.name = name;
      this.dept = dept;
      this.salary = sal;
      subordinates = new ArrayList<Employee>();
   }
   public void add(Employee e) {
      subordinates.add(e);
   }
   public void remove(Employee e) {
      subordinates.remove(e);
   }
   public List<Employee> getSubordinates(){
     return subordinates;
   }
   public String toString(){
      return ("Employee :[ Name : " + name + ", dept : " + dept + ", salary :" + salary+" ]");
   }   
}

Обычно такой класс должен определять методы add(node), remove(node), getChildren().

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

наилегчайший образец

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

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

Я не буду демонстрировать этот простой код.

Резюме структурной модели

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

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

поведенческая модель

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

режим стратегии

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

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

Сначала определите интерфейс стратегии:

public interface Strategy {
   public void draw(int radius, int x, int y);
}

Затем мы определяем несколько конкретных стратегий:

public class RedPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用红色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class GreenPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用绿色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class BluePen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用蓝色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}

Классы, использующие стратегии:

public class Context {
   private Strategy strategy;
   public Context(Strategy strategy){
      this.strategy = strategy;
   }
   public int executeDraw(int radius, int x, int y){
      return strategy.draw(radius, x, y);
   }
}

Демонстрация клиента:

public static void main(String[] args) {
    Context context = new Context(new BluePen()); // 使用绿色笔来画
  	context.executeDraw(10, 0, 0);
}

Нарисуйте это на картинке, чтобы всем было хорошо видно:

strategy-1

В этот раз вы думали о режиме моста в структурном режиме? Они на самом деле очень похожи. Я сделал снимок режима моста, чтобы вы могли сравнить:

bridge-1

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

Шаблон наблюдателя

Шаблон Observer не может быть проще для нас. Операций не более двух: наблюдатели подписываются на интересующие их топики и уведомляют наблюдателей об изменении данных в топиках.

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

public class Subject {
	
   private List<Observer> observers = new ArrayList<Observer>();
   private int state;
   public int getState() {
      return state;
   }
   public void setState(int state) {
      this.state = state;
      // 数据已变更,通知观察者们
      notifyAllObservers();
   }
   public void attach(Observer observer){
      observers.add(observer);		
   }
   // 通知观察者们
   public void notifyAllObservers(){
      for (Observer observer : observers) {
         observer.update();
      }
   } 	
}

Определите интерфейс наблюдателя:

public abstract class Observer {
   protected Subject subject;
   public abstract void update();
}

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

Давайте определим несколько конкретных классов наблюдателей:

public class BinaryObserver extends Observer {
  	// 在构造方法中进行订阅主题
    public BinaryObserver(Subject subject) {
        this.subject = subject;
        // 通常在构造方法中将 this 发布出去的操作一定要小心
        this.subject.attach(this);
    }
  	// 该方法由主题类在数据变更的时候进行调用
    @Override
    public void update() {
		String result = Integer.toBinaryString(subject.getState());
        System.out.println("订阅的数据发生变化,新的数据处理为二进制值为:" + result);
    }
}
public class HexaObserver extends Observer {
    public HexaObserver(Subject subject) {
        this.subject = subject;
        this.subject.attach(this);
    }
    @Override
    public void update() {
      	String result = Integer.toHexString(subject.getState()).toUpperCase();
        System.out.println("订阅的数据发生变化,新的数据处理为十六进制值为:" + result);
    }
}

Использование клиента также очень просто:

public static void main(String[] args) {
    // 先定义一个主题
  	Subject subject1 = new Subject();
  	// 定义观察者
  	new BinaryObserver(subject1);
  	new HexaObserver(subject1);
  
  	// 模拟数据变更,这个时候,观察者们的 update 方法将会被调用
  	subject.setState(11);
}

output:

订阅的数据发生变化,新的数据处理为二进制值为:1011
订阅的数据发生变化,新的数据处理为十六进制值为:B

Конечно, jdk также предоставляет подобную поддержку.Для получения дополнительной информации вы можете обратиться к двум классам java.util.Observable и java.util.Observer.

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

Модель цепочки ответственности

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

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

Если продукт предъявляет вам это требование, я думаю, что большинство людей должны подумать в начале, использовать список для хранения всех правил, а затем foreach выполнять каждое правило. Однако не волнуйтесь, читатели, давайте посмотрим, в чем разница между моделью цепочки ответственности и тем, что мы сказали?

Во-первых, нам нужно определить базовый класс для узлов процесса:

public abstract class RuleHandler {
  
  	// 后继节点
    protected RuleHandler successor;
  
    public abstract void apply(Context context);
  
	public void setSuccessor(RuleHandler successor) {
        this.successor = successor;
    }
    public RuleHandler getSuccessor() {
        return successor;
    }
}

Далее нам нужно определить каждый конкретный узел.

Проверьте, является ли пользователь новым пользователем:

public class NewUserRuleHandler extends RuleHandler {
    
    public void apply(Context context) {
        if (context.isNewUser()) {
          	// 如果有后继节点的话,传递下去
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(context);
            }
        } else {
            throw new RuntimeException("该活动仅限新用户参与");
        }
    }
  
}

Проверяем, может ли участвовать регион пользователя:

public class LocationRuleHandler extends RuleHandler {
    public void apply(Context context) {
        boolean allowed = activityService.isSupportedLocation(context.getLocation);
      	if (allowed) {
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(context);
            }
        } else  {
            throw new RuntimeException("非常抱歉,您所在的地区无法参与本次活动");
        }
    }
}

Проверьте, был ли получен приз:

public class LimitRuleHandler extends RuleHandler {
    public void apply(Context context) {
      	int remainedTimes = activityService.queryRemainedTimes(context); // 查询剩余奖品
        if (remainedTimes > 0) {
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(userInfo);
            }
        } else {
            throw new RuntimeException("您来得太晚了,奖品被领完了");
        }
    }
}

Клиент:

public static void main(String[] args) {
    RuleHandler newUserHandler = new NewUserRuleHandler();
  	RuleHandler locationHandler = new LocationRuleHandler();
  	RuleHandler limitHandler = new LimitRuleHandler();
  
  	// 假设本次活动仅校验地区和奖品数量,不校验新老用户
  	locationHandler.setSuccessor(limitHandler);
  	locationHandler.apply(context);
}

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

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

Шаблон метода шаблона

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

Обычно будет абстрактный класс:

public abstract class AbstractTemplate {
	// 这就是模板方法
  	public void templateMethod(){
        init();
        apply(); // 这个是重点
        end(); // 可以作为钩子方法
    }
    protected void init() {
        System.out.println("init 抽象层已经实现,子类也可以选择覆写");
    }
  	// 留给子类实现
    protected abstract void apply();
    protected void end() {
    }
}

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

Пишем класс реализации:

public class ConcreteTemplate extends AbstractTemplate {
    public void apply() {
        System.out.println("子类实现抽象方法 apply");
    }
  	public void end() {
        System.out.println("我们可以把 method3 当做钩子方法来使用,需要的时候覆写就可以了");
    }
}

Демонстрация клиентского звонка:

public static void main(String[] args) {
    AbstractTemplate t = new ConcreteTemplate();
  	// 调用模板方法
  	t.templateMethod();
}

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

Резюме поведенческой модели

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

Суммировать

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

(Конец полного текста)