1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/mifengjun-java-design-patterns

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
lvgo-design-patterns.md 170 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 26.11.2024 20:32 83ccba0

Создание объектов: паттерны проектирования

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

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

  • Паттерны создания (Creational patterns) — как создавать объекты. Основной особенностью этих паттернов является отделение процесса создания объектов от их использования. В GoF (Gang of Four, «Банда четырёх») представлены следующие паттерны этого типа: Singleton, Prototype, Factory Method, Abstract Factory, Builder, Façade, Adapter, Decorator, Composite, Flyweight, Proxy.
  • Структурные паттерны (Structural patterns) — как объединять классы или объекты в более крупные структуры. В GoF представлены такие паттерны, как Proxy, Facade, Adapter, Bridge, Composite, Decorator.
  • Поведенческие паттерны (Behavioral patterns) — описывают способы взаимодействия между классами или объектами для выполнения задач, которые не могут быть выполнены одним объектом, а также способы распределения обязанностей между ними. В GoF описаны следующие поведенческие паттерны: Template Method, Strategy, Command, Chain of Responsibility, State, Observer, Mediator, Memento, Visitor, Interpreter, Iterator, и некоторые другие.

Паттерн Singleton

Singleton используется для описания способа создания объекта. Его основная особенность заключается в том, что он отделяет процесс создания объекта от его использования. GoF описывает следующие виды паттерна Singleton: Singleton, Multiton, Registry.

Singleton

На рисунке представлена диаграмма классов для паттерна Singleton.

Singleton — класс, который должен быть реализован в виде синглтона.

Проблемы, решаемые с помощью паттерна Singleton:

  1. Обеспечение существования только одного экземпляра класса.
  2. Предотвращение создания нескольких экземпляров класса.
  3. Управление доступом к единственному экземпляру класса.
  4. Предоставление глобального доступа к экземпляру класса без передачи его в качестве параметра.
  5. Реализация паттерна Singleton позволяет избежать проблем, связанных с использованием глобальных переменных.
  6. Паттерн Singleton может использоваться для реализации глобальных конфигурационных параметров приложения.
  7. Синглтоны могут использоваться для управления ресурсами, такими как соединения с базой данных или пулы потоков.
  8. Паттерн Singleton обеспечивает глобальный доступ к объекту, что упрощает взаимодействие между различными частями системы.
  9. Синглтон может быть использован для обеспечения безопасности, например, для ограничения доступа к определённым функциям или данным.
  10. Синглтон позволяет упростить тестирование кода, так как он предоставляет предсказуемый и контролируемый доступ к объектам.

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

  • Он может затруднить тестирование, поскольку сложно создать несколько экземпляров класса для тестирования различных сценариев.
  • Синглтон делает класс зависимым от глобального состояния, что может усложнить понимание и поддержку кода.
  • Использование паттерна Singleton может привести к проблемам с многопоточностью, если не обеспечить надлежащую синхронизацию доступа к общему ресурсу.

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

Применение паттерна Singleton в реальной жизни

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

  1. Глобальные константы. В Java, например, класс java.lang.Math является синглтоном, предоставляющим доступ к математическим функциям.

  2. Управление ресурсами. Синглтоны используются для управления соединениями с базами данных, пулами потоков и другими ресурсами. Это позволяет эффективно использовать ресурсы и предотвращать их чрезмерное потребление.

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

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

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

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

current = dclSingleton = new DCLSingleton(); } } return current; }

📃 Заметки: DCL-метод предназначен для решения проблем с рациональным распределением и использованием ресурсов в отложенной загрузке (ленивая инициализация).

Конечно, я не рекомендую использовать все три метода! 😂


4. Немедленная загрузка (голодная инициализация)

public class StraightwaySingleton {

    private static final StraightwaySingleton straightwaySingleton = new StraightwaySingleton();

    private StraightwaySingleton() {
    }

    public static StraightwaySingleton getInstance() {
        return straightwaySingleton;
    }
}

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

Runtime.java использует этот подход для реализации.

Процесс загрузки класса: загрузка — проверка — подготовка — анализ — инициализация — использование — выгрузка

5. Внутренний класс (рекомендуется)

public class InnerClassSingleton {

    private InnerClassSingleton() {
    }

    public static InnerClassSingleton getInstance() {
        return InnerClassSingletonBuild.innerClassSingleton;
    }

    private static class InnerClassSingletonBuild {
        private static final InnerClassSingleton innerClassSingleton = new InnerClassSingleton();
    }

}

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

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

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

Что касается внутренних классов, вот некоторые дополнительные сведения о внутренних классах. Для получения дополнительной информации о внутренних классах см. мой блог CSDN.

Внутренние классы делятся на уровневые и классовые.

— Классовые внутренние классы относятся к статическим членам внешнего класса.

— Если нет статических членов, внутренний класс называется внутренним классом уровня объекта.

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

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

👉 Классовые внутренние классы загружаются только при первом использовании.

6. Перечисление (рекомендуется использовать)

public enum EnumIvoryTower {

    /**
     * Экземпляр
     */
    INSTANCE
}

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

👍 Одноэлементный метод, предложенный Джошуа Блохом, почти так же хорош, как и немедленная загрузка.

Он прост, щедр, элегантен и совершенен.

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

Защита одноэлементного режима

Нарушение одноэлементности

Даже если мы используем вышеупомянутые методы для создания одноэлементных объектов, всё равно есть два способа нарушить одноэлементность (кроме перечисления):

  1. Разрушение одноэлементности с помощью отражения.
  2. Разрушение одноэлементности путём сериализации.

Защита одноэлементности

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

  1. Измените частный конструктор, чтобы предотвратить создание одноэлементного объекта с помощью отражения.
  2. Перепишите метод readResolve().
Изменение частного конструктора, предотвращение создания одноэлементного объекта посредством отражения
    // Решение нарушения одноэлементности через отражение
    if (dclSingleton != null) {
        throw new IllegalStateException("Already initialized");
    }
Переписать метод readResolve()
    /**
     * Решение проблемы нарушения одноэлементности из-за сериализации
     */
    private Object readResolve() {
        return straightwaySingleton;
    }

Резюме одноэлементного шаблона

Основные моменты

  1. Объект сам создаёт себя, то есть конструктор является частным.
  2. Глобальный унифицированный доступ, экземпляр может быть доступен повторно, то есть экземпляр является статическим экземпляром.

Выбор метода реализации

Внутренний класс > перечисление > немедленная загрузка

Преимущества и недостатки

Преимущества: рациональное распределение и использование ресурсов.

Недостатки: нарушение принципа единой ответственности.

Краткое введение в Джошуа Блоха

Автор «Эффективной Java» и других книг серии «Эффективная». Мы используем код, написанный им каждый день, и фреймворк коллекций, расположенный в java.util.

 * @param <E> тип элементов в этой коллекции
 *
 * @author  Josh Bloch
 * @author  Neal Gafter
 * @see     Set
 * @see     List
 * @see     Map
 * @see     SortedSet
 * @see     SortedMap
 * @see     HashSet
 * @see     TreeSet
 * @see     ArrayList
 * @see     LinkedList
 * @see     Vector
 * @see     Collections
 * @see     Arrays
 * @see     AbstractCollection
 * @since 1.2
 */

public interface Collection<E> extends Iterable<E> {}

GitHub старого джентльмена: https://github.com/jbloch


Прототипный паттерн

Используйте существующий экземпляр в качестве прототипа, а затем создайте новый объект, скопировав прототип.

Источник: https://refactoringguru.cn/design-patterns/prototype

Источник изображения: https://refactoringguru.cn/design-patterns/prototype

Лунный пирог? Плагиат? 🤔

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

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

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

Приведённые выше примеры показывают, что поведение людей заключается в том, чтобы сэкономить время на создании чего-либо, одновременно достигая своих целей. В области проектирования это называется прототипным шаблоном, который предназначен для решения проблемы создания объектов и относится к категории шаблонов создания. Прототипный паттерн и паттерн метода-шаблона: в чём разница?

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

Схема прототипного паттерна:

prototype-UML

Рассмотрим код:

Чтобы получить исходный код, подпишитесь на ответ «Исходный код».

В Java JDK есть возможность использовать маркерный интерфейс Cloneable, который позволяет определить класс, реализующий этот интерфейс, как класс-прототип.

Реализация прототипного паттерна с помощью интерфейса Cloneable:

public class Graphics implements Cloneable {

    private final String color;
    private final String shape;


    public Graphics(String color, String shape) {
        this.color = color;
        this.shape = shape;
    }

    @Override
    protected Graphics clone() throws CloneNotSupportedException {
        return (Graphics) super.clone();
    }
    
    ..... set/get/toString
}

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

«Поверхностное» и «глубокое» клонирование

«Поверхностный» и «глубокий» относятся к правам владения объектом. Например, если я одолжу вам свой телефон, вы сможете пользоваться только тем, что уже есть в нём, и если я удалю какое-то приложение, у вас его тоже не будет. А если я подарю вам телефон, то вы сможете делать с ним всё, что захотите, не беспокоясь о моих действиях, потому что он теперь ваш.

Как же понять разницу между «поверхностным» и «глубоким» клонированием в коде Java?

После реализации интерфейса Cloneable можно создать копию объекта, отличную от текущего, но для объектов ссылочного типа это копирование не работает. Вы получаете новый объект, но не можете получить его «сердцевину». Если вы хотите получить её, нужно сделать так, чтобы она тоже была скопирована. Но для этого она должна поддерживать интерфейс Cloneable.

Пример «поверхностного» клонирования:

public class Graphics implements Cloneable {

    private final String color;
    private final String shape;
    // Ссылочный тип не реализует интерфейс Cloneable
    private final Size size;


    public Graphics(String color, String shape, Size size) {
        this.color = color;
        this.shape = shape;
        this.size = size;
    }

    @Override
    protected Graphics clone() throws CloneNotSupportedException {
        return (Graphics) super.clone();
    }
}

Ссылочный тип не реализовал интерфейс Cloneable:

// Ссылочный тип не реализовал интерфейс Cloneable
public class Size {
    public int width;
    public int height;

    public Size(int width, int height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public String toString() {
        return "Size(" + width + ", " + height + ")";
    }
}

Тестирование:

class GraphicsTest {
    @Test
    void graphicsTest() throws CloneNotSupportedException {
        Size size = new Size(1, 2);
        Graphics graphics = new Graphics("red", "circular", size);
        Graphics clone = graphics.clone();
        size.height = 3;
        size.width = 5;
        System.out.println("graphics = " + graphics);
        // Проверяем, отличаются ли два объекта
        Assertions.assertNotSame(graphics, clone);
        clone.setColor("blue");
        clone.setShape("square");
        System.out.println("clone = " + clone);
    }
}

Обратите внимание на значение ссылочного объекта size после изменения его содержимого:

graphics = Graphics[color='red', shape='circular', size=Size(5, 3)]
clone = Graphics[color='blue', shape='square', size=Size(5, 3)]

Глубокое клонирование: ссылочный тип также реализует Cloneable интерфейс

// Ссылочный тип реализовал интерфейс Cloneable
public class Size implements Cloneable {
    public int width;
    public int height;

    public Size(int width, int height) {
        this.width = width;
        this.height = height;
    }

    @Override
    protected Size clone() throws CloneNotSupportedException {
        return (Size) super.clone();
    }

    @Override
    public String toString() {
        return "Size(" + width + ", " + height + ")";
    }
}

Изменяем метод clone в исходном классе:

    @Override
    protected Graphics clone() throws CloneNotSupportedException {
        Graphics clone = (Graphics) super.clone();
        clone.size = size.clone();
        return clone;
    }

Тестируем:

class GraphicsTest {
    @Test
    void graphicsTest() throws CloneNotSupportedException {
        Size size = new Size(1, 2);
        Graphics graphics = new Graphics("red", "circular", size);
        Graphics clone = graphics.clone();
        // Изменяем содержимое ссылочного типа
        size.height = 3;
        size.width = 5;
        System.out.println("graphics = " + graphics);
        // Проверяем, отличаются ли два объекта
        Assertions.assertNotSame(graphics, clone);
        clone.setColor("blue");
        clone.setShape("square");
        System.out.println("clone = " + clone);
    }
}

Обратите внимание на значение ссылочного объекта size:

graphics = Graphics[color='red', shape='circular', size=Size(5, 3)]
clone = Graphics[color='blue',
``` **CircularFactory** расширяет класс **AbstractGraphicalFactory**:

«Процесс создания сложных кругов инкапсулируется в фабрике.
1. Выбор позиции круга;
2. Указание радиуса круга;
3. Настройка кисти, используемой для рисования фигуры;
4. Выбор цвета фигуры;
5. и т. д.

@Override
public Graphical creat() {
    return new Circular();
}
»

**Использование:**

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

**Примеры фабричного дизайна в JDK:**

* Методы *getInstance()* в java.util.Calendar, ResourceBundle и NumberFormat используют фабричный режим.
* Методы *valueOf()* в классах-обёртках (например, Boolean, Integer и др.).

**Заключение:**

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

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

При выборе фабричного метода необходимо учитывать несколько факторов:

* Количество объектов невелико, создание не сложно (*new*).
* Создание процесса сложное, но используется редко (строитель).
* Используется часто, но только один объект удовлетворяет требованиям (*Singleton*).

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

Эти проблемы можно решить с помощью *Singleton* и *Builder*, но это не значит, что нужно обязательно использовать фабричный паттерн. Главное — применять его по назначению.

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

***Абстрактная фабрика***

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

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

Абстрактная фабрика, например, «Fujifilm», имеет несколько семейств продуктов. Это помогает понять разницу между абстрактной фабрикой и фабричным методом.

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

В конце **примера абстрактной фабрики в JDK** производится серия продуктов. Таким образом, абстрактная фабрика и сама фабрика не имеют большой разницы. Когда фабрика может производить несколько серий продуктов, она фактически становится абстрактной фабрикой. Например, если вы читали мою предыдущую статью о фабричном режиме, то знаете, что в примере из JDK используется статический фабричный режим. На этот раз представлен абстрактный фабричный (фабричный метод) режим. Абстрактная фабрика и фабричный метод на самом деле происходят от одного корня. Я знаю, что запутал вас, но моя цель — дать вам понять, что эти два понятия (абстрактная фабричная модель и так называемый фабричный метод) являются одной и той же идеей.

**🔔 Если вы считаете, что я недостаточно ясно объяснил концепцию абстрактной фабрики, пожалуйста, свяжитесь со мной. Буду рад, если меня побеспокоят.**

«Если содержание, представленное звёздной пылью, не было объяснено ясно, не спешите. Продолжайте читать дальше. Если я всё ещё не понимаю, дайте мне шанс, добавьте меня в WeChat (lvgocc) или в публичный аккаунт для личного общения, пока мы не разберёмся. Вы можете сделать это, а я готов быть настойчивым».

### Использование

- Когда вы хотите управлять несколькими сериями продуктов, например, несколькими пакетами услуг? Разнообразными комбинациями стратегий? Посмотрите на свои потребности, используйте их разумно, в общем, используйте их для нескольких серий!

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

### Пример абстрактной фабрики в JDK

> Источник примера: https://www.journaldev.com/1418/abstract-factory-design-pattern-in-java

- javax.xml.parsers.DocumentBuilderFactory#newInstance()
- javax.xml.transform.TransformerFactory#newInstance()
- javax.xml.xpath.XPathFactory#newInstance()

----

## Строительный паттерн

![build](https://i.loli.net/2020/10/17/8NsSJTuzyraGeOV.png)

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

### Понимание строительства в программном обеспечении 🗯

Для слова «строительство» нечего сказать, но что такое строительный паттерн в программном обеспечении? Я уверен, что после этого примера вы поймёте, что такое строительный паттерн, хотя этого недостаточно, давайте разберёмся постепенно.

#### StringBuilder в JDK

```java
public class StringBuilderTest {
    @Test
    void test(){
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(1).append("个张三,和").append(4).append("个李四");
        System.out.println(stringBuilder.toString());
    }
}
1个张三,和4个李四
Process finished with exit code 0

Приведённый выше пример является хорошо известным StringBuilder в JDK, который используется для создания объектов String. У него есть брат-близнец StringBuffer, используемый в многопоточной среде.

ServerBootstrap в Netty

Вот ещё один пример — стартер Netty.

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(parentGroup, childGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 128)
                .childHandler(new NettyProtobufChannelInitializer());

        try {
            ChannelFuture sync = bootstrap.bind(2333).sync();
            sync.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

Silent параллельный обработчик задач в lvgo

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

        new TaskHandler<String>(testData) {
            @Override
            public void run(String s) {
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("第" + s + "个任务" + Thread.currentThread());
            }
        }.sync(false).overRun(() -> {
            log.debug("Я завершил все задачи.");
        }).execute(10);

Вышеупомянутый компонент был опубликован в репозитории Maven;

<dependency>
  <groupId>org.lvgo</groupId>
  <artifactId>silent</artifactId>
  <version>1.0</version>
</dependency>

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

  • StringBuilder создаёт разные результаты с разными параметрами append;
  • разные параметры настройки ServerBootstrap приводят к различным функциям netty сервера;
  • TaskHandler позволяет выполнять различные действия с разными параметрами.

Диаграмма классов строительного паттерна 📌

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

build-UML

Диаграмма классов GOF для строительного паттерна

gof-build-UML

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

Код 📄

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

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

Реализация диаграммы классов GOF

public abstract class Builder {

    protected PlayerRole playerRole = new PlayerRole();

    abstract void setHairColor();
    abstract void setShape();
    abstract void setSkinColour();

    PlayerRole build() {
        return playerRole;
    }
}
public class Director {

    private final Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    public PlayerRole construct() {
        builder.setHairColor();
        builder.setShape();
``` ```
builder.setSkinColour();
return builder.build();
}
public PlayerRole construct2() {
builder.setHairColor();
return builder.build();
}
public PlayerRole construct3() {
builder.setSkinColour();
return builder.build();
}
public class PlayerRoleBuilder extends Builder {
@Override
void setHairColor() {
playerRole.setHairColor("褐色");
}

@Override
void setShape() {
playerRole.setShape("健硕");
}

@Override
void setSkinColour() {
playerRole.setSkinColour("古铜色");
}
}
public class PlayerRole {

private String hairColor;
private String shape;
private String skinColour;
}

Тестовый класс

void build() {
Builder playerRoleBuilder = new PlayerRoleBuilder();
Director playerRoleBuildDirector = new Director(playerRoleBuilder);
PlayerRole construct = playerRoleBuildDirector.construct();
}

Результат

construct = PlayerRole{hairColor='褐色', shape='健硕', skinColour='古铜色'}

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

Вот переписанный код от lvgo:

public class PlayerRole {

    private String hairColor;
    private String shape;
    private String skinColour;
}
public class PlayerRoleBuilder {

    private final PlayerRole playerRole = new PlayerRole();

    PlayerRoleBuilder hairColor(String color) {
        playerRole.setHairColor(color);
        return this;
    }

    PlayerRoleBuilder shape(String shape) {
        playerRole.setShape(shape);
        return this;
    }

    PlayerRoleBuilder skinColour(String skinColour) {
        playerRole.setSkinColour(skinColour);
        return this;
    }

    PlayerRole build() {
        return playerRole;
    }
}

Тест

@Test
void test(){
    PlayerRoleBuilder playerRoleBuilder = new PlayerRoleBuilder();
    playerRoleBuilder.hairColor("红色").shape("健硕").skinColour("古铜色");
    PlayerRole build = playerRoleBuilder.build();
    System.out.println("build = " + build);
}

Результат

build = PlayerRole{hairColor='红色', shape='健硕', skinColour='古铜色'}

Резюме 🐱‍💻

«Одинаковые ресурсы, разные результаты» — это моё понимание создания объектов с помощью паттерна «Строитель». Это как строительство нашей жизни, нам предоставляются одинаковые мир, воздух, но каждый человек проявляет себя по-разному.

Используя паттерн «Строитель», мы можем более гибко обрабатывать сложный процесс создания объекта. Он отделяет процесс построения от представления. Например, если вы боретесь с длинной цепочкой методов set, возможно, стоит рассмотреть подход GOF. Это делает код более чистым и читаемым.

xxx.setA();
xxx.setB();
xxx.setC();
xxx.setD();
xxx.setE();
xxx.A().B().C().D().E().build();

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

Недостатки:

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

Пример использования

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

  1. В системе общественного питания есть 23 вида вегетарианских блюд и 18 видов мясных блюд, сегодня шеф-повар предлагает 8 видов наборов из двух вегетарианских и одного мясного блюда, как вы это реализуете?
  2. Представьте себе, что в моей вселенной (MC) есть различные предметы, и один и тот же набор предметов создаёт разные дома, ваш дом и мой дом выглядят по-разному.
  3. В играх жанра Tower Defense один и тот же лучник, каждый раз, когда он накапливает деньги на повышение уровня, в конечном итоге становится высокоуровневым лучником с одиночной атакой или низкоуровневым стрелком с рассеянной атакой.

🖇 Структурные паттерны (7)

Структурные паттерны описывают, как классы или объекты объединяются в более крупные структуры. В GoF представлены следующие структурные паттерны: прокси, адаптер, мост, декоратор, фасад, компоновщик и прототип.

Паттерн «Прокси»

Паттерн «Прокси»

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

Цель паттерна «Прокси» — решить проблему доступа к объектам, особенно когда целевой объект нельзя изменить. Эффект будет более очевидным.

Давайте сначала рассмотрим несколько диалогов, чтобы почувствовать этот «прокси».

  • «Этот Google работает медленно, давайте использовать прокси»
  • «Здравствуйте, могу ли я назначить встречу на март-декабрь следующего года?» »Свяжитесь с моим агентом📞«
  • «Я хочу сообщить вам» «Хорошо, есть какие-то проблемы, поговорите с моим адвокатом»
  • «О?👀 Это императорский посланник» «Это указ императора» «О! (Быстро преклоняю колени)»
  • «Здравствуйте, это линия связи мэра города 12345☎»

«Контролировать доступ к реальному объекту и одновременно достигать определённых целей»

Примеры из жизни

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

Знаменитости и агенты

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

Ворота парка и охрана

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

Использование паттерна «Прокси» позволяет сделать структуру нашего бизнес-кода более полной и понятной, передав некоторые контрольные и вспомогательные функции прокси-классу, который отражает принципы единой ответственности и принцип разделения интерфейсов.

В приведённых выше примерах я пытаюсь объяснить, что цель паттерна «Прокси» состоит в том, чтобы контролировать доступ к реальным объектам.

Программные примеры

Интерфейс данных mybats

Наиболее часто используемым примером паттерна «Прокси» в программировании должен быть интерфейс данных ORM-фреймворка, такого как хорошо известный mybatis, который использует паттерн «Прокси» для контроля доступа к базе данных. Через определённый интерфейс определяется, где выполнять SQL (сопоставление картографов, пространство операторов), а остальное остаётся на усмотрение разработчика.

Если перевести идею обработки паттерна «Прокси» mybatis в простые слова: «Скажите мне, где вы хотите выполнить SQL (картограф сопоставления), и я сделаю всё остальное». Здесь картограф интерфейса является прокси базы данных.

Даже #{} ${} заполнители являются проявлением паттерна «Прокси», и не обязательно иметь полный интерфейс и конкретную реализацию, прокси-класс является проявлением паттерна «Прокси». Возможно, это звучит немного натянуто.

Диаграмма классов паттерна «Прокси» 📌

prxy-UML

Код 📄

А вот и сам код:

builder.setSkinColour();
return builder.build();
}
public PlayerRole construct2() {
builder.setHairColor();
return builder.build();
}
public PlayerRole construct3() {
builder.setSkinColour();
return builder.build();
}
public class PlayerRoleBuilder extends Builder {
@Override
void setHairColor() {
playerRole.setHairColor("褐色");
}

@Override
void setShape() {
playerRole.setShape("健硕");
}

@Override
void setSkinColour() {
playerRole.setSkinColour("古铜色");
}
}
public class PlayerRole {

private String hairColor;
private String shape;
private String skinColour;
}

Тестовый класс

void build() {
Builder playerRoleBuilder = new PlayerRoleBuilder();
Director playerRoleBuildDirector = new Director(playerRoleBuilder);
PlayerRole construct = playerRoleBuildDirector.construct();
}

Результат

construct = PlayerRole{hairColor='褐色', shape='健硕', skinColour='古铜色'}
``` Для выполнения перевода необходимо уточнить запрос. Пожалуйста, уточните запрос, добавив недостающую часть исходного текста.

**Перевод предоставленного фрагмента**:

Чтобы глубже понять прокси-режим, я решил использовать принцип псевдокода прокси-режима MyBatis и изучить некоторые детали динамического прокси JDK. Конечно, я не буду писать исходный код (в конце концов, все понимают, что невозможно просто создать экземпляр интерфейса, здесь обязательно есть проблемы, я верю, что вы можете увидеть эту часть в любой статье блога, конечно, также добро пожаловать, чтобы добавить меня в WeChat (lvgocc), чтобы обсудить это в группе).

**Псевдокод основного прокси MyBatis**

```java
/**
 * Интерфейс пользователя
 *
 * @author lvgorice@gmail.com
 * @date 2020/10/21 22:51
 * @since 1.0.0
 */
public interface UserMapper {

    /**
     * Запрос
     *
     * @param id Идентификатор пользователя
     */
    void selectByUserId(int id);
}
UserMapper o = (UserMapper) Proxy.newProxyInstance(
        UserMapper.class.getClassLoader(),
        new Class[]{UserMapper.class},
        (proxy, method, arg) -> {
            // Здесь будет выполняться конкретная операция соединения с базой данных для выполнения SQL, если вам интересно, вы можете продолжить изучение MyBatis, чтобы узнать больше.
            
            // Печать параметров
             logger.info("statement position: {}, args: {}", method.getDeclaringClass().getCanonicalName() + "#" + method.getName(), Arrays.toString(arg));
             return "Идентификатор пользователя: " + arg[0] + " Друг публичного аккаунта: Звезда, присоединяйтесь к группе, чтобы вместе изучать шаблоны проектирования";
        });
14:25:43.966 [main] INFO io.github.lvgocc.App - Hello World!
14:25:44.251 [main] INFO io.github.lvgocc.App - statement position: io.github.lvgocc.proxy.UserMapper#selectByUserId, args: [2333]
14:25:44.258 [main] INFO io.github.lvgocc.App - 查询结果:Идентификатор пользователя: 2333 Друг публичного аккаунта: Звезда, присоединяйтесь к группе, чтобы вместе изучать шаблоны проектирования

MyBatis использует динамический прокси, позволяя одному интерфейсу действовать как прокси для реального объекта базы данных. Когда вам нужно, вы создаёте соединение, обращаетесь к базе данных, выполняете SQL и возвращаете результат. Если вы уже знакомы с прокси-режимом MyBatis, это должно быть несложно понять.

Давайте рассмотрим ещё один простой пример, который, конечно же, легче объяснить с помощью диаграммы.

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

Резюме 🐱‍💻

Прокси-режим существует для решения проблемы контроля доступа к объектам.

  • Когда вы хотите купить «билет домой», вы выбираете «запасного игрока», он выбирает «ускориться». В этом случае 12306 или третья сторона становятся вашими билетными агентами.
  • Когда вы приходите в незнакомый район, вам необходимо использовать карту доступа, чтобы войти. В этом случае карта доступа становится агентом района.

На основе вышеизложенного я понимаю:

  1. Когда я выбираю запасного игрока, я и покупка билетов разъединены ✔, мне не нужно ждать результата, просто подождите уведомления. Но между ними появился дополнительный запасной игрок, цепь стала длиннее ❌.
  2. Мне нужно использовать карту доступа при входе в район, количество объектов, которые необходимо поддерживать, увеличилось ❌, хотя система стала более сложной ❌, но район стал более безопасным ✔, защищая район.

Адаптерный режим

adapter-title.png

Что такое «Сун»

嘶衣唔嗯ěn损, fao喽密, Сун!

Прежде всего, давайте посмотрим на Baidu Learning.

sun.png

Сун [sǔn]: Изготовлен из дерева, бамбука и других материалов для соединения двух частей материала.

Это кажется недостаточно наглядным, поэтому давайте найдём картинку и посмотрим.

sun-picture.png

И ещё немного

sun-picture2.png

Здесь я приношу цветы Будде, Сун говорит о соединении двух кусков материала с выступающей частью, называемой вогнутой частью.

Для чего используется эта штука? Я думаю, вы знаете, это то, что говорится в Baidu Baike, именно так используется (чёрт, это не лишнее, не так ли, это именно то, что сказано в Baidu Baike), теперь она даже распространилась за границу.

Сун и вогнутая часть

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

Следующие изображения взяты из «Дизайн-паттернов Чань (второе издание)» и были слегка изменены.

Изначально они были соединены таким образом

adapter.png

Однажды мне дали один

adapter1.png

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

adapter2.png

К счастью, эти великие мастера и ремесленники сделали адаптер, и он, вероятно, выглядит примерно так.

adapter3.png

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

adapter4.png

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

kuozhanwu

Это расширение Huawei, на всём компьютере всего два внешних интерфейса, один для зарядки, а другой для этого расширения. Чтобы подключить USB-устройство, необходимо пройти через это расширение.

Определение

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

Во-первых, вы должны знать, что существует две формы адаптера, одна из которых является адаптером класса, а другая называется адаптером объекта. Что это такое?

Адаптер класса: Достижение адаптации через наследование классов или реализацию интерфейсов;

Объектный адаптер: Достижение адаптации путём объединения объектов.

Диаграмма классов адаптера 📌

Посмотрите на диаграмму ещё раз и переварите её.

adapter-class.png

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

Код не может быть переписан. (Трудозатраты, стабильность системы и т. д.)

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

adapter-object

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

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

Такой способ, как объектная композиция, будет более гибким. Обычно адаптер предпочитает этот способ для подключения двух интерфейсов. ``` calendar.setTime(date); SimpleDateFormat df = new SimpleDateFormat("YYYY_MM_DD_TIGHT", Locale.CHINA); return df.format(calendar.getTime()); }

public static String format(Date date) { if (date == null) { return null; } else { return new SimpleDateFormat("YYYY_MM_DD", Locale.CHINA).format(date); } }


Этот фрагмент представляет собой класс на языке Java, который содержит метод для форматирования даты. Метод `format` принимает объект типа `Date` и возвращает строку с представлением даты в формате YYYY-MM-DD. Если переданный параметр равен `null`, то метод вернёт `null`.

В методе используется класс `SimpleDateFormat` для создания объекта форматирования, который будет использоваться для преобразования даты в строку. Формат строки задаётся в виде строки, которая определяет шаблон для представления даты. В данном случае это формат YYYY_MM_DD, где YYYY обозначает год, MM — месяц, а DD — день.

Также в классе есть метод `format`, который принимает объект `Date` и использует другой экземпляр класса `SimpleDateFormat`, чтобы преобразовать дату в строку в формате YYYY-MM-DD.

/**

  • 数组工具类

  • 欢迎跟我一起学习,公众号搜索:星尘的一个朋友

  • 也可以加我微信(lvgocc)拉你进群

  • @author lvgorice@gmail.com

  • @version 1.0

  • @blog @see http://lvgo.org

  • @CSDN @see https://blog.csdn.net/sinat_34344123

  • @date 2020/10/29 */ public class ArraysUtil {

    public static List asList(T... a) { return new ArrayList<>(a); } private static class ArrayList extends AbstractList implements RandomAccess, java.io.Serializable { private static final long serialVersionUID = -2764017481108945198L; private final E[] a;

     ArrayList(E[] array) {
         a = Objects.requireNonNull(array);
     }
     .......
         ......

    } }


Это класс на Java, который реализует массивный инструмент. Он предоставляет метод `asList`, который создаёт список из массива элементов. Этот метод может быть использован для удобного преобразования массива в список.

Класс также содержит внутренний класс `ArrayList`, который расширяет абстрактный класс `AbstractList` и реализует интерфейсы `RandomAccess` и `java.io.Serializable`. Это позволяет использовать его как обычный список в Java.

Остальная часть текста описывает различные аспекты использования адаптеров в программировании и их роль в решении проблем совместимости между различными компонентами системы. **Грин Ректангулар**, **Грин Триангулар**... (звук становится тише)

Старший брат: «А давай ещё один эллипс добавим».

«Эмм... у меня нож пропал!»

«Младший брат, не нервничай, старший брат тебе поможет найти».

**Старший брат помогает диагностировать код**

Старший брат: «У тебя тут проблема с наследованием, из-за этого класс взрывается. Если ты не избавишься от неправильного понимания наследования, всё будет потеряно».

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

Старший брат: «Ну, давай посмотрим. Когда ты используешь наследование?»

Я: «Когда у нескольких классов есть общие характеристики, мы абстрагируем эти характеристики и расширяем их с помощью наследования».

Старший брат: «Понятно, похоже, что у тебя ещё есть шанс. А как насчёт абстракции сейчас?»

Я тихо бормочу: «Много форм, абстракция в виде формы, никаких проблем».

Старший брат: «Что насчёт цвета? Как цвет связан с формой?»

Я: «Эмм... как связан? Старший брат, дай мне подсказку».

Старший брат: «Ты не изучал UML и агрегацию/композицию?»

Я: «Нет, не изучал».

Старший брат: «Тогда я объясню тебе ещё раз. Смотри внимательно».

![uml](https://i.loli.net/2020/10/27/RQfH3ho9Zg7TJmG.png)

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

Старший брат: «Теперь ты понимаешь, как лечить взрыв класса?»

Я: «Я должен абстрагировать цвет и использовать композицию для связи с формой! Верно?»

Старший брат: «Неплохо, продолжай смотреть. Мне нужно заняться своими делами».

**Рефакторинг кода**

После того как я понял смысл слов старшего брата, я провёл рефакторинг своего кода.

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

![bridge1.png](https://i.loli.net/2020/10/27/DUeGhk2pIcoKdwW.png)

С диаграммой классов я быстро реорганизовал свой код и протестировал его.

> Полный код можно получить, подписавшись на публичный аккаунт и ответив «исходный код».



![bridge-test.png](https://i.loli.net/2020/10/27/EPTuQae6DofyFSG.png)

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

### Определение

> Разделение абстрактной части и её реализации, чтобы они могли изменяться независимо друг от друга.

### Понимание абстракции, реализации и разделения

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

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

### Абстракция, реализация и разделение

Благодаря помощи старшего брата мне стало легче понять фразу «разделение абстрактной части и её реализации, чтобы они могли изменяться независимо друг от друга».

Возьмём пример с графическим интерфейсом, о котором я недавно узнал.

- Абстрактная часть — это форма и цвет графики, которые обязательно имеют форму и цвет. Существует два различных измерения изменений.
- Реализация — это конкретные формы и цвета. Формы и цвета должны иметь конкретное выражение. Это может быть красный прямоугольник или прозрачный квадрат. Форма является частью графика, поэтому она может наследоваться от основного объекта через наследование. Цвет можно выделить отдельно для расширения.

Независимое изменение означает реализацию двух аспектов абстрактной и конкретной частей.

- Изменение абстрактной части заключается в создании прямоугольника, наследуемого от абстрактного класса графики. Одновременно завершается конструктор, который исправляет абстрактную часть.
- Изменение конкретной части соответствует принципу замены Лискова и принципу инверсии зависимостей с абстрактной частью. Таким образом, конкретная часть может свободно изменяться в своём собственном интерфейсе определения, а также может быть связана с абстрактной частью (мост).

Я пытаюсь упростить сложные вещи.

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

### Диаграмма моста

![bridge2.png](https://i.loli.net/2020/10/27/eQgjH6wkB2pSLfO.png)

### Код 📄

> Полный исходный код можно найти в общедоступном аккаунте.



![bridge-code.png](https://i.loli.net/2020/10/27/iXWcetHJUxyBFNT.png)

### Заключение 🐱‍💻

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

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

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

Недостатком этой модели является то, что она увеличивает сложность системы и требует более глубокого понимания системы. Количество поддерживаемых классов увеличивается.
Это даёт ощущение «съесть горькое и почувствовать облегчение». На самом деле, для моста есть ещё одно, что вам нужно правильно разделить многомерные состояния объекта, иначе это будет похоже на «держать молоток в руке и видеть всё как гвозди». **Код демонстрирует способ понимания конкретной реализации паттерна «Декоратор» через игру, в которой игрок получает предметы-усилители.**

Кажется, я не выбрал Супер Марио, потому что его сложно найти, и код плохо передаёт идею.

Поэтому я выбрал игру **«Tank Battle»**. 😂

«Здесь так много воспоминаний», и это хорошо передаёт атмосферу. 😁

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

На этом изображении мы видим, что после того как мы съели звезду (декоратор), мы стали сильнее. 

**Структура:**
1. Интерфейс декорируемого объекта.
2. Конкретный декоратор.
3. Абстрактный декоратор.
4. Конкретные декораторы.

Поскольку у усиления есть три уровня, то должно быть три декоратора.

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

Итак, у нас есть три класса персонажей:

1. Декорируемый объект — танк (Tank).
2. Конкретный декорируемый объект — игрок-танк (PlayerTank).
3. Абстрактный декоратор — используется для определения базовой информации о декораторах, например, конструкторов (TankDecorator).
4. Три конкретных декоратора (OneStarTankDecorator, TwoStarTankDecorator и ThreeStarTankDecorator).

**Части кода:**

> Подписывайтесь на публичный аккаунт: «Друг одного из друзей звёздной пыли», чтобы получить полный код и диаграмму классов.

```java
/**
 * Игрок-танк
 *
 * @author lvgorice@gmail.com
 * @date 2020/10/25 11:40
 * @since 1.0.0
 */
public class PlayerTank extends Tank {

    /**
     * Данные игрока по умолчанию: скорость перемещения 1, конструкция 1
     */
    public PlayerTank() {
        super(1, 1);
    }
}
/**
 * Определение абстрактного декоратора танка
 *
 * @author lvgorice@gmail.com
 * @date 2020/10/25 11:40
 * @since 1.0.0
 */
public abstract class TankDecorator extends Tank{

    protected Tank tank;

    public TankDecorator(Tank tank) {
        this.tank = tank;
    }

    @Override
    protected void move() {
        tank.move();
    }

    @Override
    protected void fire() {
        tank.fire();
    }
}

Результаты теста:

Заключение

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

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

Паттерн «Декоратор» позволяет динамически усиливать объекты без изменения их структуры. Это также отражает принцип «открыто-закрыто». Однако чрезмерное использование этого паттерна может привести к увеличению количества классов и усложнению поддержки объектов с несколькими декораторами.

Например, в JDK метод read() для операций ввода-вывода был усилен до readLine().

Ещё один пример — инструменты Collections в JDK, которые усиливают коллекции, делая их потокобезопасными, но не изменяя сами объекты.

Это всего лишь добавление ключевого слова synchronized перед каждым методом исходного объекта.

А List по-прежнему имеет множество подклассов. Инструменты Collections предоставляют усиление для List.

Конец

Когда мы понимаем суть вещи, нам легче видеть её проявления. Но самое важное — это работать с ней самостоятельно и получать опыт. Это напоминает мне фразу из учебника физики в средней школе: «Изучай физику, работая руками».

Также важно понимать, что просто читать и слушать недостаточно. Как и в случае с учебниками по литературе, нужно практиковаться. Продолжайте!


Фасад

Сегодня я потратил 5 минут 23 секунды, чтобы понять паттерн «Фасад», засекал время!

История студента

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

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

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

Я думаю о том, как слуга в истории студента представляет паттерн «Фасад». Он упрощает процесс отправки писем для студента, предоставляя ему простой интерфейс для взаимодействия.

Определение

Фасад предоставляет единый интерфейс для доступа к нескольким сложным системам, упрощая их использование.

Первый пример фасада, который приходит мне в голову, — это SLF4J, фреймворк для логирования. Он действует как фасад, предоставляя простой способ записи логов, скрывая сложность процесса логирования.

Другой пример — MVC (Model-View-Controller), который является архитектурным паттерном. Он разделяет приложение на три компонента: модель, представление и контроллер. Контроллер выступает в роли фасада, обрабатывая запросы пользователя и взаимодействуя с моделью и представлением. MVC, model, view, control. Но все ли внимательно исследовали отношения этих трёх понятий? Конечно, сегодня речь пойдёт не о MVC, а о внешнем шаблоне проектирования.

V — это клиент (view), C — сервер (control), M — процесс отправки и получения сообщений (model). Клиент (view) никогда не должен знать, как сервер (control) отправляет и получает сообщения (model).

Внешний шаблон проектирования: схема классов

Фасад 2

facade2

Внешний шаблон проектирования: классы

facade3.png

Мы рассмотрим код, чтобы понять, как клиент помогает серверу отправлять сообщения.

/**
 * 书生送信 - 书生
 * <p>
 * 欢迎跟我一起学习,公众号搜索:星尘的一个朋友
 * 也可以加我微信(lvgocc)拉你进群
 *
 * @author lvgorice@gmail.com
 * @version 1.0
 * @blog @see http://lvgo.org
 * @CSDN @see https://blog.csdn.net/sinat_34344123
 * @date 2020/11/2
 */
public class ShuSheng {
    static final Logger LOGGER = LoggerFactory.getLogger(ShuSheng.class);

    public static void main(String[] args) {
        LOGGER.info("书生写好信给了书童");
        ShuTong shuTong = new ShuTong();
        shuTong.songXin();
        LOGGER.info("书童拿回了信给了书生");
    }
}

Клиент (book youth) пишет сообщение и передаёт его серверу (book boy). Сервер обрабатывает сообщение и возвращает результат клиенту.

Внешний шаблон проектирования — это шаблон, который следует принципу наименьших знаний (LKP). Принцип LKP также называют принципом Деметры. В 1987 году в Северо-Восточном университете (Northeastern University) появился проект под названием «Деметра».

Внешний шаблон проектирования похож на фасад. Фасад скрывает сложность системы и предоставляет простой интерфейс для взаимодействия с ней.

В этом примере мы видим два подмодуля: ворота и отец.

Резюме

Принцип LKP (принцип наименьших знаний) также называется принципом Деметры. Он был предложен в 1987 году в проекте «Деметра» Северо-восточного университета. Внешний шаблон проектирования следует этому принципу.

Клиенту (book youth) нужно только отправить сообщение серверу (book boy), он не знает, как именно сервер обрабатывает это сообщение. Это упрощает взаимодействие между клиентом и сервером.

Основные характеристики внешнего шаблона проектирования:

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

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

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

Автор считает, что истинное понимание чего-либо приходит тогда, когда человек может научить этому других.

Шаблон проектирования «Flyweight»

Шаблон проектирования «flyweight» (или «享元») используется для эффективного повторного использования большого количества мелких объектов.

Слово «flyweight» переводится как «вес пера», но автор считает, что более подходящим переводом будет «лёгкий вес». Однако слово «享元» уже закрепилось в качестве перевода, поэтому автор использует его.

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

Автор приводит пример из игры «Minecraft». В этой игре карта состоит из множества блоков, каждый из которых занимает определённый объём памяти. Если каждый блок занимает 1 КБ, то для загрузки карты потребуется 1 ГБ памяти. Если размер блока увеличится до 2 КБ, то потребуется уже 2 ГБ.

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

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

Ещё один пример — игра «League of Legends». В игре есть три линии, на каждой из которых находится определённое количество юнитов. Всего на поле может быть до 48 юнитов, но они могут повторяться.

Анализ показывает, что в игре всего три типа юнитов: пехотинцы, маги и артиллерия. Они различаются по цвету. Также есть два вида снарядов. Получается, что существует всего восемь уникальных объектов, которые можно повторно использовать.

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

Объекты, которыми управляет шаблон проектирования, называются «flyweights» («享元»).

Структура шаблона проектирования «flyweight»:

  1. Клиент — отвечает за получение и отображение объектов.
  2. Фабрика — предоставляет единый интерфейс для создания объектов.
  3. Объекты — конкретные объекты, которые могут быть повторно использованы. 抽象工厂模式 — это шаблон проектирования, который используется для создания объектов без указания конкретных классов. Он позволяет создавать объекты, не зная их конкретных типов, и обеспечивает гибкость в выборе реализации.

享元模式 (Flyweight) — это шаблон, который позволяет эффективно использовать ресурсы путём совместного использования общих данных между несколькими объектами. Это достигается за счёт разделения объекта на две части: внешнюю, которая может отличаться у разных объектов, и внутреннюю, которая является общей для всех объектов.

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

В тексте запроса есть код на языке Java, но он не связан с описанными шаблонами проектирования. Определение алгоритма в операции: что это значит?

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

Это позволяет подклассам переопределять определённые шаги алгоритма, сохраняя его общую структуру. Здесь «определённые» означает те шаги, которые были оставлены как «заполнители» в алгоритме.

Например, при создании презентаций PowerPoint (PPT) большинство настроек макета уже заданы, и пользователю остаётся только заполнить содержимое страниц. Аналогично, при написании требований часто используется шаблонный подход.

Переопределение

Шаблонный метод — это предоставление конкретного алгоритма с определёнными шагами (1, 2, 3, 4), где шаги 1, 2 и 4 уже реализованы, а шаг 3 оставлен для выполнения конкретным исполнителем. Важно, чтобы этот шаг соответствовал общим требованиям алгоритма.

Схема шаблонного метода

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

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

Использование шаблонного метода на практике

Шаблонные методы могут показаться простыми, но их применение может быть весьма эффективным. Например, механизм блокировки Java использует шаблонный метод для управления блокировками через AQS (AbstractQueuedSynchronizer).

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

  1. Определение интерфейса блокировки, который ограничивает несколько ключевых методов.
  2. Реализация конкретной блокировки, где «особенные шаги» определяются AQS.

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

Текст запроса:

    l++;
}
signal.countDown();
});
thread.start();
}
signal.await();
Assertions.assertEquals(threadCount * loop, f);
}

Запускаемый результат:

template-method-test.png

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

Резюме 📚

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

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

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


Стратегический режим

strategy-title.png

Стратегический паттерн был впервые представлен в «Битве танков» Ма Лаоши, который говорил кратко и ясно. Теперь, когда я вспоминаю об этом, это всё ещё свежо в моей памяти.

Говоря о стратегическом паттерне, следует сосредоточиться на самом слове «стратегия». Я прямо приведу отрывок из определения этого слова на Baidu:

image-20201121172440172

Извлеките и разработайте два связанных значения:

  1. Набор возможных стратегий для достижения цели.
  2. Выбор различных стратегий в зависимости от ситуации.

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

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

Имея это базовое понимание, учиться становится намного проще.

Диаграмма классов стратегического паттерна 📌

strategy-uml.png

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

Переключение тем сайта 🎨

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

Требования:

  1. Пользователи могут выбирать из трёх предустановленных тем по своему вкусу.

Три предустановленные темы:

  1. Разноцветная чёрная:
    • Фон: backgroundColor чёрный.
    • Цвет шрифта: fontColor серый.
  2. Пёстрая чёрно-белая:
    • Фон: backgroundColor чёрно-серый.
    • Цвет шрифта: fontColor белый.
  3. Яркая многоцветная чёрная:
    • Фон: backgroundColor серо-чёрный.
    • Цвет шрифта: fontColor чёрный.

Реализация кода:

/**
 * Theme 主题接口
 * <p>
 * 欢迎跟我一起学习,微信(lvgocc)公众号:星尘的一个朋友
 *
 * @author lvgorice@gmail.com
 * @version 1.0
 * @blog @see http://lvgo.org
 * @CSDN @see https://blog.csdn.net/sinat_34344123
 * @date 2020/11/21
 */
public interface Theme {

    void show();
}
/**
 * Context
 * <p>
 * 欢迎跟我一起学习,微信(lvgocc)公众号:星尘的一个朋友
 *
 * @author lvgorice@gmail.com
 * @version 1.0
 * @blog @see http://lvgo.org
 * @CSDN @see https://blog.csdn.net/sinat_34344123
 * @date 2020/11/21
 */
public class Context {
    private Theme theme;

    public Theme getTheme() {
        return theme;
    }

    public void setTheme(Theme theme) {
        this.theme = theme;
    }

    public void show() {
        theme.show();
    }
}

Тестовый код:

/**
 * ThemeTest
 * <p>
 * 欢迎跟我一起学习,微信(lvgocc)公众号:星尘的一个朋友
 *
 * @author lvgorice@gmail.com
 * @version 1.0
 * @blog @see http://lvgo.org
 * @CSDN @see https://blog.csdn.net/sinat_34344123
 * @date 2020/11/21
 */
class ThemeTest {

    @Test
    void show() {
        Context context = new Context();
        System.out.println("Разноцветная чёрная");
        context.setTheme(new ColorfulBlack());
        context.show();

        System.out.println("Пёстрая чёрно-белая");
        context.setTheme(new MotleyBlack());
        context.show();

        System.out.println("Яркая многоцветная чёрная");
        context.setTheme(new SplendidBlack());
        context.show();
    }
}

Результаты теста:

Разноцветная чёрная
- Фон: backgroundColor чёрный.
- Цвет шрифта: fontColor серый.

Пёстрая чёрно-белая
- Фон: backgroundColor чёрно-серый.
- Цвет шрифта: fontColor белый.

Яркая многоцветная чёрная
- Фон: backgroundColor серо-чёрный.
- Цвет шрифта: fontColor чёрный.

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

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

JDK определяет серию алгоритмов для разных типов данных и инкапсулирует их, позволяя им заменять друг друга через интерфейс Comparator. Для клиента вызов метода сравнения — это всё, что нужно сделать, даже если алгоритм меняется (заменяется другим алгоритмом), это не повлияет на использование клиентом. Текст на русском языке:

CaseInsensitiveComparator реализует Comparator и java.io.Serializable { // use serialVersionUID from JDK 1.2.2 for interoperability private static final long serialVersionUID = 8575799808933029326L;

public int compare(String s1, String s2) {
    int n1 = s1.length();
    int n2 = s2.length();
    int min = Math.min(n1, n2);
    for (int i = 0; i < min; i++) {
        char c1 = s1.charAt(i);
        char c2 = s2.charAt(i);
        if (c1 != c2) {
            c1 = Character.toUpperCase(c1);
            c2 = Character.toUpperCase(c2);
            if (c1 != c2) {
                c1 = Character.toLowerCase(c1);
                c2 = Character.toLowerCase(c2);
                if (c1 != c2) {
                    // No overflow because of numeric promotion
                    return c1 - c2;
                }
            }
        }
    }
    return n1 - n2;
}

/** Replaces the de-serialized object. */
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }

}

Если объект сам поддерживает сравнение, то есть является реализацией интерфейса Comparable, можно использовать метод, предоставляемый Comparator:

public static <T extends Comparable<? super T>> Comparator naturalOrder() { return (Comparator) Comparators.NaturalOrderComparator.INSTANCE; }

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

/**

  • Package private supporting class for {@link Comparator}. */ class Comparators { private Comparators() { throw new AssertionError("no instances"); }

    /**

    • Compares {@link Comparable} objects in natural order.

    • @see Comparable */ enum NaturalOrderComparator implements Comparator<Comparable> { INSTANCE;

      @Override public int compare(Comparable c1, Comparable c2) { return c1.compareTo(c2); }

      @Override public Comparator<Comparable> reversed() { return Comparator.reverseOrder(); } }

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

// Обычное сравнение static <T extends Comparable<? super T>> Comparator naturalOrder() { return NaturalOrderComparator.INSTANCE; } // Пустые значения меньше непустых static Comparator nullsFirst(Comparator<? super T> var0) { return new NullComparator(true, var0); } // Пустые значения больше непустых static Comparator nullsLast(Comparator<? super T> var0) { return new NullComparator(false, var0); }

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

Давайте посмотрим, как использовать компараторы JDK для достижения второго пункта стратегического шаблона: «Они могут заменять друг друга, и изменение алгоритма не влияет на клиентов, использующих этот алгоритм».

Кто больше: «ты» или «хорошо»?

Мы попробуем сравнить два китайских иероглифа: «你» и «好».

public class TestJDKComparator {

    public static void main(String[] args) {
        String you = "你";
        String fine = "好";
        // String 的比较器
        int ctic = you.compareToIgnoreCase(fine);
        System.out.println("compareToIgnoreCase:" + ctic);

        // JDK 提供的默认比较器
        Comparator<String> comparator = Comparator.naturalOrder();
        int compare = comparator.compare(you, fine);
        System.out.println("naturalOrder = " + compare);

        // 自定义比较器1
        Comparator<String> stringComparator1 = Comparator.nullsFirst((o1, o2) -> {
            byte[] bytes1 = o1.getBytes();
            byte[] bytes2 = o2.getBytes();
            return bytes1.length - bytes2.length;

        });
        int compare1 = stringComparator1.compare(you, fine);
        System.out.println("nullsFirst&customComparator1 = " + compare1);

        // 自定义比较器2
        Comparator<String> stringComparator2 = Comparator.nullsFirst((o1, o2) -> {
            int length1 = o1.length();
            int length2 = o2.length();
            int min = Math.min(length1, length2);
            for (int i = 0; i < min; i++) {
                char o1Char =
``` **Текст на русском языке:**

o1.charAt(i); char o2Char = o2.charAt(i); if (o1Char != o2Char) { return o2Char - o1Char; } } return length2 - length1; }); int compare2 = stringComparator2.compare(you, fine); System.out.println("nullsFirst&customComparator2 = " + compare2); }


**Тестирование результатов:**

В JDK есть метод сравнения, который позволяет нам учиться. Это метод naturalOrder(), возвращающий этот компаратор.

```java
// Обычный компаратор
static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
    return NaturalOrderComparator.INSTANCE;
}
enum NaturalOrderComparator implements Comparator<Comparable<Object>> {
    INSTANCE;

    // Реализация метода сравнения компаратора
    @Override
    public int compare(Comparable<Object> c1, Comparable<Object> c2) {
        // Использование стратегии, определённой параметром
        return c1.compareTo(c2);
    }

    @Override
    public Comparator<Comparable<Object>> reversed() {
        return Comparator.reverseOrder();
    }
}

Этот компаратор имеет ограничение параметра: он должен быть реализацией класса Comarable и быть подклассом этого класса. Фактически, этот параметр является стратегическим интерфейсом «стратегии», а передаваемый параметр — это конкретная стратегия. Поскольку этот переданный параметр должен реализовывать метод compareTo, то есть реализовывать абстрактный метод интерфейса Comarable.

public interface Comparable<T> {
    int compareTo(T var1);
}

В JDK более гибкое использование компараторов осуществляется с использованием анонимных классов.

Резюме

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


Шаблон команды

Инкапсулирует запрос в виде объекта, позволяя вызывающему объекту и объекту, выполняющему запрос, быть разделенными.

Введение

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

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

Начало обучения

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

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

  1. Клиентское приложение.
  2. Кнопка «Создать файл» (вызов операции создания файла).
  3. Операция команды.
  4. Приёмник операции (отвечает за конкретное выполнение операции).

Я попытался написать код в соответствии со структурой:

public class Client {
    public static void main(String[] args) {
        FileReceiver fileReceiver = new FileReceiver();
        AddFileCommand addFileCommand = new AddFileCommand(fileReceiver);
        Invoker invoker = new Invoker();
        invoker.setCommand(addFileCommand);
        invoker.executeCommand();
    }
}
  1. Клиенты Client.
  2. Кнопка «Создать файл» (Invoker).
  3. Команда операции AddFileCommand.
  4. Получатель операции FileReceiver.
Создать файл

Сомнения относительно «команды» 🤔

Следуя этому методу реализации, у меня возникло ощущение, что я напрямую вызываю FileReceiver, разве это не хорошо?

Без необходимости в команде:

Создать файл

Решение «команда» 🤪

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

Затем мы начали размышлять 🤔: если нет промежуточного объекта «команда», где эти функции будут реализованы? Они могут быть реализованы только в получателе, то есть в конкретном классе реализации логики, тогда это нарушает принцип единой ответственности? Кроме того, такое «вспомогательное» расширение функций может привести к раздуванию класса реализации логики.

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

Диаграмма классов шаблона команды 📌

Основная структура:

  1. Вызывающий объект, также открытый для клиентского приложения Invoker.
  2. Интерфейс команды Command (соответствует принципу инверсии зависимости, легко расширяемый).
  3. Конкретная команда, включая получателя этой команды ConcreteCommand.
  4. Получатель команды, здесь не перечислены конкретные классы реализации, потому что любой класс может быть получателем Receiver.

Код 📃

Эта статья использует общую структуру для реализации шаблона команды, на основе которой мы можем сделать много расширений, таких как замена команды на List<Command> в классе Invoker, чтобы реализовать организацию очередей запросов, отмену и т. д.

Резюме 📚

Сценарии применения: 1. Необходимо регистрировать запросы; 2. Запросы могут быть поставлены в очередь на обработку; 3. Запросами можно управлять: отменять или повторять; 4. Решение о выполнении запроса принимает конкретный получатель (если запрос не оформлен как объект, то принять решение сложно).

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

В заключение ещё раз о командном режиме: «Чжан Сань, закрой дверь». Здесь я — Invoker, «закрой дверь» — это команда (command), а «Чжан Сань» — получатель (receiver). Чаще всего в реальной разработке «закрой дверь» уже определено, и можно просто выбрать его, как кнопку на пульте дистанционного управления. Но помните, что использовать этот режим следует по назначению!

Если у вас есть вопросы или сомнения, пожалуйста, свяжитесь со мной через WeChat (lvgocc) для обсуждения или присоединяйтесь к группе для общения! Становится прохладно 🥶🥶, присоединяйтесь к группе, чтобы согреться! Жду вас~


Цепочка обязанностей

chain-of-responsibility.png

Изображение взято из источника: https://refactoringguru.cn/design-patterns/chain-of-responsibility

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

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

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

chain-of-responsibility-none

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

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

chain-of-responsibility-wanger

Ван Эр сдался.

Когда коллега Чжан Сан узнал о ситуации Ван Эр, он решил внести предложение по улучшению процесса оформления отпусков в знак уважения к грустному опыту Ван Эр. Вот его предложения:

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

Диаграмма классов модели цепочки обязанностей 📌

chain-of-responsibility-UML.png

Процесс оформления отпуска Чжан Саня 📃

Схема процесса оформления отпуска с использованием шаблона цепочки обязанностей.

chain-of-responsibility-zhangsan

Абстрактный обработчик, обеспечивающий единый стандарт обработки для всех этапов.

public abstract class AbstractHandler {

    protected AbstractHandler next;

    public AbstractHandler getNext() {
        return next;
    }

    public void setNext(AbstractHandler next) {
        this.next = next;
    }

    protected void handle(String request) {
        conCreteHandle(request);
        if (getNext() == null) {
            System.out.println("Процесс завершён");
        } else {
            getNext().handle(request);
        }
    }

    protected abstract void conCreteHandle(String request);
}
class QingJia extends AbstractHandler{
    @Override
    protected void conCreteHandle(String request) {
        System.out.println(request);
    }
}
class AbstractHandlerTest {

    @Test
    void handle() {
        AbstractHandler qingJia = new QingJia();
        AbstractHandler renShi = new RenShi();
        AbstractHandler shangjiLingdao = new ShangjiLingdao();
        AbstractHandler tongShi = new TongShi();
        AbstractHandler zuZhang = new ZuZhang();

        qingJia.setNext(tongShi);
        tongShi.setNext(zuZhang);
        zuZhang.setNext(shangjiLingdao);
        shangjiLingdao.setNext(renShi);

        qingJia.handle("Чжан Сан просит отпуск");

    }
}

Тестирование процесса оформления отпуска Чжан Саня

Чжан Сан просит отпуск
Одобрено коллегой
Одобрен руководителем группы
Одобрено руководителем отдела
Одобрено отделом кадров
Процесс завершён

Полный код находится в конце статьи. Ответьте «исходный код», чтобы получить его.

Резюме 📚

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

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

Это типичный пример кода, написанного в соответствии с принципами процедурного программирования. Впоследствии он был переработан в соответствии с концепцией объектно-ориентированного подхода.

public class OOPWork {

    public static void main(String[] args) {
        Working working = new Working();
        // 9 часов утра
        working.clock = 9;
        working.writeCode();
        // полдень, 12 часов
        working.clock = 12;
        working.writeCode();
        // 3 часа дня
        working.clock = 15;
        working.writeCode();
        // 21 час вечера
        working.clock = 21;
        working.writeCode();
    }
}

class Working {
    int clock;

    public void writeCode() {
        if (clock < 12) {
            new MorningState();
        } else if (clock < 13) {
            new NoonState();
        } else if (clock < 17) {
            new AfterNoonState();
        } else if (clock < 23) {
            new EveningState();
        }
    }
}

class MorningState {

    {
        System.out.println("Написание кода с трудом");
    }
}

class NoonState {

    {
        System.out.println("Усталость и сонливость при написании кода");
    }
}

class AfterNoonState {

    {
        System.out.println("Обычное состояние при написании кода");
    }
}


class EveningState {

    {
        System.out.println("Утомление от сверхурочной работы при написании кода");
    }
}

На самом деле, я думаю, что даже если вы не читали эту книгу, вы всё равно сможете кое-что понять. Этот класс Working выглядит немного странно. Каждый раз, когда добавляется новое состояние, приходится изменять этот класс. Кроме того, здесь он отвечает за все рабочие состояния, а также за наиболее важный аспект — это оператор if else. Не слишком ли он длинный? На самом деле, всё это можно решить с помощью паттерна «Состояние», и эти вещи также нарушают некоторые принципы или рекомендации в области проектирования программного обеспечения.

  1. Добавление нового состояния требует изменения класса (нарушение принципа открытости/закрытости).
  2. Здесь он отвечает за все рабочие состояния (нарушение принципа единственной ответственности).
  3. И самое главное, этот оператор if else кажется слишком длинным (это плохой запах кода, о котором говорится в книге по рефакторингу).

Решение этих трёх проблем с использованием паттерна «Состояние»

Сначала рассмотрим структуру паттерна «Состояние».

Структура паттерна «Состояние»:

Здесь есть несколько ключевых ролей:

  • Контекст среды выполнения Context, который соответствует классу Working в программе.
  • Интерфейс состояния State, предназначенный для решения проблем с единственной ответственностью и принципом открытости/закрытости.
  • Конкретные состояния, то есть реализации интерфейса State.

Код паттерна «Состояние»:

Если мы переведём код выше в формат паттерна «Состояние», он будет выглядеть следующим образом:

@Test
void writeCode() {
    Working working = new Working(new MorningState());
    // Имитация разных моментов времени вручную
    working.setClock(9);
    working.writeCode();

    working.setClock(12);
    working.writeCode();

    working.setClock(15);
    working.writeCode();

    working.setClock(21);
    working.writeCode();


    working.setClock(24);
    working.writeCode();
}
public class Working {
    /**
     * Текущее рабочее состояние
     */
    private final WorkState concurrentState;

    /**
     * Текущий момент времени
     */
    private int clock;

    public Working(WorkState concurrentState) {
        this.concurrentState = concurrentState;
    }

    public void writeCode() {
        concurrentState.handle(this);
    }
    ....
    ....
    // Из-за ограничения объёма информации полный код предоставляется в ответ на запрос «исходный код».
}

Результаты теста:

Написание кода с трудом
Усталость и сонливость при написании кода
Обычное состояние при написании кода
Утомление от сверхурочной работы при написании кода
Не пишите больше, программист идёт домой, попробуйте завтра утром, сейчас уже 24 часа. Пожалейте его. Сейчас уже полночь.

Из-за ограничения объёма информации полный код предоставляется в ответ на запрос «исходный код».

Таким образом, не только устраняется проблема длинного метода if else, но и класс Working становится более сфокусированным на «написании кода». Также появляется интерфейс State, реализующий принцип открытости/закрытости, обеспечивая гибкость расширения программы. Ключевым моментом является то, что результаты вызова метода writeCode различаются в зависимости от текущего момента времени. Это похоже на изменение кода класса.

Расширение паттерна «Состояние»

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

state-order

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

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

  1. После оформления заказа клиент внезапно меняет своё решение и отказывается от покупки. В этом случае клиенту предоставляются два варианта: «отмена заказа» и «оплата». На этом этапе статус заказа — «ожидание оплаты».
  2. После оплаты клиент снова смотрит и видит более выгодное предложение от другого магазина. Деньги уже уплачены, поэтому клиенту предлагается «подать заявку на возврат средств». Статус заказа в этом случае — «ожидает отправки».
  3. Обработка заказов может быть очень сложной. Тем не менее, основная идея остаётся неизменной: разные состояния определяют ограниченный набор действий, которые могут выполнять клиенты, и последующее поведение заказа.

Давайте посмотрим на эти сценарии с помощью диаграммы:

state-exception-order

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

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

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

Использование паттерна «состояние» для управления статусами заказов позволяет высокоуровневым модулям никогда не беспокоиться о состоянии, что является хорошим примером принципов открытости/закрытости и единственной ответственности. Каждое состояние определяет, какие действия могут быть выполнены, и даже сложные бизнес-процессы становятся более управляемыми и разбитыми на мелкие части из-за разделения состояний (в отличие от традиционного оператора if else, где весь бизнес-процесс должен быть полностью протестирован заново, что не является чьим-либо мнением, а результатом тесной связи и монолитного дизайна, который невозможно избежать). Это значительно повышает эффективность разработки и тестирования и снижает затраты.

Хорошо, теперь о паттерне «конечный автомат» для обработки заказов. Реальные системы обработки заказов обычно управляются конечными автоматами.

Заключение 📚

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

Ключевые моменты паттерна «Состояние»:

  1. Среда приложения, которая служит входом в паттерн «Состояние» и отвечает за вызов текущего метода выполнения состояния.

  2. В интернете есть много сайтов с информацией на эту тему. Посмотрите, что нашлось в поиске ``` public class Versailles { public String getInterpretation1() { return "凡尔赛是法国巴黎的卫星城以及伊夫林省省会,曾是法兰西王朝的行政中心。"; }

    public String getInterpretation2() { return ""; }

    public String getInterpretation3() { return ""; }

    public String getInterpretation4() { return "凡尔赛文学,网络热词,指通过先抑后扬、自问自答或第三人称视角,不经意间露出"贵族生活的线索"."; }

    public String getInterpretation5() { return "啥???"; } }

public interface Visitor { void visit(Versailles versailles); }

class I implements Visitor { @Override public void visit(Versailles versailles) { System.out.println("lvgo 你知道凡尔赛吗?"); } }

class MyFriend implements Visitor { @Override public void visit(Versailles versailles) { System.out.println(versailles.getInterpretation5()); } }

class You implements Visitor { @Override public void visit(Versailles versailles) { System.out.println(versailles.getInterpretation4()); } }

class VisitorTest {

@Test
void visit() {
    Versailles versailles = new Versailles();

    System.out.println("lvgo 你知道凡尔赛吗?");
    versailles.accept(new I());

    System.out.println("\n狗哥 你知道凡尔赛吗?");
    versailles.accept(new MyFriend());
    
    System.out.println("\n你知道凡尔赛吗?");
    versailles.accept(new You());
}

}

lvgo 你知道凡尔赛吗? 凡尔赛是法国巴黎的卫星城以及伊夫林省省会,曾是法兰西王朝的行政中心。 《凡尔赛》是皮埃尔·苏勒执导的剧情片。 以法国路易十四为时代背景的电视剧。

狗哥 你知道凡尔赛吗? 啥???

你知道凡尔赛吗? 凡尔赛文学,网络热词,指通过先抑后扬、自问自答或第三人称视角,不经意间露出"贵族生活的线索"。


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

**Ты можешь понять «язык тела» ТА?**

Помнишь те фильмы, которые ты смотрел в прошлом? Или мемы?

|Интерпретатор-1.jpg|
|:--:|
|Интерпретатор-2.jpg|

Ты можешь понять язык тела Кэрри Цзо и его жены?

Честно говоря, я вообще не могу понять. Я просто смотрю на эту ситуацию и совершенно не понимаю, что она означает.

Но если я заранее объясню тебе правила?

**Рисунок Кэрри Цзо:**

1. Кэрри Цзо качает головой влево — вперёд!
2. Кэрри Цзо качает головой вправо — назад!

**Рисунок «жены»:**

1. «Жена» сидит на подушке — сердито!
2. «Жена» сидит на спинке стула — радостно!

Тогда, глядя на их «язык тела», ты сможешь понять? Если у меня есть эти определения, то я знаю:

— Кэрри Цзо имеет в виду «вперёд!» (предполагается, что он качает головой влево).
— «Жена» очень счастлива!

### Ещё раз о режиме интерпретатора

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

Определить язык: «язык тела».

Определить его грамматику: «качание головой», «положение сидя».

Определите интерпретатор: «правила».

Таким образом, мы можем понять ТА через этот интерпретатор.

**Учитывая «язык тела», определите «качание головы» или «положение сидя» и определите правило, тогда мы сможем объяснить фигуры Кэрри Цзо и «жены».**

### Диаграмма классов режима интерпретатора 📌

Это структура довольно проста, определяет интерфейс интерпретатора, а затем два конкретных интерпретатора:

1. Окончательный интерпретатор
2. Неокончательный интерпретатор
3. Среда

Эти два немного похожи на дочерние и листовые узлы комбинированного режима. Здесь `NonTerminalExpression` может быть несколько;

Здесь наиболее сложным является `Context` среда.

### Код 📃

Давайте посмотрим на код, чтобы реализовать «язык тела»:

```java
@Test
void getType() {
    EyeColor eyeColor = null;
    Context context = new Context("Кэрри Цзо качал головой влево | жена сидела на стуле");
    String content = context.getContent();
    String[] strings = content.split("\\|");
    for (int i = 0; i < strings.length; i++) {
        String string = strings[i];
        context.setContent(string);
        if (string.contains("Кэрри Цзо")) {
            eyeColor = new KeZhenE();
        } else if (string.contains("жена")) {
            eyeColor = new Wife();
        }
        assert eyeColor != null;
        eyeColor.interpreter(context);
    }
}
public class KeZhenE implements EyeColor {
    @Override
    public void interpreter(Context context) {
        if (context.getContent().contains("влево")) {
            System.out.println("Вперёд!");
        } else if (context.getContent().contains("вправо")) {
            System.out.println("Назад!");
        }
    }
}
public class Wife implements EyeColor {
    @Override
    public void interpreter(Context context) {
        if (context.getContent().contains("сидит на подушке")) {
            System.out.println("Сердито!");
        } else if (context.getContent().contains("спинка стула")) {
            System.out.println("Радостно!");
        }
    }
}
Вперёд!
Радостно!

Резюме 📚

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

На самом деле, режим интерпретатора похож на то, как мы сейчас используем высокоуровневый язык для разработки программного обеспечения, как компьютер узнает, что мы говорим? Это именно роль интерпретатора. Мы следуем определённым правилам (грамматике), чтобы написать код, а интерпретатор следует определённым правилам, чтобы перевести наш код в машинный код 01.

Для интерпретатора он берёт на себя сложные вещи сам, но как только появляются новые правила, вам приходится модифицировать сложный процесс анализа.

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

Дизайн-паттерны — это, как я изначально понимал, нечто вроде тайных знаний боевых искусств, где каждый приём и движение чётко определены. Если вы пропустите один приём или движение, то можете оказаться в невыгодном положении, подобно герою Сунь Укуну из романа «Путешествие на Запад», который в конце концов сразился с Чжан Уцзи и проиграл, потому что ему не хватило одного движения из восемнадцати ладоней покорения дракона. Позже появились безымянные техники боя, такие как безымянный стиль меча и стиль меча Тайцзицюань Чжан Саньфэна, которые подчёркивают гибкость и адаптивность, а не жёсткое следование определённым шаблонам. Истинная суть боевых искусств заключается в их гибкости и приспособляемости, а дизайн-паттерны являются своего рода тайными знаниями в мире технологий, и сейчас они доступны каждому.

Научно-популярная беседа

Закон сохранения сложности был предложен Ларри Теслером в 1984 году и также известен как закон Теслера. Согласно этому закону, каждое приложение имеет свою неотъемлемую сложность, которую невозможно упростить. Эта сложность присутствует как на этапе разработки продукта, так и во время взаимодействия пользователя с продуктом, и её нельзя устранить по нашему желанию, можно только попытаться регулировать и балансировать.

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

Как было указано в Википедии:

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

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

[Что такое RPC? В чём преимущества удалённого вызова?]

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

2018-01-15 09:04:03

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

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

Разговор о собственном идеальном «знании»

Как делиться знаниями

В выходные я посетил библиотеку и отправился в отдел компьютерных технологий в поисках книг для чтения. Я наткнулся на книгу «Основы облачных вычислений для начинающих», и я обнаружил, что у меня и автора очень похожие взгляды. Автор обсуждает, как термин «облачные вычисления» стал настолько перегруженным различными определениями, что люди начали терять его первоначальное значение. Он начинает с основ облачных вычислений и убирает все слои упаковки, позволяя читателям понять, что такое облачные вычисления (через собственное мышление), вместо того чтобы дать им сложное определение, которое создаёт впечатление чего-то волшебного. По крайней мере, после прочтения я смог увидеть сквозь маркетинговые или прикладные разговоры о «облачных вычислениях» и мог бы задать вопросы, если бы кто-то сказал мне, как конкретное приложение использует «облачные вычисления».

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

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

timg.gif

Как изучать знания

Знания в интернете многочисленны, и большая часть контента представляет собой личное понимание автора. Некоторые используют «шаблонные» методы написания, не заботясь о качестве содержания. Как читатели, мы должны обладать способностью различать реальное и нереальное, абстрагироваться от деталей и видеть суть. А не «два зайца рядом бегут, как же нам их различить». Приведу пример, который не совсем уместен, но всё же: как вы различаете, мужчина перед вами или женщина, независимо от того, во что он или она одеты, накрашены или нет, сделали пластическую операцию или нет, длинные у них волосы или короткие, и так далее.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/mifengjun-java-design-patterns.git
git@api.gitlife.ru:oschina-mirror/mifengjun-java-design-patterns.git
oschina-mirror
mifengjun-java-design-patterns
mifengjun-java-design-patterns
main