Зарегистрируйтесь, чтобы продолжить обучение

Паттерны (продолжение) Java: ООП

Наблюдатель (Observer)

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

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

Интерфейсы, которые должны реализовывать издатель и наблюдатели:

public interface Observable {
    void subscribe(Observer observer);
    void unsubscribe(Observer observer);
    void notifyAllSubscribers();
}

public interface Observer {
    void handleEvent();
}

Классы издателя и наблюдателя:

public class Samplebserver implements Observer {
    @Override
    public void handleEvent() {
        System.out.println("event happened");
    }
}

public class SamplePublisher implements Observable {
    private final List<Observer> subscribers = new ArrayList<>();

    @Override
    public void subscribe(Observer observer) {
        subscribers.add(observer);
    }

    @Override
    public void unsubscribe(Observer observer) {
        subscribers.remove(observer);
    }

    @Override
    public void notifyAllSubscribers() {
        subscribers.forEach(Observer::handleEvent);
    }
}

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

public class Main {
    public static void main(String[] args) {
        var obs1 = new SampleObserver();
        var obs2 = new SampleObserver();
        var obs3 = new SampleObserver();

        var publisher = new SamplePublisher();

        publisher.subscribe(obs1);
        publisher.subscribe(obs2);
        publisher.subscribe(obs3);

        publisher.notifyAllSubscribers();
    }
}

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

Стратегия

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

@ALlArgsConstructor
public class Cart {

    private int amount;

    public int getAmount() {
        return amount;
    }
}

Интерфейс и классы стратегий

public interface DiscountStrategy {
    boolean isApplicable(Cart cart);
    int applyDiscount(Cart cart);
}

public class NewYearStrategy implements DiscountStrategy {
    public boolean isApplicable(Cart cart) {
        return LocalDate.now().getDay0fYear() < 10;
    }

    public int applyDiscount(Cart cart) {
        return (int) (cart.getAmount() * 0.90);
    }
}

public class DefaultStrategy implements DiscountStrategy {

    public boolean isApplicable (Cart cart) {
        return false;
    }

    public int applyDiscount(Cart cart) {
        return cart.getAmount();
    }
}


public class BigAmountStrategy implements DiscountStrategy {
    public boolean isApplicable(Cart cart) {
        return cart.getAmount() > 10000;
    }

    public int applyDiscount(Cart cart) {
        return (int) (cart.getAmount() * 0.95);
    }
}

Класс для выбора стратегий в зависимости от текущих условий

public class StrategySelector {

    private final List<DiscountStrategy> discountStrategies = List.of(
        new NewYearStrategy(),
        new BigAmountStrategy()
    );

    private final DiscountStrategy defaultStrategy = new DefaultStrategy();

    public DiscountStrategy getDiscountStrategy (Cart cart) {

        return discountStrategies.stream()
            .filter(s -> s.isApplicable(cart))
            .findFirst()
            .orElse(defaultStrategy);
    }
}

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

public class Main {
    public static void main(String[] args) {
        var cart = new Cart(10000);
        var selector = new StrategySelector();
        DiscountStrategy discountStrategy = selector.getDiscountStrategy(cart); // DefaultStrategy
        System.out.println(discountStrategy.applyDiscount()); // => 10000

        var cart1 = new Cart(10001);
        var selector = new StrategySelector();
        DiscountStrategy discountStrategy = selector.getDiscountStrategy(cart1); // BigAmountStrategy
        System.out.println(discountStrategy.applyDiscount()); // => 9500
    }
}

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

Состояние

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

Отличие паттерна "Состояние" от паттерна "Стратегия" заключается в том, что "Состояние" фокусируется на изменении поведения объекта в зависимости от его внутреннего состояния, тогда как "Стратегия" фокусируется на изменении алгоритма или поведения объекта в зависимости от внешних факторов.

Интерфейс и классы состояний

public interface DocumentState {
    void proceed();
    void reject();
}


@AllArgsConstructor
public class Draft implements DocumentState {
    private Document context;

    @Override
    public void proceed() {
        System.out.println("proceed draft document");
        context.setState(new New(this.context));
    }

    @Override
    public void reject() {
        System.out.println("not applicable");
    }
}

@AllArgsConstructor
public class New implements DocumentState {

    private Document context;

    @Override
    public void proceed() {
        System.out.println("proceed new document");
        context.setState(new Saved(context));
    }

    @Override
    public void reject() {
        System.out.println("Reject new document");
        context.setState(new Draft(context));
    }
}

@AllArgsConstructor
public class Saved implements DocumentState {

    private Document context;

    @Override
    public void proceed() {
        System.out.println("Document was saved");
    }

    @Override
    public void reject() {
        System.out.println("Reject Saved document");
        context.setState(new New(context));
    }
}

Основной класс - контекст

public class Document implements DocumentState {
    private DocumentState state;

    public Document() {
        state = new Draft(this);
    }

    public void setState(DocumentState state) {
        this.state = state;
    }

    @Override
    public void proceed() {
        state.proceed();
    }

    @Override
    public void reject() {
        state.reject();
    }
}

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

public class Main {
    public static void main(String[] args) {
        var document = new Document();
        document.proceed();
        document.proceed();
        document.reject();
        document.reject();
        document.reiect();
    }
}

Дополнительные материалы

  1. Паттерны без привязки к языку

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

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

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
Программирование на Java, Разработка веб-приложений и микросервисов используя Spring Boot, проектирование REST API
10 месяцев
с нуля
Старт 13 марта

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»
Изображение Тото

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