W3docs

Введение в шаблоны проектирования Java

Введение в шаблоны проектирования Java — что это такое и как использовать наиболее распространённые из них.

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

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

Шаблоны стали популярны благодаря книге 1994 года Design Patterns «Банды четырёх», в которой описано 23 шаблона. Не нужно знать все 23, чтобы начать. Несколько из них, применённых в нужный момент, делают код проще в изменении без риска что-то сломать.

Три семейства

Классический каталог делит шаблоны на три группы по тому, с чем они помогают:

СемействоЗадачаПримеры
ПорождающиеКак создаются объектыSingleton, Factory, Builder
СтруктурныеКак объекты компонуютсяAdapter, Decorator, Facade
ПоведенческиеКак объекты взаимодействуютStrategy, Observer, Iterator

В самом Java таких шаблонов множество. StringBuilder — это Builder, Iterator — шаблон Iterator, а java.util.logging использует Singleton. Вы уже применяли шаблоны, не называя их по именам.

Strategy: замена алгоритма

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

interface DiscountStrategy {
    double apply(double price);
}

class PercentOff implements DiscountStrategy {
    private final double percent;
    PercentOff(double percent) { this.percent = percent; }
    public double apply(double price) { return price * (1 - percent / 100); }
}

Класс контекста хранит один DiscountStrategy и делегирует ему работу вместо цепочки if/switch. Добавление нового вида скидки означает добавление класса — без изменения существующего кода. (Полный контекст Checkout, а также варианты NoDiscount и FlatOff представлены в запускаемом примере ниже.)

Factory: централизованное создание

Factory — это единственный метод (или класс), отвечающий за выбор конкретного типа для создания экземпляра. Вызывающий код запрашивает объект по описанию и получает его, реализующий интерфейс, не зная точного класса.

static DiscountStrategy forCustomer(String tier) {
    return switch (tier) {
        case "gold"   -> new PercentOff(20);
        case "silver" -> new PercentOff(10);
        default       -> new NoDiscount();
    };
}

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

Singleton: ровно один экземпляр

Singleton гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему. В современном Java самый простой и потокобезопасный способ — использовать enum: JVM гарантирует, что его константы создаются ровно один раз. Варианты с ленивой инициализацией и двойной проверкой блокировки рассматриваются в разделе Шаблон Singleton.

enum Config {
    INSTANCE;
    private final String env = "production";
    public String env() { return env; }
}

// usage
String e = Config.INSTANCE.env();

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

Когда не стоит применять шаблон

Шаблоны добавляют структуру, а структура имеет свою цену. switch с двумя вариантами не нуждается в шаблоне Strategy; класс, создаваемый один раз, не нуждается в Factory. Обращайтесь к шаблону тогда, когда ощущаете ту боль, которую он решает — дублирующиеся ветвления, разбросанные вызовы new, запутанные зависимости объектов, — а не прежде. Чрезмерное применение шаблонов порождает код, который сложнее читать, чем исходная задача.

Strategy и Factory вместе

Приведённый ниже запускаемый пример объединяет два шаблона. DiscountStrategy — это интерфейс Strategy с тремя реализациями; forCustomer — Factory, которая выбирает одну из них. Контекст Checkout делегирует работу той стратегии, которую хранит, поэтому его код не изменяется по мере смены ценового поведения.

java— editable, runs on the server

Что следует вынести из запуска:

  • Каждый уровень выводит разную итоговую сумму — regular остаётся $100.00, silver снижается до $90.00, gold до $80.00, coupon до $95.00 — хотя Checkout.total вызывает один и тот же единственный метод.
  • Контекст Checkout никогда не ветвится по уровню сам; он просто делегирует работу той DiscountStrategy, которую хранит в данный момент.
  • Factory forCustomer — единственное место, которое знает, какой конкретный класс соответствует какому уровню, поэтому логика выбора сосредоточена в одном месте.
  • Смена поведения — это просто setStrategy(...) во время выполнения: добавление четвёртой скидки потребует нового класса и одного case в фабрике без каких-либо изменений в Checkout.
  • Последние строки подтверждают активную стратегию: после повторного выбора gold политика показывает 20.0% off, а итог равен $80.00, что доказывает: контекст отражает последнюю установленную стратегию.

Практика

Практика
Какую задачу решает шаблон Strategy?
Какую задачу решает шаблон Strategy?
Was this page helpful?