Behavioral Patterns
Sau bài này, bạn sẽ:
- Hiểu các Behavioral Patterns chính: Strategy, Observer, Template Method, Command, Iterator, State
- Biết cách Strategy pattern thay thế if-else chains bằng composition
- Nắm được Observer pattern — publish-subscribe cho event handling
- Áp dụng Template Method cho algorithm skeleton với customizable steps
- Phân biệt Command, Strategy, và State patterns — 3 patterns dễ nhầm lẫn
Bài trước: Structural Patterns — Đã học về Adapter, Decorator, Proxy, Facade. Bài này sẽ tìm hiểu Behavioral Patterns — cách objects giao tiếp và phân chia trách nhiệm.
Behavioral Patterns tập trung vào giao tiếp giữa các objects và cách phân chia trách nhiệm. Các patterns này giúp quản lý thuật toán, quan hệ và workflow giữa các objects một cách linh hoạt.
1. Strategy Pattern
Mục đích
Định nghĩa một family of algorithms, đóng gói từng algorithm thành class riêng, và làm chúng có thể thay thế lẫn nhau (interchangeable). Strategy cho phép algorithm thay đổi độc lập với client sử dụng nó.
Vấn đề
// Anti-pattern: If-else hell
public class PaymentProcessor {
public void processPayment(String type, double amount) {
if (type.equals("credit")) {
// Credit card logic
System.out.println("Processing credit card: $" + amount);
} else if (type.equals("paypal")) {
// PayPal logic
System.out.println("Processing PayPal: $" + amount);
} else if (type.equals("crypto")) {
// Crypto logic
System.out.println("Processing crypto: $" + amount);
}
// Thêm payment method mới phải sửa code này! (vi phạm Open/Closed)
}
}
Giải pháp: Strategy Pattern
// Strategy interface
interface PaymentStrategy {
void pay(double amount);
}
// Concrete strategies
class CreditCardStrategy implements PaymentStrategy {
private String cardNumber;
private String cvv;
public CreditCardStrategy(String cardNumber, String cvv) {
this.cardNumber = cardNumber;
this.cvv = cvv;
}
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using Credit Card ending in " +
cardNumber.substring(cardNumber.length() - 4));
}
}
class PayPalStrategy implements PaymentStrategy {
private String email;
public PayPalStrategy(String email) {
this.email = email;
}
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using PayPal account: " + email);
}
}
class CryptoStrategy implements PaymentStrategy {
private String walletAddress;
public CryptoStrategy(String walletAddress) {
this.walletAddress = walletAddress;
}
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using Crypto wallet: " + walletAddress);
}
}
// Context class
class ShoppingCart {
private List<String> items = new ArrayList<>();
private PaymentStrategy paymentStrategy;
public void addItem(String item) {
items.add(item);
}
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public void checkout(double amount) {
if (paymentStrategy == null) {
throw new IllegalStateException("Payment strategy not set");
}
System.out.println("Items: " + items);
paymentStrategy.pay(amount);
}
}
// Sử dụng - có thể đổi strategy lúc runtime!
ShoppingCart cart = new ShoppingCart();
cart.addItem("Laptop");
cart.addItem("Mouse");
// Thanh toán bằng Credit Card
cart.setPaymentStrategy(new CreditCardStrategy("1234-5678-9012-3456", "123"));
cart.checkout(1500.00);
// Đổi sang PayPal
cart.setPaymentStrategy(new PayPalStrategy("[email protected]"));
cart.checkout(1500.00);
// Đổi sang Crypto
cart.setPaymentStrategy(new CryptoStrategy("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"));
cart.checkout(1500.00);
Ví dụ 2: Sorting Strategy
interface SortStrategy {
void sort(int[] numbers);
}
class BubbleSortStrategy implements SortStrategy {
@Override
public void sort(int[] numbers) {
System.out.println("Sorting using Bubble Sort");
// Bubble sort implementation
for (int i = 0; i < numbers.length - 1; i++) {
for (int j = 0; j < numbers.length - i - 1; j++) {
if (numbers[j] > numbers[j + 1]) {
int temp = numbers[j];
numbers[j] = numbers[j + 1];
numbers[j + 1] = temp;
}
}
}
}
}
class QuickSortStrategy implements SortStrategy {
@Override
public void sort(int[] numbers) {
System.out.println("Sorting using Quick Sort");
quickSort(numbers, 0, numbers.length - 1);
}
private void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
private int partition(int[] arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
}
class Sorter {
private SortStrategy strategy;
public void setStrategy(SortStrategy strategy) {
this.strategy = strategy;
}
public void sort(int[] numbers) {
strategy.sort(numbers);
}
}
// Sử dụng
int[] data = {5, 2, 9, 1, 7, 6};
Sorter sorter = new Sorter();
// Nhỏ hơn 10 elements → Bubble Sort
if (data.length < 10) {
sorter.setStrategy(new BubbleSortStrategy());
} else {
sorter.setStrategy(new QuickSortStrategy());
}
sorter.sort(data);
System.out.println(Arrays.toString(data));
- Có nhiều variants of an algorithm (sorting, payment, compression)
- Muốn tránh if-else/switch statements phức tạp
- Cần đổi algorithm at runtime
- Muốn hide implementation details của algorithms
2. Observer Pattern
Mục đích
Định nghĩa one-to-many dependency giữa objects: khi một object thay đổi state, tất cả objects phụ thuộc vào nó được thông báo và cập nhật tự động.
Còn gọi là: Publish-Subscribe pattern.
Ví dụ 1: Weather Station
import java.util.ArrayList;
import java.util.List;
// Observer interface
interface Observer {
void update(float temperature, float humidity, float pressure);
}
// Subject interface
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// Concrete Subject
class WeatherStation implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherStation() {
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
System.out.println("Observer registered");
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
System.out.println("Observer removed");
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
// Khi data thay đổi, notify tất cả observers
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
notifyObservers();
}
}
// Concrete Observers
class CurrentConditionsDisplay implements Observer {
private float temperature;
private float humidity;
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("Current conditions: " + temperature + "°C, " +
humidity + "% humidity");
}
}
class StatisticsDisplay implements Observer {
private List<Float> temperatures = new ArrayList<>();
@Override
public void update(float temperature, float humidity, float pressure) {
temperatures.add(temperature);
display();
}
public void display() {
float sum = 0;
for (float temp : temperatures) {
sum += temp;
}
float avg = sum / temperatures.size();
System.out.println("Avg/Max/Min temperature: " + avg + "/" +
temperatures.stream().max(Float::compare).orElse(0f) + "/" +
temperatures.stream().min(Float::compare).orElse(0f));
}
}
class ForecastDisplay implements Observer {
private float currentPressure = 1013.0f;
private float lastPressure;
@Override
public void update(float temperature, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}
public void display() {
System.out.print("Forecast: ");
if (currentPressure > lastPressure) {
System.out.println("Improving weather on the way!");
} else if (currentPressure == lastPressure) {
System.out.println("More of the same");
} else {
System.out.println("Watch out for cooler, rainy weather");
}
}
}
// Sử dụng
WeatherStation weatherStation = new WeatherStation();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay();
StatisticsDisplay statisticsDisplay = new StatisticsDisplay();
ForecastDisplay forecastDisplay = new ForecastDisplay();
weatherStation.registerObserver(currentDisplay);
weatherStation.registerObserver(statisticsDisplay);
weatherStation.registerObserver(forecastDisplay);
// Update measurements → tất cả displays tự động update
weatherStation.setMeasurements(25.5f, 65.0f, 1013.0f);
System.out.println();
weatherStation.setMeasurements(27.0f, 70.0f, 1012.0f);
System.out.println();
weatherStation.setMeasurements(23.0f, 90.0f, 1010.0f);
Ví dụ 2: Newsletter Subscription
interface Subscriber {
void update(String article);
}
class EmailSubscriber implements Subscriber {
private String email;
public EmailSubscriber(String email) {
this.email = email;
}
@Override
public void update(String article) {
System.out.println("Email to " + email + ": New article published - " + article);
}
}
class SMSSubscriber implements Subscriber {
private String phoneNumber;
public SMSSubscriber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
@Override
public void update(String article) {
System.out.println("SMS to " + phoneNumber + ": New article - " + article);
}
}
class Newsletter {
private List<Subscriber> subscribers = new ArrayList<>();
public void subscribe(Subscriber subscriber) {
subscribers.add(subscriber);
}
public void unsubscribe(Subscriber subscriber) {
subscribers.remove(subscriber);
}
public void publishArticle(String article) {
System.out.println("\n[Newsletter] Publishing: " + article);
for (Subscriber subscriber : subscribers) {
subscriber.update(article);
}
}
}
// Sử dụng
Newsletter newsletter = new Newsletter();
newsletter.subscribe(new EmailSubscriber("[email protected]"));
newsletter.subscribe(new EmailSubscriber("[email protected]"));
newsletter.subscribe(new SMSSubscriber("+1234567890"));
newsletter.publishArticle("Design Patterns in Java");
newsletter.publishArticle("Spring Boot Best Practices");
- Event handling: UI events, user actions
- Real-time updates: Stock prices, weather, notifications
- Decoupling: Subject không cần biết chi tiết về observers
- One-to-many: Một thay đổi ảnh hưởng đến nhiều objects
3. Command Pattern
Mục đích
Đóng gói request thành object, cho phép:
- Parameterize clients với các requests khác nhau
- Queue hoặc log requests
- Hỗ trợ undo/redo operations
Ví dụ 1: Remote Control
// Command interface
interface Command {
void execute();
void undo();
}
// Receiver (thực hiện công việc thực sự)
class Light {
private String location;
public Light(String location) {
this.location = location;
}
public void on() {
System.out.println(location + " light is ON");
}
public void off() {
System.out.println(location + " light is OFF");
}
}
// Concrete Commands
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
// Receiver 2
class Stereo {
public void on() {
System.out.println("Stereo is ON");
}
public void off() {
System.out.println("Stereo is OFF");
}
public void setCD() {
System.out.println("CD mode");
}
public void setVolume(int volume) {
System.out.println("Volume set to " + volume);
}
}
class StereoOnCommand implements Command {
private Stereo stereo;
public StereoOnCommand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void execute() {
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
@Override
public void undo() {
stereo.off();
}
}
// Invoker (gọi commands)
class RemoteControl {
private Command[] onCommands;
private Command[] offCommands;
private Command undoCommand;
public RemoteControl() {
onCommands = new Command[7]; // 7 buttons
offCommands = new Command[7];
// No-op command (default)
Command noCommand = new Command() {
public void execute() {}
public void undo() {}
};
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonPressed(int slot) {
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
public void offButtonPressed(int slot) {
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
public void undoButtonPressed() {
undoCommand.undo();
}
}
// Sử dụng
RemoteControl remote = new RemoteControl();
Light livingRoomLight = new Light("Living Room");
Light kitchenLight = new Light("Kitchen");
Stereo stereo = new Stereo();
// Set commands
remote.setCommand(0,
new LightOnCommand(livingRoomLight),
new LightOffCommand(livingRoomLight));
remote.setCommand(1,
new LightOnCommand(kitchenLight),
new LightOffCommand(kitchenLight));
remote.setCommand(2,
new StereoOnCommand(stereo),
new Command() { // Anonymous off command
public void execute() { stereo.off(); }
public void undo() { stereo.on(); }
});
// Test
remote.onButtonPressed(0); // Living Room light ON
remote.offButtonPressed(0); // Living Room light OFF
remote.undoButtonPressed(); // Undo → Living Room light ON
remote.onButtonPressed(2); // Stereo ON, CD mode, Volume 11
remote.undoButtonPressed(); // Undo → Stereo OFF
Ví dụ 2: Text Editor (Undo/Redo)
interface TextCommand {
void execute();
void undo();
}
class TextEditor {
private StringBuilder text = new StringBuilder();
public void write(String str) {
text.append(str);
}
public void delete(int length) {
int start = text.length() - length;
text.delete(start, text.length());
}
public String getText() {
return text.toString();
}
}
class WriteCommand implements TextCommand {
private TextEditor editor;
private String textToWrite;
public WriteCommand(TextEditor editor, String textToWrite) {
this.editor = editor;
this.textToWrite = textToWrite;
}
@Override
public void execute() {
editor.write(textToWrite);
}
@Override
public void undo() {
editor.delete(textToWrite.length());
}
}
class CommandManager {
private Stack<TextCommand> history = new Stack<>();
private Stack<TextCommand> redoStack = new Stack<>();
public void executeCommand(TextCommand command) {
command.execute();
history.push(command);
redoStack.clear(); // Clear redo stack after new command
}
public void undo() {
if (!history.isEmpty()) {
TextCommand command = history.pop();
command.undo();
redoStack.push(command);
}
}
public void redo() {
if (!redoStack.isEmpty()) {
TextCommand command = redoStack.pop();
command.execute();
history.push(command);
}
}
}
// Sử dụng
TextEditor editor = new TextEditor();
CommandManager manager = new CommandManager();
manager.executeCommand(new WriteCommand(editor, "Hello "));
System.out.println(editor.getText()); // Hello
manager.executeCommand(new WriteCommand(editor, "World"));
System.out.println(editor.getText()); // Hello World
manager.undo();
System.out.println(editor.getText()); // Hello
manager.redo();
System.out.println(editor.getText()); // Hello World
- Cần undo/redo functionality
- Queue operations (task scheduler, job queue)
- Log requests (audit trail, transaction log)
- Macro commands (combine multiple commands)
4. Template Method Pattern
Mục đích
Định nghĩa skeleton của thuật toán trong method, để các subclass override các bước cụ thể mà không thay đổi cấu trúc thuật toán.
Ví dụ 1: Beverage Preparation
// Abstract class với template method
abstract class Beverage {
// Template method - final để subclass không override
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) { // Hook method
addCondiments();
}
}
// Common methods
private void boilWater() {
System.out.println("Boiling water");
}
private void pourInCup() {
System.out.println("Pouring into cup");
}
// Abstract methods - subclass must implement
protected abstract void brew();
protected abstract void addCondiments();
// Hook method - subclass có thể override (optional)
protected boolean customerWantsCondiments() {
return true; // Default
}
}
// Concrete classes
class Tea extends Beverage {
@Override
protected void brew() {
System.out.println("Steeping the tea");
}
@Override
protected void addCondiments() {
System.out.println("Adding lemon");
}
}
class Coffee extends Beverage {
@Override
protected void brew() {
System.out.println("Dripping coffee through filter");
}
@Override
protected void addCondiments() {
System.out.println("Adding sugar and milk");
}
@Override
protected boolean customerWantsCondiments() {
// Ask user
return true; // Simplified
}
}
// Sử dụng
System.out.println("Making tea:");
Beverage tea = new Tea();
tea.prepareRecipe();
System.out.println("\nMaking coffee:");
Beverage coffee = new Coffee();
coffee.prepareRecipe();
Output:
Making tea:
Boiling water
Steeping the tea
Pouring into cup
Adding lemon
Making coffee:
Boiling water
Dripping coffee through filter
Pouring into cup
Adding sugar and milk
Ví dụ 2: Data Mining
abstract class DataMiner {
// Template method
public final void mine(String path) {
openFile(path);
extractData();
parseData();
analyzeData();
sendReport();
closeFile();
}
// Common operations
private void openFile(String path) {
System.out.println("Opening file: " + path);
}
private void closeFile() {
System.out.println("Closing file");
}
// Abstract methods - subclass implements
protected abstract void extractData();
protected abstract void parseData();
// Hook methods - có implementation default
protected void analyzeData() {
System.out.println("Analyzing data");
}
protected void sendReport() {
System.out.println("Sending report");
}
}
class PDFDataMiner extends DataMiner {
@Override
protected void extractData() {
System.out.println("Extracting data from PDF");
}
@Override
protected void parseData() {
System.out.println("Parsing PDF data");
}
}
class CSVDataMiner extends DataMiner {
@Override
protected void extractData() {
System.out.println("Extracting data from CSV");
}
@Override
protected void parseData() {
System.out.println("Parsing CSV data");
}
@Override
protected void analyzeData() {
System.out.println("Performing statistical analysis on CSV data");
}
}
// Sử dụng
DataMiner pdfMiner = new PDFDataMiner();
pdfMiner.mine("report.pdf");
DataMiner csvMiner = new CSVDataMiner();
csvMiner.mine("data.csv");
- Template Method: Sử dụng inheritance, subclass override methods
- Strategy: Sử dụng composition, inject strategy object
- Template Method ít linh hoạt hơn nhưng đơn giản hơn cho fixed algorithm structure
5. Iterator Pattern
Mục đích
Cung cấp cách truy cập tuần tự các phần tử của aggregate object mà không lộ cấu trúc bên trong.
Java Iterator
Java đã có sẵn Iterator interface:
import java.util.*;
// Custom collection
class BookCollection implements Iterable<String> {
private List<String> books = new ArrayList<>();
public void addBook(String book) {
books.add(book);
}
@Override
public Iterator<String> iterator() {
return books.iterator();
}
}
// Sử dụng
BookCollection collection = new BookCollection();
collection.addBook("Design Patterns");
collection.addBook("Clean Code");
collection.addBook("Effective Java");
// For-each (internally uses Iterator)
for (String book : collection) {
System.out.println(book);
}
// Explicit Iterator
Iterator<String> iterator = collection.iterator();
while (iterator.hasNext()) {
String book = iterator.next();
System.out.println(book);
}
Custom Iterator
// Custom collection
class Menu {
private String[] items;
private int position = 0;
public Menu(int size) {
items = new String[size];
}
public void addItem(String item) {
if (position < items.length) {
items[position++] = item;
}
}
public Iterator<String> createIterator() {
return new MenuIterator();
}
// Inner class Iterator
private class MenuIterator implements Iterator<String> {
private int currentPosition = 0;
@Override
public boolean hasNext() {
return currentPosition < position && items[currentPosition] != null;
}
@Override
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return items[currentPosition++];
}
}
}
// Sử dụng
Menu breakfastMenu = new Menu(5);
breakfastMenu.addItem("Pancakes");
breakfastMenu.addItem("Eggs");
breakfastMenu.addItem("Toast");
Iterator<String> iterator = breakfastMenu.createIterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
6. State Pattern
Mục đích
Cho phép object thay đổi behavior khi internal state thay đổi. Object sẽ như thể đã thay đổi class.
Ví dụ: Vending Machine
// State interface
interface VendingMachineState {
void insertCoin();
void ejectCoin();
void selectProduct();
void dispense();
}
// Context
class VendingMachine {
private VendingMachineState noCoinState;
private VendingMachineState hasCoinState;
private VendingMachineState soldState;
private VendingMachineState soldOutState;
private VendingMachineState currentState;
private int count;
public VendingMachine(int count) {
this.count = count;
noCoinState = new NoCoinState(this);
hasCoinState = new HasCoinState(this);
soldState = new SoldState(this);
soldOutState = new SoldOutState(this);
if (count > 0) {
currentState = noCoinState;
} else {
currentState = soldOutState;
}
}
public void insertCoin() {
currentState.insertCoin();
}
public void ejectCoin() {
currentState.ejectCoin();
}
public void selectProduct() {
currentState.selectProduct();
}
public void dispense() {
currentState.dispense();
}
public void setState(VendingMachineState state) {
this.currentState = state;
}
public void releaseProduct() {
if (count > 0) {
System.out.println("Product dispensed");
count--;
}
}
// Getters for states
public VendingMachineState getNoCoinState() { return noCoinState; }
public VendingMachineState getHasCoinState() { return hasCoinState; }
public VendingMachineState getSoldState() { return soldState; }
public VendingMachineState getSoldOutState() { return soldOutState; }
public int getCount() { return count; }
}
// Concrete States
class NoCoinState implements VendingMachineState {
private VendingMachine machine;
public NoCoinState(VendingMachine machine) {
this.machine = machine;
}
@Override
public void insertCoin() {
System.out.println("Coin inserted");
machine.setState(machine.getHasCoinState());
}
@Override
public void ejectCoin() {
System.out.println("No coin to eject");
}
@Override
public void selectProduct() {
System.out.println("Please insert coin first");
}
@Override
public void dispense() {
System.out.println("Please pay first");
}
}
class HasCoinState implements VendingMachineState {
private VendingMachine machine;
public HasCoinState(VendingMachine machine) {
this.machine = machine;
}
@Override
public void insertCoin() {
System.out.println("Coin already inserted");
}
@Override
public void ejectCoin() {
System.out.println("Coin ejected");
machine.setState(machine.getNoCoinState());
}
@Override
public void selectProduct() {
System.out.println("Product selected");
machine.setState(machine.getSoldState());
}
@Override
public void dispense() {
System.out.println("Please select product first");
}
}
class SoldState implements VendingMachineState {
private VendingMachine machine;
public SoldState(VendingMachine machine) {
this.machine = machine;
}
@Override
public void insertCoin() {
System.out.println("Please wait, dispensing product");
}
@Override
public void ejectCoin() {
System.out.println("Sorry, product already selected");
}
@Override
public void selectProduct() {
System.out.println("Already selected");
}
@Override
public void dispense() {
machine.releaseProduct();
if (machine.getCount() > 0) {
machine.setState(machine.getNoCoinState());
} else {
System.out.println("Machine sold out");
machine.setState(machine.getSoldOutState());
}
}
}
class SoldOutState implements VendingMachineState {
private VendingMachine machine;
public SoldOutState(VendingMachine machine) {
this.machine = machine;
}
@Override
public void insertCoin() {
System.out.println("Machine sold out, coin ejected");
}
@Override
public void ejectCoin() {
System.out.println("No coin to eject");
}
@Override
public void selectProduct() {
System.out.println("Machine sold out");
}
@Override
public void dispense() {
System.out.println("Machine sold out");
}
}
// Sử dụng
VendingMachine machine = new VendingMachine(2);
machine.insertCoin();
machine.selectProduct();
machine.dispense();
System.out.println();
machine.insertCoin();
machine.selectProduct();
machine.dispense();
System.out.println();
machine.insertCoin(); // Sold out
Tổng hợp Behavioral Patterns
| Pattern | Mục đích | Khi nào dùng | Ví dụ thực tế |
|---|---|---|---|
| Strategy | Encapsulate algorithms | Nhiều variants của algorithm | Comparator, LayoutManager |
| Observer | One-to-many notification | Event handling, real-time updates | EventListener, PropertyChangeListener |
| Command | Encapsulate request | Undo/redo, queuing, logging | Runnable, ActionListener |
| Template Method | Define algorithm skeleton | Fixed structure, varying steps | InputStream.read(), AbstractList |
| Iterator | Sequential access | Traverse collection | Iterator, Stream |
| State | Change behavior based on state | State machine, workflow | TCP connection states |
Bài tập
Bài 1: Strategy - Discount Calculator
Implement discount strategies:
- NoDiscount: 0%
- SeasonalDiscount: 10%
- LoyaltyDiscount: 15%
- CombinedDiscount: 20%
Bài 2: Observer - Stock Market
Implement stock market system:
Stock(subject): price thay đổiInvestor(observer): được notify khi price thay đổi- Hiển thị: "Stock XYZ changed to $100"
Bài 3: Command - Smart Home
Implement smart home control:
- Devices: Light, Fan, TV
- Commands: On, Off
- RemoteControl với undo/redo
Bài 4: State - Order Processing
Implement order states:
- NEW → PAID → SHIPPED → DELIVERED
- Mỗi state có các operations khác nhau
Trong bài cuối, chúng ta sẽ học SOLID Principles và Best Practices để viết code chất lượng cao.