Design Patterns trong Java
1. Creational Patterns
1.1 Singleton Pattern
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
1.2 Factory Method Pattern
public interface Animal {
void makeSound();
}
public class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
public class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("Meow!");
}
}
public class AnimalFactory {
public Animal createAnimal(String type) {
if ("dog".equalsIgnoreCase(type)) {
return new Dog();
} else if ("cat".equalsIgnoreCase(type)) {
return new Cat();
}
throw new IllegalArgumentException("Unknown animal type");
}
}
1.3 Builder Pattern
public class User {
private final String firstName;
private final String lastName;
private final int age;
private final String email;
private User(UserBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.email = builder.email;
}
public static class UserBuilder {
private final String firstName;
private final String lastName;
private int age;
private String email;
public UserBuilder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public UserBuilder email(String email) {
this.email = email;
return this;
}
public User build() {
return new User(this);
}
}
}
// Sử dụng
User user = new User.UserBuilder("John", "Doe")
.age(30)
.email("john@example.com")
.build();
2. Structural Patterns
2.1 Adapter Pattern
public interface MediaPlayer {
void play(String audioType, String fileName);
}
public interface AdvancedMediaPlayer {
void playVlc(String fileName);
void playMp4(String fileName);
}
public class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Playing vlc file: " + fileName);
}
@Override
public void playMp4(String fileName) {
// do nothing
}
}
public class MediaAdapter implements MediaPlayer {
AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
if(audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer = new VlcPlayer();
}
}
@Override
public void play(String audioType, String fileName) {
if(audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer.playVlc(fileName);
}
}
}
2.2 Decorator Pattern
public interface Coffee {
double getCost();
String getDescription();
}
public class SimpleCoffee implements Coffee {
@Override
public double getCost() {
return 1;
}
@Override
public String getDescription() {
return "Simple coffee";
}
}
public abstract class CoffeeDecorator implements Coffee {
protected final Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
public double getCost() {
return decoratedCoffee.getCost();
}
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
public class Milk extends CoffeeDecorator {
public Milk(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
@Override
public String getDescription() {
return super.getDescription() + ", milk";
}
}
3. Behavioral Patterns
3.1 Observer Pattern
public interface Observer {
void update(String message);
}
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers(String message);
}
public class NewsAgency implements Subject {
private List<Observer> observers = new ArrayList<>();
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(String message) {
for(Observer observer: observers) {
observer.update(message);
}
}
}
3.2 Strategy Pattern
public interface PaymentStrategy {
void pay(int amount);
}
public class CreditCardPayment implements PaymentStrategy {
private String cardNumber;
public CreditCardPayment(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid with credit card");
}
}
public class PayPalPayment implements PaymentStrategy {
private String emailId;
public PayPalPayment(String emailId) {
this.emailId = emailId;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid using PayPal");
}
}
public class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}
4. Best Practices
1. SOLID Principles
- Single Responsibility Principle (SRP)
- Open/Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
2. Khi nào sử dụng Pattern
- Singleton: Khi cần đảm bảo chỉ có một instance của class
- Factory: Khi logic tạo đối tượng phức tạp
- Builder: Khi có nhiều tham số tùy chọn khi khởi tạo
- Adapter: Khi cần tương thích giữa các interface khác nhau
- Observer: Khi cần notify nhiều đối tượng về thay đổi
- Strategy: Khi có nhiều thuật toán có thể hoán đổi cho nhau
3. Anti-patterns cần tránh
- God Object: Class quá lớn, làm quá nhiều việc
- Golden Hammer: Sử dụng một pattern cho mọi vấn đề
- Premature Optimization: Tối ưu hóa quá sớm
- Spaghetti Code: Code không có cấu trúc rõ ràng