บทนำ: “Code ที่ดี” หมายความว่าอะไร?
เมื่อเขียนโปรแกรม หลายคนคิดว่า “code ทำงานได้” ก็เพียงพอ แต่ในโลก production:
text❌ ปัญหา 1: ต้องเพิ่ม feature ใหม่ แต่ต้องแก้ code เก่า
❌ ปัญหา 2: Code ซ้ำซ้อนทั่ว project
❌ ปัญหา 3: ไม่รู้ว่า module นี้ใช้ได้ที่ไหนบ้าง
❌ ปัญหา 4: เปลี่ยนสิ่งหนึ่ง แต่ทำให้ส่วนอื่น break
❌ ปัญหา 5: ไม่รู้วิธีทำให้ code scale ขึ้น
Reusable & Scalable Code = Code ที่ ดูแลรักษาง่าย, ขยายได้, และใช้ซ้ำได้
SOLID Principles: Foundation of Good Design
Principle 1: Single Responsibility Principle (SRP)
“Class ควรมีเพียง ONE reason to change”
java// ❌ ไม่ดี: Class มี multiple responsibilities
public class UserManager {
// Responsibility 1: User data management
public User getUser(int id) {
// Database query
}
public void saveUser(User user) {
// Database save
}
// Responsibility 2: Email sending
public void sendWelcomeEmail(User user) {
// Email logic
}
// Responsibility 3: Logging
public void logUserActivity(User user) {
// Logging logic
}
// ← ต้อง change class นี้ถ้า:
// - Database layer เปลี่ยน
// - Email service เปลี่ยน
// - Logging format เปลี่ยน
// = 3 reasons to change!
}
// ✓ ดี: Each class มี ONE responsibility
public class UserRepository {
public User getUser(int id) {
// Only database operations
}
public void saveUser(User user) {
// Only database operations
}
}
public class EmailService {
public void sendWelcomeEmail(User user) {
// Only email operations
}
}
public class UserLogger {
public void logUserActivity(User user) {
// Only logging operations
}
}
// Usage: Compose responsibilities
public class UserOnboarding {
private UserRepository userRepository;
private EmailService emailService;
private UserLogger userLogger;
public void registerUser(User user) {
userRepository.saveUser(user);
emailService.sendWelcomeEmail(user);
userLogger.logUserActivity(user);
}
}
// ← Each class มี ONE reason to change
// ← Easier to test
// ← Easier to maintain
ประโยชน์ของ SRP:
- Focused – Each class ทำ one thing ดี
- Testable – Test หนึ่ง responsibility ต่อครั้ง
- Reusable – UserRepository ใช้ได้ที่ใดที่ไหน
- Maintainable – Change ใน one class ไม่ affect others
Principle 2: Open/Closed Principle (OCP)
“Code ควร OPEN for extension, CLOSED for modification”
java// ❌ ไม่ดี: ต้อง modify ทุกครั้งที่เพิ่ม discount type
public class PriceCalculator {
public double calculatePrice(double price, String discountType) {
if (discountType.equals("SUMMER")) {
return price * 0.8; // 20% off
} else if (discountType.equals("WINTER")) {
return price * 0.9; // 10% off
} else if (discountType.equals("CLEARANCE")) {
return price * 0.5; // 50% off
}
return price;
}
// ← ต้อง modify method นี้ทุกครั้งที่เพิ่ม discount
}
// ✓ ดี: Extend by creating new classes, not modifying existing
public interface DiscountStrategy {
double applyDiscount(double price);
}
public class SummerDiscount implements DiscountStrategy {
@Override
public double applyDiscount(double price) {
return price * 0.8; // 20% off
}
}
public class WinterDiscount implements DiscountStrategy {
@Override
public double applyDiscount(double price) {
return price * 0.9; // 10% off
}
}
public class ClearanceDiscount implements DiscountStrategy {
@Override
public double applyDiscount(double price) {
return price * 0.5; // 50% off
}
}
// ← เพิ่ม discount type โดยไม่ modify PriceCalculator
public class BlackFridayDiscount implements DiscountStrategy {
@Override
public double applyDiscount(double price) {
return price * 0.3; // 70% off
}
}
public class PriceCalculatorImproved {
public double calculatePrice(double price, DiscountStrategy strategy) {
return strategy.applyDiscount(price);
}
}
// Usage
PriceCalculatorImproved calculator = new PriceCalculatorImproved();
System.out.println(calculator.calculatePrice(100, new SummerDiscount())); // 80
System.out.println(calculator.calculatePrice(100, new BlackFridayDiscount())); // 30
// ← เพิ่ม discount ใหม่ได้ทันทีโดยไม่ modify calculator
ประโยชน์ของ OCP:
- Easy to extend – เพิ่มฟีเจอร์โดยไม่แก้เก่า
- Low risk – ไม่กระทบ existing code
- Scalable – Add strategies infinitely
- Stable – Existing code ไม่เปลี่ยน
Principle 3: Liskov Substitution Principle (LSP)
“Subclass ควรใช้ทดแทน Superclass ได้ โดยไม่ break”
java// ❌ ไม่ดี: Bird subclass ไม่ implement fly() ได้
public abstract class Bird {
abstract void fly();
}
public class Sparrow extends Bird {
@Override
void fly() {
System.out.println("Sparrow is flying");
}
}
public class Penguin extends Bird {
@Override
void fly() {
// ← Penguin ไม่บินได้!
throw new UnsupportedOperationException("Penguin cannot fly");
}
}
public void makeBirdFly(Bird bird) {
bird.fly();
// ← ถ้า bird เป็น Penguin จะ crash!
}
// ✓ ดี: Design hierarchy ให้ถูกต้อง
public abstract class Bird {
// Only common behaviors
}
public interface Flyable {
void fly();
}
public class Sparrow extends Bird implements Flyable {
@Override
public void fly() {
System.out.println("Sparrow is flying");
}
}
public class Penguin extends Bird {
public void swim() {
System.out.println("Penguin is swimming");
}
// ← Penguin ไม่ implement Flyable
}
public void makeBirdFly(Flyable bird) {
bird.fly();
// ← ทุก bird ที่ส่งเข้ามา บินได้ทั้งหมด
}
// Usage
makeBirdFly(new Sparrow()); // OK
makeBirdFly(new Penguin()); // Compile error - good!
ประโยชน์ของ LSP:
- Type Safety – Subclass substitution ปลอดภัย
- Predictable – No surprises when using polymorphism
- Correct Hierarchy – Design inheritance อย่างถูกต้อง
Principle 4: Interface Segregation Principle (ISP)
“Client ไม่ควร depend on interface ที่ไม่ใช้”
java// ❌ ไม่ดี: Fat interface ที่ต้อง implement ทั้งหมด
public interface Worker {
void work();
void eat();
void sleep();
}
public class Robot implements Worker {
@Override
public void work() {
System.out.println("Robot is working");
}
@Override
public void eat() {
// ← Robot ไม่ต้องกิน!
throw new UnsupportedOperationException();
}
@Override
public void sleep() {
// ← Robot ไม่ต้องนอน!
throw new UnsupportedOperationException();
}
}
// ✓ ดี: Segregate interface เป็นส่วนเล็ก
public interface Workable {
void work();
}
public interface Eatable {
void eat();
}
public interface Sleepable {
void sleep();
}
public class Human implements Workable, Eatable, Sleepable {
@Override
public void work() { System.out.println("Human is working"); }
@Override
public void eat() { System.out.println("Human is eating"); }
@Override
public void sleep() { System.out.println("Human is sleeping"); }
}
public class Robot implements Workable {
@Override
public void work() { System.out.println("Robot is working"); }
// ← Robot only implement what it needs
}
ประโยชน์ของ ISP:
- Flexible – Implement only needed behaviors
- Clean – No unnecessary method implementations
- Focused – Interface ทำ one thing ดี
Principle 5: Dependency Inversion Principle (DIP)
“Depend on abstractions, not concretions”
java// ❌ ไม่ดี: High-level depends on low-level (tight coupling)
public class MySQLDatabase {
public void save(String data) {
System.out.println("Saving to MySQL: " + data);
}
}
public class UserService {
private MySQLDatabase database = new MySQLDatabase(); // ← Tight coupling!
public void saveUser(String user) {
database.save(user);
}
}
// ← ถ้าต้องเปลี่ยนจาก MySQL ไป PostgreSQL ต้อง modify UserService
// ✓ ดี: Both depend on abstraction
public interface Database {
void save(String data);
}
public class MySQLDatabase implements Database {
@Override
public void save(String data) {
System.out.println("Saving to MySQL: " + data);
}
}
public class PostgreSQLDatabase implements Database {
@Override
public void save(String data) {
System.out.println("Saving to PostgreSQL: " + data);
}
}
public class UserService {
private Database database; // ← Depend on abstraction
// Dependency Injection
public UserService(Database database) {
this.database = database;
}
public void saveUser(String user) {
database.save(user);
}
}
// Usage
UserService service1 = new UserService(new MySQLDatabase());
UserService service2 = new UserService(new PostgreSQLDatabase());
// ← ไม่ต้อง modify UserService!
ประโยชน์ของ DIP:
- Decoupled – High-level ไม่ depend on low-level
- Flexible – Easy to swap implementations
- Testable – Mock dependency ได้ง่าย
ตัวอย่างที่ 1: Reusable Code Design
Problem: Duplicated Validation Logic
java// ❌ ไม่ดี: Validation logic ซ้ำกันทั่วที่
public class UserController {
public void registerUser(String email, String password) {
// Validation logic
if (email == null || email.isEmpty()) {
throw new IllegalArgumentException("Email required");
}
if (!email.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
if (password == null || password.length() < 6) {
throw new IllegalArgumentException("Password too short");
}
// Register logic
}
}
public class ProductService {
public void createProduct(String email) {
// Same validation logic repeated!
if (email == null || email.isEmpty()) {
throw new IllegalArgumentException("Email required");
}
if (!email.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
// Create logic
}
}
// ✓ ดี: Reusable validator
public class Validator {
public static void validateEmail(String email) {
if (email == null || email.isEmpty()) {
throw new IllegalArgumentException("Email required");
}
if (!email.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
}
public static void validatePassword(String password) {
if (password == null || password.length() < 6) {
throw new IllegalArgumentException("Password too short");
}
}
}
public class UserController {
public void registerUser(String email, String password) {
Validator.validateEmail(email);
Validator.validatePassword(password);
// Register logic
}
}
public class ProductService {
public void createProduct(String email) {
Validator.validateEmail(email);
// Create logic
}
}
// ← Validation logic ใน ONE place
// ← Reusable across project
// ← Easy to modify validation rules
ตัวอย่างที่ 2: Scalable Architecture
Problem: Hard to Add New Features
java// ❌ ไม่ดี: Monolithic service, hard to scale
public class OrderService {
public void processOrder(Order order) {
// 1. Validate order
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must have items");
}
// 2. Check inventory
for (OrderItem item : order.getItems()) {
if (!hasStock(item)) {
throw new RuntimeException("Out of stock");
}
}
// 3. Process payment
PaymentGateway gateway = new PaymentGateway();
boolean paid = gateway.charge(order.getTotal());
if (!paid) {
throw new RuntimeException("Payment failed");
}
// 4. Send email
EmailService email = new EmailService();
email.send(order.getCustomer().getEmail(),
"Order confirmed: " + order.getId());
// 5. Update inventory
updateInventory(order);
// 6. Log order
System.out.println("Order processed: " + order.getId());
}
// ← ต้องแก้ method นี้ทุกครั้งที่เพิ่ม step
}
// ✓ ดี: Scalable with composable services
public interface OrderStep {
void execute(Order order);
}
public class OrderValidationStep implements OrderStep {
@Override
public void execute(Order order) {
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must have items");
}
}
}
public class InventoryCheckStep implements OrderStep {
@Override
public void execute(Order order) {
for (OrderItem item : order.getItems()) {
if (!hasStock(item)) {
throw new RuntimeException("Out of stock");
}
}
}
}
public class PaymentProcessingStep implements OrderStep {
private PaymentGateway gateway;
public PaymentProcessingStep(PaymentGateway gateway) {
this.gateway = gateway;
}
@Override
public void execute(Order order) {
boolean paid = gateway.charge(order.getTotal());
if (!paid) {
throw new RuntimeException("Payment failed");
}
}
}
public class NotificationStep implements OrderStep {
private EmailService emailService;
public NotificationStep(EmailService emailService) {
this.emailService = emailService;
}
@Override
public void execute(Order order) {
emailService.send(order.getCustomer().getEmail(),
"Order confirmed: " + order.getId());
}
}
public class OrderServiceScalable {
private List<OrderStep> steps = new ArrayList<>();
// Add steps dynamically
public void addStep(OrderStep step) {
steps.add(step);
}
public void processOrder(Order order) {
for (OrderStep step : steps) {
step.execute(order);
}
}
}
// Usage: Easy to add/remove steps
OrderServiceScalable service = new OrderServiceScalable();
service.addStep(new OrderValidationStep());
service.addStep(new InventoryCheckStep());
service.addStep(new PaymentProcessingStep(new PaymentGateway()));
service.addStep(new NotificationStep(new EmailService()));
service.processOrder(order);
// ← เพิ่ม step ใหม่ได้ง่าย (e.g., ApplyCouponStep, LoggingStep)
// ← ไม่ต้องแก้ OrderServiceScalable
// ← Each step ใช้ซ้ำได้ที่อื่น
ตัวอย่างที่ 3: Testable Architecture
Problem: Hard to Test Due to Dependencies
java// ❌ ไม่ดี: Hard to test (real database, real email)
public class UserService {
public void registerUser(User user) {
// Real database
MySQLDatabase db = new MySQLDatabase();
db.save(user);
// Real email service
SMTPEmailService email = new SMTPEmailService();
email.send(user.getEmail(), "Welcome!");
}
}
// Test นี้ต้อง:
// - Create real database
// - Create real email service
// - ใช้เวลา
// - ไม่ reliable
// ✓ ดี: Testable with dependency injection
public interface UserRepository {
void save(User user);
}
public interface EmailService {
void send(String email, String message);
}
public class UserService {
private UserRepository repository;
private EmailService emailService;
// Dependency Injection
public UserService(UserRepository repository, EmailService emailService) {
this.repository = repository;
this.emailService = emailService;
}
public void registerUser(User user) {
repository.save(user);
emailService.send(user.getEmail(), "Welcome!");
}
}
// ✓ Test with mock objects
@Test
public void testRegisterUser() {
// Mock repository
UserRepository mockRepo = new UserRepository() {
@Override
public void save(User user) {
System.out.println("Mock: Saving user");
}
};
// Mock email service
EmailService mockEmail = new EmailService() {
@Override
public void send(String email, String message) {
System.out.println("Mock: Sending email");
}
};
UserService service = new UserService(mockRepo, mockEmail);
User user = new User("[email protected]");
service.registerUser(user);
// Test passes instantly, no real DB/email
}
Measuring Code Quality
Metrics ที่ต้องตระหนัก
text✓ HIGH COHESION:
- Class มี related responsibilities
- Methods collaborate ใน same class
- Indicator: Class ทำ ONE thing ดี
✓ LOW COUPLING:
- Class ไม่ depend much on others
- Easy to change one class independently
- Indicator: Few dependencies
✓ HIGH REUSABILITY:
- Components ใช้ซ้ำได้หลาย context
- Small, focused classes
- Indicator: Components reused in project
✓ EASY TESTABILITY:
- ทุก component test ได้ independently
- Mock dependencies ได้
- Indicator: Test suite comprehensive
❌ CODE SMELLS (ต้องระวัง):
- God Class (class ทำหลายอย่าง)
- Duplicated Code
- Long Method
- Tight Coupling
- Too Many Parameters
Best Practices: Creating Reusable & Scalable Code
text✓ DO:
☑ Follow SOLID principles
☑ Extract common logic to utilities/services
☑ Use interfaces for abstraction
☑ Apply Dependency Injection
☑ Design for extension, not modification
☑ Write focused, single-purpose classes
☑ Test with mocks and interfaces
☑ Document why design was chosen
✗ DON'T:
☐ Create God Classes (too many responsibilities)
☐ Duplicate code across project
☐ Tight couple components
☐ Mix layers (business logic with data access)
☐ Over-engineer for hypothetical scenarios
☐ Hardcode dependencies
☐ Make everything abstract immediately
☐ Skip testing because code "seems simple"
สรุป
Principle of Reusable & Scalable Code ไม่ใช่ “nice to have” แต่เป็น essential skill สำหรับ professional developers:
5 SOLID Principles:
- SRP: One responsibility per class
- OCP: Open for extension, closed for modification
- LSP: Substitutable subclasses
- ISP: Segregated interfaces
- DIP: Depend on abstractions
3 Key Outcomes:
- Reusable – Components ใช้ซ้ำได้ที่หลาย projects
- Scalable – Add features ได้โดยไม่break existing
- Maintainable – Easy to test, fix, และ modify
Real-world Impact:
- Team productivity เพิ่ม – code reuse ลด duplication
- Bug reduction – focused classes ใช้ง่ายและ test ง่าย
- Faster onboarding – clear responsibility ทำให้ code เข้าใจ
- Technical debt ลด – design ที่ดี ใช้ระยะยาวได้
Principles เหล่านี้ not rules to follow blindly แต่เป็น guidelines ที่help คุณ write code ที่ stands the test of time – code ที่ยังใช้ได้และ maintain ได้เมื่อ requirements เปลี่ยน ทีมเปลี่ยน และ project grow ขึ้น
