บทนำ: ทำไมต้อง Design Patterns?
เมื่อเขียนโปรแกรม คุณอาจเจอปัญหา “ยังไง” ซ้ำๆ:
❓ ปัญหา 1: "ไม่รู้ต้องสร้าง object ไหนตอน runtime"
❓ ปัญหา 2: "ต้องเปลี่ยน algorithm บ่อยตามเงื่อนไข"
❓ ปัญหา 3: "ต้องบอก objects อื่นว่ามีอะไรเปลี่ยน"
❓ ปัญหา 4: "Database access code ซ้ำกันทั่ว project"
Design Patterns = Proven solutions สำหรับปัญหา common ในโปรแกรมมิ่ง
Pattern 1: Factory Pattern
ปัญหา: Object Creation Complexity
java// ❌ ปัญหา: Tight coupling, hard to maintain
public class ReportGenerator {
public void generateReport(String type) {
if (type.equals("pdf")) {
PDFReport report = new PDFReport();
report.generate();
} else if (type.equals("excel")) {
ExcelReport report = new ExcelReport();
report.generate();
} else if (type.equals("csv")) {
CSVReport report = new CSVReport();
report.generate();
}
// ← ถ้าต้องเพิ่ม report type ต้อง modify method นี้
// ← ทำให้ dependency ยุ่งมาก
}
}
วิธีแก้: Factory Pattern
java// ==== Interface ====
public interface Report {
void generate();
void export(String filename);
}
// ==== Concrete Implementations ====
public class PDFReport implements Report {
@Override
public void generate() {
System.out.println("Generating PDF report...");
}
@Override
public void export(String filename) {
System.out.println("Exporting to: " + filename + ".pdf");
}
}
public class ExcelReport implements Report {
@Override
public void generate() {
System.out.println("Generating Excel report...");
}
@Override
public void export(String filename) {
System.out.println("Exporting to: " + filename + ".xlsx");
}
}
public class CSVReport implements Report {
@Override
public void generate() {
System.out.println("Generating CSV report...");
}
@Override
public void export(String filename) {
System.out.println("Exporting to: " + filename + ".csv");
}
}
// ==== Factory ====
/**
* ReportFactory: centralized object creation
* ← Single Responsibility: การสร้าง objects
*/
public class ReportFactory {
/**
* Create report based on type
* @param type report type (pdf, excel, csv)
* @return appropriate Report implementation
*/
public static Report createReport(String type) {
switch (type.toLowerCase()) {
case "pdf":
return new PDFReport();
case "excel":
return new ExcelReport();
case "csv":
return new CSVReport();
default:
throw new IllegalArgumentException("Unknown report type: " + type);
}
}
}
// ==== Usage ====
public class ReportGeneratorImproved {
public void generateReport(String type) {
// ← ใช้ factory เพื่อ create object
Report report = ReportFactory.createReport(type);
report.generate();
report.export("output_report");
}
}
public class FactoryPatternExample {
public static void main(String[] args) {
ReportGeneratorImproved generator = new ReportGeneratorImproved();
System.out.println("=== Generate Reports ===");
generator.generateReport("pdf");
generator.generateReport("excel");
generator.generateReport("csv");
// ← หากต้องเพิ่ม report type ใหม่:
// 1. Create new class implement Report
// 2. Add case ใน ReportFactory เท่านั้น
// 3. Code อื่นไม่ต้องแก้ไข (Open/Closed Principle)
}
}
// Output:
// === Generate Reports ===
// Generating PDF report...
// Exporting to: output_report.pdf
// Generating Excel report...
// Exporting to: output_report.xlsx
// Generating CSV report...
// Exporting to: output_report.csv
ประโยชน์ของ Factory Pattern:
- Decoupling – ระหว่าง creator กับ concrete classes
- Flexibility – เพิ่ม types ได้โดยไม่ modify existing code
- Single Responsibility – factory ดูแล creation logic เท่านั้น
Pattern 2: Strategy Pattern
ปัญหา: Algorithm ต้องเปลี่ยนตามเงื่อนไข
java// ❌ ปัญหา: Many if-else, hard to test
public class PaymentProcessor {
public void processPayment(String method, double amount) {
if (method.equals("creditCard")) {
// Validate credit card
// Charge credit card
// Record transaction
System.out.println("Processing credit card payment...");
} else if (method.equals("bankTransfer")) {
// Validate bank account
// Initiate transfer
// Wait for confirmation
System.out.println("Processing bank transfer...");
} else if (method.equals("eWallet")) {
// Validate e-wallet
// Deduct balance
// Update wallet
System.out.println("Processing e-wallet payment...");
}
// ← ยาว ยุ่ง ยากต่อการ test
}
}
วิธีแก้: Strategy Pattern
java// ==== Strategy Interface ====
public interface PaymentStrategy {
void validate();
void pay(double amount);
void recordTransaction();
}
// ==== Concrete Strategies ====
public class CreditCardStrategy implements PaymentStrategy {
private String cardNumber;
public CreditCardStrategy(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public void validate() {
System.out.println("✓ Validating credit card: " + cardNumber);
}
@Override
public void pay(double amount) {
System.out.println("✓ Charging card: $" + amount);
}
@Override
public void recordTransaction() {
System.out.println("✓ Recording credit card transaction");
}
}
public class BankTransferStrategy implements PaymentStrategy {
private String bankAccount;
public BankTransferStrategy(String bankAccount) {
this.bankAccount = bankAccount;
}
@Override
public void validate() {
System.out.println("✓ Validating bank account: " + bankAccount);
}
@Override
public void pay(double amount) {
System.out.println("✓ Initiating transfer: $" + amount);
}
@Override
public void recordTransaction() {
System.out.println("✓ Recording bank transfer");
}
}
public class EWalletStrategy implements PaymentStrategy {
private String walletId;
public EWalletStrategy(String walletId) {
this.walletId = walletId;
}
@Override
public void validate() {
System.out.println("✓ Validating e-wallet: " + walletId);
}
@Override
public void pay(double amount) {
System.out.println("✓ Deducting from wallet: $" + amount);
}
@Override
public void recordTransaction() {
System.out.println("✓ Recording e-wallet transaction");
}
}
// ==== Context ====
/**
* PaymentProcessor: Uses strategy
* ← No if-else! Strategy is pluggable
*/
public class PaymentProcessorImproved {
private PaymentStrategy strategy;
// Setter for strategy (Dependency Injection)
public void setPaymentStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void processPayment(double amount) {
if (strategy == null) {
throw new IllegalStateException("Payment strategy not set");
}
strategy.validate();
strategy.pay(amount);
strategy.recordTransaction();
}
}
// ==== Usage ====
public class StrategyPatternExample {
public static void main(String[] args) {
PaymentProcessorImproved processor = new PaymentProcessorImproved();
System.out.println("=== Credit Card Payment ===");
processor.setPaymentStrategy(new CreditCardStrategy("1234-5678-9012-3456"));
processor.processPayment(99.99);
System.out.println("\n=== Bank Transfer ===");
processor.setPaymentStrategy(new BankTransferStrategy("ACC-123456"));
processor.processPayment(500.00);
System.out.println("\n=== E-Wallet Payment ===");
processor.setPaymentStrategy(new EWalletStrategy("wallet_12345"));
processor.processPayment(49.99);
}
}
// Output:
// === Credit Card Payment ===
// ✓ Validating credit card: 1234-5678-9012-3456
// ✓ Charging card: $99.99
// ✓ Recording credit card transaction
//
// === Bank Transfer ===
// ✓ Validating bank account: ACC-123456
// ✓ Initiating transfer: $500.0
// ✓ Recording bank transfer
//
// === E-Wallet Payment ===
// ✓ Validating e-wallet: wallet_12345
// ✓ Deducting from wallet: $49.99
// ✓ Recording e-wallet transaction
ประโยชน์ของ Strategy Pattern:
- No if-else – Algorithm ต่างกันขาดจากกัน
- Runtime Flexibility – เปลี่ยน strategy ได้ทันที
- Testability – Test แต่ละ strategy แยกกัน
- Easy to Extend – เพิ่ม strategies ใหม่ง่าย
Pattern 3: Observer Pattern
ปัญหา: Tight Coupling เมื่อ Notify Objects
java// ❌ ปัญหา: Order ต้อง know ทุก classes ที่ต้องอัพเดท
public class Order {
private String status;
public void changeStatus(String newStatus) {
this.status = newStatus;
// ← Order ต้อง notify ทุก related objects
// Tight coupling!
EmailService emailService = new EmailService();
emailService.sendNotification("Order status: " + status);
InventoryService inventoryService = new InventoryService();
inventoryService.updateStock(this);
LogService logService = new LogService();
logService.logOrderChange(this);
// ← ถ้าต้องเพิ่ม observer ต้อง modify Order class
}
}
วิธีแก้: Observer Pattern
java// ==== Observer Interface ====
public interface OrderObserver {
void update(Order order);
}
// ==== Concrete Observers ====
public class EmailObserver implements OrderObserver {
@Override
public void update(Order order) {
System.out.println("📧 Email: Sending notification for order " +
order.getId() + " - Status: " + order.getStatus());
}
}
public class InventoryObserver implements OrderObserver {
@Override
public void update(Order order) {
System.out.println("📦 Inventory: Updating stock for order " +
order.getId());
}
}
public class LogObserver implements OrderObserver {
@Override
public void update(Order order) {
System.out.println("📝 Log: Recording order change - ID: " +
order.getId() + ", Status: " + order.getStatus());
}
}
public class AnalyticsObserver implements OrderObserver {
@Override
public void update(Order order) {
System.out.println("📊 Analytics: Tracking order " +
order.getId() + " status change");
}
}
// ==== Subject (Observable) ====
/**
* Order: maintains list of observers
* ← Order ไม่รู้ใครคือ observers
*/
public class Order {
private String id;
private String status;
private List<OrderObserver> observers = new ArrayList<>();
public Order(String id) {
this.id = id;
this.status = "CREATED";
}
public String getId() { return id; }
public String getStatus() { return status; }
// Observer management
public void attach(OrderObserver observer) {
observers.add(observer);
System.out.println("✓ Observer attached");
}
public void detach(OrderObserver observer) {
observers.remove(observer);
System.out.println("✓ Observer detached");
}
public void notifyObservers() {
for (OrderObserver observer : observers) {
observer.update(this);
}
}
// Business logic
public void changeStatus(String newStatus) {
this.status = newStatus;
System.out.println("➡️ Order status changed to: " + status);
notifyObservers(); // ← Notify all observers
}
}
// ==== Usage ====
public class ObserverPatternExample {
public static void main(String[] args) {
Order order = new Order("ORD-001");
// Attach observers
System.out.println("=== Attaching Observers ===");
order.attach(new EmailObserver());
order.attach(new InventoryObserver());
order.attach(new LogObserver());
order.attach(new AnalyticsObserver());
// Change order status (notifies all observers)
System.out.println("\n=== Order Status Change ===");
order.changeStatus("PROCESSING");
System.out.println("\n=== Another Status Change ===");
order.changeStatus("SHIPPED");
}
}
// Output:
// === Attaching Observers ===
// ✓ Observer attached
// ✓ Observer attached
// ✓ Observer attached
// ✓ Observer attached
//
// === Order Status Change ===
// ➡️ Order status changed to: PROCESSING
// 📧 Email: Sending notification for order ORD-001 - Status: PROCESSING
// 📦 Inventory: Updating stock for order ORD-001
// 📝 Log: Recording order change - ID: ORD-001, Status: PROCESSING
// 📊 Analytics: Tracking order ORD-001 status change
//
// === Another Status Change ===
// ➡️ Order status changed to: SHIPPED
// 📧 Email: Sending notification for order ORD-001 - Status: SHIPPED
// 📦 Inventory: Updating stock for order ORD-001
// 📝 Log: Recording order change - ID: ORD-001, Status: SHIPPED
// 📊 Analytics: Tracking order ORD-001 status change
ประโยชน์ของ Observer Pattern:
- Loose Coupling – Subject ไม่ต้อง know observers
- Dynamic Subscriptions – Attach/detach observers ได้ anytime
- Flexible – Add observers โดยไม่ modify Subject
- Scalable – Order ไม่เพิ่มความซับซ้อน เพิ่ม observers
Pattern 4: Repository Pattern
ปัญหา: Data Access Logic ซ้ำกันทั่ว Project
java// ❌ ปัญหา: Data access logic ไปอยู่ทั่วหลาย classes
public class UserController {
public void getUser(int id) {
// ← Data access code mixed with business logic
try {
Connection conn = DriverManager.getConnection("...");
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE id = ?"
);
stmt.setInt(1, id);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
User user = new User(rs.getInt("id"), rs.getString("name"));
System.out.println(user);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public class UserService {
public void updateUser(User user) {
// ← Same data access code repeated!
try {
Connection conn = DriverManager.getConnection("...");
PreparedStatement stmt = conn.prepareStatement(
"UPDATE users SET name = ? WHERE id = ?"
);
stmt.setString(1, user.getName());
stmt.setInt(2, user.getId());
stmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
วิธีแก้: Repository Pattern
java// ==== POJO ====
public class User {
private int id;
private String name;
private String email;
public User(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// Getters
public int getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
@Override
public String toString() {
return String.format(
"User{id=%d, name='%s', email='%s'}",
id, name, email
);
}
}
// ==== Repository Interface ====
/**
* UserRepository: Abstraction for data access
* ← Hides database implementation details
*/
public interface UserRepository {
User findById(int id);
List<User> findAll();
void save(User user);
void update(User user);
void delete(int id);
}
// ==== Repository Implementation ====
/**
* UserRepositoryImpl: Concrete implementation
* ← All data access logic in ONE place
*/
public class UserRepositoryImpl implements UserRepository {
@Override
public User findById(int id) {
String sql = "SELECT id, name, email FROM users WHERE id = ?";
try (Connection conn = DatabaseConfig.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return new User(
rs.getInt("id"),
rs.getString("name"),
rs.getString("email")
);
}
}
} catch (SQLException e) {
System.err.println("Error: " + e.getMessage());
}
return null;
}
@Override
public List<User> findAll() {
List<User> users = new ArrayList<>();
String sql = "SELECT id, name, email FROM users";
try (Connection conn = DatabaseConfig.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
User user = new User(
rs.getInt("id"),
rs.getString("name"),
rs.getString("email")
);
users.add(user);
}
} catch (SQLException e) {
System.err.println("Error: " + e.getMessage());
}
return users;
}
@Override
public void save(User user) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
try (Connection conn = DatabaseConfig.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, user.getName());
pstmt.setString(2, user.getEmail());
pstmt.executeUpdate();
System.out.println("✓ User saved: " + user.getName());
} catch (SQLException e) {
System.err.println("Error: " + e.getMessage());
}
}
@Override
public void update(User user) {
String sql = "UPDATE users SET name = ?, email = ? WHERE id = ?";
try (Connection conn = DatabaseConfig.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, user.getName());
pstmt.setString(2, user.getEmail());
pstmt.setInt(3, user.getId());
pstmt.executeUpdate();
System.out.println("✓ User updated: " + user.getName());
} catch (SQLException e) {
System.err.println("Error: " + e.getMessage());
}
}
@Override
public void delete(int id) {
String sql = "DELETE FROM users WHERE id = ?";
try (Connection conn = DatabaseConfig.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
pstmt.executeUpdate();
System.out.println("✓ User deleted");
} catch (SQLException e) {
System.err.println("Error: " + e.getMessage());
}
}
}
// ==== Service Layer (uses Repository) ====
public class UserService {
private UserRepository userRepository;
// Dependency Injection
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void createUser(String name, String email) {
User user = new User(0, name, email);
userRepository.save(user);
}
public User getUser(int id) {
return userRepository.findById(id);
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
public void updateUserInfo(int id, String newName, String newEmail) {
User user = new User(id, newName, newEmail);
userRepository.update(user);
}
}
// ==== Controller Layer ====
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
public void handleGetUser(int id) {
User user = userService.getUser(id);
if (user != null) {
System.out.println(user);
}
}
public void handleCreateUser(String name, String email) {
userService.createUser(name, email);
}
}
// ==== Usage ====
public class RepositoryPatternExample {
public static void main(String[] args) {
// Setup dependency injection
UserRepository userRepository = new UserRepositoryImpl();
UserService userService = new UserService(userRepository);
UserController userController = new UserController(userService);
System.out.println("=== Create User ===");
userController.handleCreateUser("John Doe", "[email protected]");
userController.handleCreateUser("Jane Smith", "[email protected]");
System.out.println("\n=== Get All Users ===");
List<User> users = userService.getAllUsers();
users.forEach(System.out::println);
System.out.println("\n=== Get Specific User ===");
userController.handleGetUser(1);
System.out.println("\n=== Update User ===");
userController.handleGetUser(1);
}
}
// Output:
// === Create User ===
// ✓ User saved: John Doe
// ✓ User saved: Jane Smith
//
// === Get All Users ===
// User{id=1, name='John Doe', email='[email protected]'}
// User{id=2, name='Jane Smith', email='[email protected]'}
//
// === Get Specific User ===
// User{id=1, name='John Doe', email='[email protected]'}
//
// === Update User ===
// User{id=1, name='John Doe', email='[email protected]'}
ประโยชน์ของ Repository Pattern:
- Separation of Concerns – Data access ต่างจาก business logic
- Testability – Mock repository สำหรับ testing
- Maintainability – เปลี่ยน database ได้โดยไม่ modify business logic
- Reusability – Repository ใช้ได้ในหลาย services
Comparison: 4 Patterns
| Pattern | ปัญหา | วิธีแก้ | ใช้เมื่อ |
|---|---|---|---|
| Factory | Tight coupling ในการสร้าง objects | Centralize creation logic | Nerd create different types dynamically |
| Strategy | Many if-else for algorithms | Encapsulate algorithms ในclasses | Algorithm เปลี่ยนตามเงื่อนไข |
| Observer | Tight coupling เมื่อ notify | Loose coupling via interfaces | Event-driven architecture |
| Repository | Data access ซ้ำกันทั่วที่ | Centralize data access | Multi-layer architecture |
Best Practices: Pattern Selection
text✓ DO:
☑ Choose pattern to solve real problem
☑ Understand when to apply pattern
☑ Use patterns consistently
☑ Keep patterns simple (don't over-engineer)
☑ Document why pattern was chosen
☑ Refactor to patterns gradually
✗ DON'T:
☐ Use pattern just because it exists
☐ Mix patterns unnecessarily
☐ Apply patterns prematurely
☐ Make code overly complex
☐ Ignore existing code patterns
☐ Create patterns without understanding
สรุป
Design Patterns ไม่ใช่ “magic solution” แต่เป็น documented best practices ที่ help solve common problems:
4 Patterns ที่สำคัญ:
- Factory → Simplify object creation
- Strategy → Encapsulate algorithms
- Observer → Decouple event handling
- Repository → Centralize data access
เมื่อใช้ Patterns ถูกต้อง:
- Code ของคุณ cleaner – clear responsibilities
- Code ของคุณ more flexible – เปลี่ยนแปลงได้ง่าย
- Code ของคุณ more testable – mock ได้ง่าย
- Team ของคุณ understands faster – patterns เป็น common language
Design Patterns คือ “vocabulary” ของ software engineers – เมื่อคุณ master patterns คุณจะ communicate design ideas ได้ชัดเจนและ create systems ที่ยืดหยุ่นและยั่งยืน ยิ่งกว่านั้น pattern ยังช่วยให้คุณหลีกเลี่ยงปัญหา common ที่นักพัฒนาอื่นเคยประสบมา
