текст перекладу
Шаблон проєктування State є одним з найбільш корисних поведінкових шаблонів, визначених у шаблонах проєктування Gang of Four (GoF). Він дозволяє об'єкту змінювати свою поведінку, коли його внутрішній стан змінюється, що дає можливість змінити вигляд об'єкта, ніби він змінює свій клас. Цей шаблон допомагає уникнути складної умовної логіки, яка інакше була б розкидана по коду.
У цій статті ми детально розглянемо шаблон проєктування State, охопивши його ключові концепції, переваги та реальні приклади на Java, щоб показати, як він спрощує логіку, залежну від стану, у вашому коді.
Що таке шаблон проєктування State?
Шаблон State дозволяє об'єкту змінювати свою поведінку в залежності від його поточного стану. Замість використання великих блоків if-else або switch-case для обробки різних станів, поведінка інкапсулюється в класи станів. Контекст делегує поведінку поточному стану.
Ключові концепції шаблону State:
- Інтерфейс State: Визначає загальну поведінку для всіх станів.
- Конкретні стани: Реалізують поведінку для кожного стану.
- Контекст: Зберігає посилання на поточний стан і делегує запити поточному об'єкту стану.
Коли використовувати шаблон проєктування State:
- Коли поведінка об'єкта залежить від його стану.
- Коли у вас є багато умовних операторів if-else або switch, які змінюють поведінку залежно від стану.
- Коли поведінка, що залежить від стану, змінюється часто або має бути легко розширювана.
Реальний приклад: Система світлофора
Розглянемо систему світлофора як приклад. Світлофор може бути в одному з трьох станів:
- Червоне світло: Автомобілі повинні зупинитися.
- Зелене світло: Автомобілі можуть їхати.
- Жовте світло: Автомобілі повинні готуватися до зупинки.
Крок 1: Визначте інтерфейс State
Інтерфейс TrafficLightState визначає загальну поведінку для всіх станів світлофора.
interface TrafficLightState {
void handleRequest(TrafficLightContext context);
}
Крок 2: Реалізуйте конкретні стани
1. Стан червоного світла
class RedLightState implements TrafficLightState {
@Override
public void handleRequest(TrafficLightContext context) {
System.out.println("Червоне світло: Автомобілі повинні зупинитися.");
context.setState(new GreenLightState()); // Перехід на зелений після червоного
}
}
2. Стан зеленого світла
class GreenLightState implements TrafficLightState {
@Override
public void handleRequest(TrafficLightContext context) {
System.out.println("Зелене світло: Автомобілі можуть їхати.");
context.setState(new YellowLightState()); // Перехід на жовтий після зеленого
}
}
Крок 3: Створіть клас Context
Клас TrafficLightContext містить посилання на поточний стан і делегує запити цьому стану.
class TrafficLightContext {
private TrafficLightState currentState;
public TrafficLightContext() {
currentState = new RedLightState(); // Початковий стан за замовчуванням
}
public void setState(TrafficLightState state) {
this.currentState = state;
}
public void changeLight() {
currentState.handleRequest(this);
}
}
Крок 4: Код клієнта
public class StatePatternDemo {
public static void main(String[] args) {
TrafficLightContext trafficLight = new TrafficLightContext();
for (int i = 0; i < 6; i++) { // Зміна світла кілька разів
trafficLight.changeLight();
System.out.println();
}
}
}
Виведення:
Червоне світло: Автомобілі повинні зупинитися.
Зелене світло: Автомобілі можуть їхати.
Жовте світло: Автомобілі повинні готуватися до зупинки.
Червоне світло: Автомобілі повинні зупинитися.
Зелене світло: Автомобілі можуть їхати.
Жовте світло: Автомобілі повинні готуватися до зупинки.
У цьому прикладі, контекст OrderContext проходить через різні стани (Placed, Shipped, Delivered, Cancelled). Поведінка в кожному стані інкапсульована, що забезпечує легкість підтримки та розширення станів і поведінки.
текст перекладу
Реальний приклад: Стан замовлення в системі електронної комерції
У системі електронної комерції замовлення може проходити через кілька станів: Placed, Shipped, Delivered, Cancelled. Кожен стан має специфічні дії, які можуть бути виконані.
Крок 1: Визначте інтерфейс State
interface OrderState {
void next(OrderContext context);
void cancel(OrderContext context);
}
Крок 2: Реалізуйте конкретні стани
1. Стан замовлення "Placed"
class OrderPlacedState implements OrderState {
@Override
public void next(OrderContext context) {
System.out.println("Замовлення було розміщене. Переходимо до стану Shipped.");
context.setState(new OrderShippedState());
}
@Override
public void cancel(OrderContext context) {
System.out.println("Замовлення було скасоване.");
context.setState(new OrderCancelledState());
}
}
2. Стан замовлення "Shipped"
class OrderShippedState implements OrderState {
@Override
public void next(OrderContext context) {
System.out.println("Замовлення було відправлено. Переходимо до стану Delivered.");
context.setState(new OrderDeliveredState());
}
@Override
public void cancel(OrderContext context) {
System.out.println("Неможливо скасувати. Замовлення вже відправлено.");
}
}
3. Стан замовлення "Delivered"
class OrderDeliveredState implements OrderState {
@Override
public void next(OrderContext context) {
System.out.println("Замовлення вже доставлено.");
}
@Override
public void cancel(OrderContext context) {
System.out.println("Неможливо скасувати. Замовлення вже доставлено.");
}
}
4. Стан замовлення "Cancelled"
class OrderCancelledState implements OrderState {
@Override
public void next(OrderContext context) {
System.out.println("Неможливо продовжити. Замовлення скасоване.");
}
@Override
public void cancel(OrderContext context) {
System.out.println("Замовлення вже скасоване.");
}
}
Крок 3: Створіть клас Context
class OrderContext {
private OrderState currentState;
public OrderContext() {
currentState = new OrderPlacedState(); // Початковий стан
}
public void setState(OrderState state) {
this.currentState = state;
}
public void proceedToNext() {
currentState.next(this);
}
public void cancelOrder() {
currentState.cancel(this);
}
}
Крок 4: Код клієнта
public class ECommerceStatePatternDemo {
public static void main(String[] args) {
OrderContext order = new OrderContext();
System.out.println("Процес замовлення:");
order.proceedToNext(); // Переходить до Shipped
order.proceedToNext(); // Переходить до Delivered
order.cancelOrder(); // Спроба скасувати після доставки
System.out.println("\nНовий процес замовлення:");
OrderContext newOrder = new OrderContext();
newOrder.cancelOrder(); // Скасування одразу після розміщення
}
}
Виведення:
Процес замовлення:
Замовлення було розміщене. Переходимо до стану Shipped.
Замовлення було відправлено. Переходимо до стану Delivered.
Неможливо скасувати. Замовлення вже доставлено.
Новий процес замовлення:
Замовлення було скасоване.
У цьому прикладі, контекст OrderContext переходить через різні стани (Placed, Shipped, Delivered, Cancelled). Поведінка в кожному стані інкапсульована, що забезпечує легкість підтримки та розширення станів і поведінки.
Переваги шаблону проєктування State:
- Покращує підтримку коду: Поведінка, що залежить від стану, інкапсульована в окремих класах, що робить її легшою для розширення або модифікації.
- Позбувається складної умовної логіки: Уникає довгих операторів if-else або switch для управління поведінкою, що залежить від стану.
- Відповідає принципу відкритості/закритості (Open/Closed Principle): Нові стани можна додавати без змін в існуючий код.
- Спрощує переходи між станами: Переходи між станами обробляються самими об'єктами стану.
текст перекладу
Збільшення кількості класів: Кожен стан вимагає створення нового класу, що може призвести до великої кількості класів у складних системах. - Навантаження на пам'ять: Оскільки кожен стан представлений окремим об'єктом, це може призвести до деякого навантаження на пам'ять.
Висновок:
Шаблон проєктування State є потужним інструментом для керування складною поведінкою, яка залежить від стану, у Java-додатках. Інкапсуляція логіки, що залежить від стану, в окремих класах робить ваш код чистішим, зрозумілішим і легшим для підтримки. Незалежно від того, чи ви реалізуєте систему світлофора, систему керування замовленнями чи сторінки станів ігрового персонажа, шаблон State допомагає створювати гнучкі системи, що відповідають принципам хорошого проєктування програмного забезпечення.
Перекладено з: Mastering the State Design Pattern in Java: A Detailed Guide with Examples