Design Patterns là gì?
Design Patterns là các giải pháp thiết kế đã được chứng minh hiệu quả cho các vấn đề phổ biến trong lập trình. Patterns không phải là code có sẵn mà là templates giúp giải quyết vấn đề một cách tối ưu.
3 nhóm Design Patterns
- Creational Patterns - Cách tạo objects
- Structural Patterns - Cách tổ chức classes/objects
- Behavioral Patterns - Cách objects tương tác
Creational Patterns
1. Singleton Pattern
Đảm bảo class chỉ có 1 instance duy nhất.
public class Database {
private static Database instance;
private Database() {
// Private constructor
}
public static Database getInstance() {
if (instance == null) {
instance = new Database();
}
return instance;
}
public void query(String sql) {
System.out.println("Executing: " + sql);
}
}
// Sử dụng
Database db1 = Database.getInstance();
Database db2 = Database.getInstance();
System.out.println(db1 == db2); // true - cùng 1 instance
Thread-safe version:
public class Database {
private static volatile Database instance;
private Database() {}
public static Database getInstance() {
if (instance == null) {
synchronized (Database.class) {
if (instance == null) {
instance = new Database();
}
}
}
return instance;
}
}
Khi nào dùng:
- Database connections
- Configuration
- Logging
- Cache
2. Factory Pattern
Tạo objects mà không cần chỉ định exact class.
// Product interface
interface Animal {
void makeSound();
}
// Concrete products
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("Meow!");
}
}
// Factory
class AnimalFactory {
public static Animal createAnimal(String type) {
switch (type.toLowerCase()) {
case "dog":
return new Dog();
case "cat":
return new Cat();
default:
throw new IllegalArgumentException("Unknown animal type");
}
}
}
// Sử dụng
Animal dog = AnimalFactory.createAnimal("dog");
dog.makeSound(); // "Woof!"
Khi nào dùng:
- Không biết trước loại object cần tạo
- Logic tạo object phức tạp
- Muốn encapsulate object creation
3. Builder Pattern
Xây dựng complex objects từng bước.
public class User {
// Required
private final String username;
private final String email;
// Optional
private final String firstName;
private final String lastName;
private final int age;
private final String phone;
private User(Builder builder) {
this.username = builder.username;
this.email = builder.email;
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
}
public static class Builder {
// Required
private final String username;
private final String email;
// Optional
private String firstName = "";
private String lastName = "";
private int age = 0;
private String phone = "";
public Builder(String username, String email) {
this.username = username;
this.email = email;
}
public Builder firstName(String firstName) {
this.firstName = firstName;
return this;
}
public Builder lastName(String lastName) {
this.lastName = lastName;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public User build() {
return new User(this);
}
}
}
// Sử dụng
User user = new User.Builder("john_doe", "john@example.com")
.firstName("John")
.lastName("Doe")
.age(30)
.phone("123-456-7890")
.build();
Khi nào dùng:
- Nhiều constructor parameters
- Nhiều optional parameters
- Cần immutable objects
Structural Patterns
4. Adapter Pattern
Chuyển đổi interface này sang interface khác.
// Target interface
interface MediaPlayer {
void play(String filename);
}
// Adaptee (class cần adapt)
class VLCPlayer {
public void playVLC(String filename) {
System.out.println("Playing VLC file: " + filename);
}
}
// Adapter
class VLCAdapter implements MediaPlayer {
private VLCPlayer vlcPlayer;
public VLCAdapter(VLCPlayer vlcPlayer) {
this.vlcPlayer = vlcPlayer;
}
@Override
public void play(String filename) {
vlcPlayer.playVLC(filename);
}
}
// Client
class AudioPlayer implements MediaPlayer {
@Override
public void play(String filename) {
String extension = filename.substring(filename.lastIndexOf('.') + 1);
if (extension.equals("mp3")) {
System.out.println("Playing MP3: " + filename);
} else if (extension.equals("vlc")) {
MediaPlayer adapter = new VLCAdapter(new VLCPlayer());
adapter.play(filename);
}
}
}
// Sử dụng
AudioPlayer player = new AudioPlayer();
player.play("song.mp3");
player.play("movie.vlc");
Khi nào dùng:
- Integrate với third-party libraries
- Legacy code compatibility
- Multiple incompatible interfaces
5. Decorator Pattern
Thêm functionality cho objects động.
// Component
interface Coffee {
double getCost();
String getDescription();
}
// Concrete Component
class SimpleCoffee implements Coffee {
@Override
public double getCost() {
return 2.0;
}
@Override
public String getDescription() {
return "Simple Coffee";
}
}
// Decorator
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
// Concrete Decorators
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
@Override
public String getDescription() {
return super.getDescription() + ", Milk";
}
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 0.2;
}
@Override
public String getDescription() {
return super.getDescription() + ", Sugar";
}
}
// Sử dụng
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
// Output: "Simple Coffee, Milk, Sugar $2.7"
Khi nào dùng:
- Thêm responsibilities động
- Tránh subclass explosion
- Extend functionality mà không modify code
6. Facade Pattern
Cung cấp interface đơn giản cho subsystem phức tạp.
// Complex subsystems
class CPU {
public void start() {
System.out.println("CPU started");
}
}
class Memory {
public void load() {
System.out.println("Memory loaded");
}
}
class HardDrive {
public void read() {
System.out.println("Hard drive read");
}
}
// Facade
class ComputerFacade {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public ComputerFacade() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
}
public void start() {
cpu.start();
memory.load();
hardDrive.read();
System.out.println("Computer started!");
}
}
// Client
ComputerFacade computer = new ComputerFacade();
computer.start(); // Đơn giản!
Behavioral Patterns
7. Observer Pattern
Notification system khi state thay đổi.
import java.util.*;
// Observer interface
interface Observer {
void update(String message);
}
// Subject
class NewsAgency {
private List<Observer> observers = new ArrayList<>();
private String news;
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void setNews(String news) {
this.news = news;
notifyObservers();
}
private void notifyObservers() {
for (Observer observer : observers) {
observer.update(news);
}
}
}
// Concrete Observers
class NewsChannel implements Observer {
private String name;
public NewsChannel(String name) {
this.name = name;
}
@Override
public void update(String news) {
System.out.println(name + " received news: " + news);
}
}
// Sử dụng
NewsAgency agency = new NewsAgency();
NewsChannel channel1 = new NewsChannel("CNN");
NewsChannel channel2 = new NewsChannel("BBC");
agency.addObserver(channel1);
agency.addObserver(channel2);
agency.setNews("Breaking: Important event!");
// Output:
// CNN received news: Breaking: Important event!
// BBC received news: Breaking: Important event!
Khi nào dùng:
- Event handling
- Model-View updates
- Pub-Sub systems
8. Strategy Pattern
Encapsulate algorithms, có thể swap được.
// Strategy interface
interface PaymentStrategy {
void pay(int amount);
}
// Concrete Strategies
class CreditCardPayment implements PaymentStrategy {
private String cardNumber;
public CreditCardPayment(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public void pay(int amount) {
System.out.println("Paid $" + amount + " with credit card: " + cardNumber);
}
}
class PayPalPayment implements PaymentStrategy {
private String email;
public PayPalPayment(String email) {
this.email = email;
}
@Override
public void pay(int amount) {
System.out.println("Paid $" + amount + " via PayPal: " + email);
}
}
// Context
class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}
// Sử dụng
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment("1234-5678"));
cart.checkout(100);
cart.setPaymentStrategy(new PayPalPayment("user@example.com"));
cart.checkout(50);
Khi nào dùng:
- Nhiều algorithms tương tự
- Conditional statements phức tạp
- Runtime algorithm selection
9. Template Method Pattern
Define skeleton của algorithm, để subclasses override steps.
abstract class DataProcessor {
// Template method
public final void process() {
readData();
processData();
saveData();
}
abstract void readData();
abstract void processData();
// Hook method (optional override)
void saveData() {
System.out.println("Saving data to database");
}
}
class CSVProcessor extends DataProcessor {
@Override
void readData() {
System.out.println("Reading CSV file");
}
@Override
void processData() {
System.out.println("Processing CSV data");
}
}
class JSONProcessor extends DataProcessor {
@Override
void readData() {
System.out.println("Reading JSON file");
}
@Override
void processData() {
System.out.println("Processing JSON data");
}
@Override
void saveData() {
System.out.println("Saving to NoSQL database");
}
}
// Sử dụng
DataProcessor csvProcessor = new CSVProcessor();
csvProcessor.process();
DataProcessor jsonProcessor = new JSONProcessor();
jsonProcessor.process();
Tổng hợp Best Practices
1. SOLID Principles
Design patterns tuân thủ SOLID:
- Single Responsibility
- Open/Closed
- Liskov Substitution
- Interface Segregation
- Dependency Inversion
2. Khi nào dùng patterns?
// ❌ Over-engineering
// Đừng dùng pattern nếu simple solution đủ
public class Calculator {
// Không cần Factory chỉ để tạo Calculator!
}
// ✅ Dùng khi cần
// Factory hợp lý khi có nhiều loại calculator
interface Calculator {}
class ScientificCalculator implements Calculator {}
class BasicCalculator implements Calculator {}
3. Kết hợp patterns
// Singleton + Factory
public class DatabaseFactory {
private static DatabaseFactory instance;
private DatabaseFactory() {}
public static DatabaseFactory getInstance() {
if (instance == null) {
instance = new DatabaseFactory();
}
return instance;
}
public Database createDatabase(String type) {
// Factory logic
}
}
Patterns trong Spring Framework
// Singleton
@Service // Spring tạo singleton bean
public class UserService {}
// Factory
ApplicationContext context = new AnnotationConfigApplicationContext();
UserService service = context.getBean(UserService.class);
// Template Method
JdbcTemplate jdbcTemplate;
jdbcTemplate.query("SELECT * FROM users", new RowMapper<User>() {
// Override mapping logic
});
// Observer
@EventListener
public void handleUserCreated(UserCreatedEvent event) {
// React to event
}
// Proxy
@Transactional // Spring tạo proxy để handle transactions
public void saveUser(User user) {}
Kết luận
Design Patterns giúp:
- Code dễ maintain và extend
- Communication giữa developers tốt hơn
- Tránh reinvent the wheel
- Best practices đã được chứng minh
Nhớ:
- Không overuse patterns
- Hiểu vấn đề trước khi áp dụng
- Simple solution đôi khi tốt hơn complex pattern
Nắm vững Design Patterns là bước quan trọng để trở thành Java developer chuyên nghiệp!