SOLID: 5 หลักการของ OOP ที่ดี

บทนำ: ทำไมต้องมี “หลักการ” ในการเขียน OOP?

เมื่อคุณเขียนโปรแกรม คุณอาจทำให้มันทำงานได้ แต่หลังจากนั้นจะเกิดปัญหา: code เริ่มซับซ้อน ยากต่อการเปลี่ยนแปลง bug ปรากฏขึ้น และการเพิ่มฟีเจอร์ใหม่กลายเป็นเรื่องที่น่ากลัว

SOLID คือชุด 5 หลักการที่ออกแบบมา เพื่อแก้ปัญหาเหล่านี้ โดยให้ code ของเรา:

  • ยืดหยุ่น – เปลี่ยนแปลงได้ง่าย
  • ขยายได้ – เพิ่มฟีเจอร์ใหม่ได้โดยไม่ทำลายเก่า
  • ดูแลรักษาง่าย – เข้าใจได้ง่าย ปรับปรุงง่าย
  • ทดสอบง่าย – เขียน unit test ได้สะดวก

SOLID: ย่อมาจาก 5 หลักการ

S - Single Responsibility Principle (SRP)
O - Open/Closed Principle (OCP)
L - Liskov Substitution Principle (LSP)
I - Interface Segregation Principle (ISP)
D - Dependency Inversion Principle (DIP)

1. Single Responsibility Principle (SRP)

หลักการ: “Class ต้องมีความรับผิดชอบเพียงหนึ่งเดียว”

หมายความว่า: แต่ละ class ควร focus ทำสิ่งหนึ่งเท่านั้น ถ้า class ต้องเปลี่ยน ก็ควรเปลี่ยนด้วยเหตุเพียงหนึ่งเดียว

❌ ไม่เป็นไปตามหลักการ

java// ❌ ไม่ดี: User class ทำหลายอย่าง
public class User {
    private String name;
    private String email;
    
    // Responsibility 1: จัดการข้อมูล user
    public void save() {
        System.out.println("บันทึก user: " + name);
    }
    
    // Responsibility 2: ส่ง email
    public void sendEmail(String message) {
        System.out.println("ส่ง email ไปยัง: " + email);
        System.out.println("เนื้อความ: " + message);
    }
    
    // Responsibility 3: ตรวจสอบข้อมูล
    public boolean validate() {
        return email.contains("@");
    }
}

ปัญหา:

  • เมื่อต้องการแก้วิธีบันทึก user ต้องแก้ class นี้
  • เมื่อต้องการแก้วิธีส่ง email ก็แก้ class นี้อีก
  • เมื่อต้องการแก้ validation ก็แก้ class นี้อีก
  • Class นี้มีหลายเหตุผลที่จะเปลี่ยน → หลาย bug เสี่ยง

✓ ตามหลักการ SRP

java// ✓ ดี: แยกความรับผิดชอบ

// Class 1: จัดการข้อมูล user เท่านั้น
public class User {
    private String name;
    private String email;
    
    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }
    
    public String getName() { return name; }
    public String getEmail() { return email; }
}

// Class 2: บันทึก user
public class UserRepository {
    public void save(User user) {
        System.out.println("บันทึก user: " + user.getName());
    }
}

// Class 3: ส่ง email
public class EmailService {
    public void send(String email, String message) {
        System.out.println("ส่ง email ไปยัง: " + email);
        System.out.println("เนื้อความ: " + message);
    }
}

// Class 4: ตรวจสอบข้อมูล
public class UserValidator {
    public boolean validate(User user) {
        return user.getEmail().contains("@");
    }
}

// การใช้งาน
public class Main {
    public static void main(String[] args) {
        User user = new User("John", "[email protected]");
        
        UserValidator validator = new UserValidator();
        if (validator.validate(user)) {
            UserRepository repo = new UserRepository();
            repo.save(user);
            
            EmailService email = new EmailService();
            email.send(user.getEmail(), "ยินดีต้อนรับ!");
        }
    }
}

ข้อดี:

  • แต่ละ class มีความรับผิดชอบเพียงอย่างเดียว
  • เปลี่ยน email logic ไม่ต้องแก้ User class
  • ง่ายต่อการทดสอบ (test) แต่ละ responsibility แยกกัน
  • ง่ายต่อการ reuse (นำไปใช้ที่อื่น)

2. Open/Closed Principle (OCP)

หลักการ: “Class ต้องเปิดให้ขยาย แต่ปิดไม่ให้แก้ไข”

หมายความว่า: คุณต้องสามารถเพิ่มฟีเจอร์ใหม่ได้โดยไม่ต้องแก้ code เดิม

❌ ไม่เป็นไปตามหลักการ

java// ❌ ไม่ดี: ต้องแก้ code เดิมเมื่อเพิ่มประเภทใหม่
public class PaymentProcessor {
    public void process(String paymentType, double amount) {
        if (paymentType.equals("CreditCard")) {
            System.out.println("💳 ประมวลผลบัตรเครดิต: " + amount);
        } else if (paymentType.equals("BankTransfer")) {
            System.out.println("🏦 ประมวลผลโอนเงิน: " + amount);
        } else if (paymentType.equals("Wallet")) {
            System.out.println("💰 ประมวลผล Wallet: " + amount);
        }
        // เมื่อต้องการเพิ่ม Cryptocurrency ต้องแก้ code นี้!
    }
}

ปัญหา:

  • ต้องแก้ PaymentProcessor ทุกครั้งที่เพิ่ม payment type ใหม่
  • เสี่ยง bug จากการแก้ไข
  • Open for modification → ไม่ดี

✓ ตามหลักการ OCP

java// ✓ ดี: ใช้ polymorphism และ interface

// Interface: สัญญา
public interface PaymentMethod {
    void process(double amount);
}

// Implementation 1: Credit Card
public class CreditCardPayment implements PaymentMethod {
    @Override
    public void process(double amount) {
        System.out.println("💳 ประมวลผลบัตรเครดิต: " + amount);
    }
}

// Implementation 2: Bank Transfer
public class BankTransferPayment implements PaymentMethod {
    @Override
    public void process(double amount) {
        System.out.println("🏦 ประมวลผลโอนเงิน: " + amount);
    }
}

// Implementation 3: Wallet
public class WalletPayment implements PaymentMethod {
    @Override
    public void process(double amount) {
        System.out.println("💰 ประมวลผล Wallet: " + amount);
    }
}

// Processor: ไม่ต้องแก้เลย!
public class PaymentProcessor {
    private PaymentMethod method;
    
    public PaymentProcessor(PaymentMethod method) {
        this.method = method;
    }
    
    public void process(double amount) {
        method.process(amount);
    }
}

// ถ้าต้องเพิ่ม Cryptocurrency: เพียงสร้าง class ใหม่ (ขยาย)
public class CryptoPayment implements PaymentMethod {
    @Override
    public void process(double amount) {
        System.out.println("🪙 ประมวลผล Cryptocurrency: " + amount);
    }
}

// การใช้งาน
public class Main {
    public static void main(String[] args) {
        PaymentProcessor processor1 = new PaymentProcessor(new CreditCardPayment());
        processor1.process(1000);
        
        PaymentProcessor processor2 = new PaymentProcessor(new CryptoPayment());
        processor2.process(1000);
        // ✓ ไม่ต้องแก้ PaymentProcessor!
    }
}

Output:

text💳 ประมวลผลบัตรเครดิต: 1000.0
🪙 ประมวลผล Cryptocurrency: 1000.0

ข้อดี:

  • เพิ่มประเภท payment ใหม่ไม่ต้องแก้ PaymentProcessor
  • Open for extension (เพิ่ม class ใหม่)
  • Closed for modification (ไม่แก้ code เดิม)

3. Liskov Substitution Principle (LSP)

หลักการ: “Subclass ต้องสามารถแทนที่ Superclass ได้”

หมายความว่า: ถ้า B เป็น subclass ของ A คุณควรสามารถใช้ B ทุกที่ที่ใช้ A ได้ โดยไม่ทำลาย logic

❌ ไม่เป็นไปตามหลักการ

java// ❌ ไม่ดี: Penguin ทำลาย LSP
public class Bird {
    public void fly() {
        System.out.println("บิน!");
    }
}

public class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("เพนกวินไม่บินได้!");
    }
}

// การใช้งาน - มีปัญหา!
public class Main {
    public static void main(String[] args) {
        Bird bird = new Penguin();
        bird.fly();  // ❌ Exception! ไม่สามารถใช้ Penguin แทน Bird
    }
}

✓ ตามหลักการ LSP

java// ✓ ดี: ออกแบบให้ถูกต้องตั้งแต่แรก

public class Animal {
    public void move() {
        System.out.println("เคลื่อนไหว");
    }
}

public class Bird extends Animal {
    @Override
    public void move() {
        System.out.println("บิน!");
    }
}

public class Penguin extends Animal {
    @Override
    public void move() {
        System.out.println("ว่ายน้ำ!");
    }
}

public class Dog extends Animal {
    @Override
    public void move() {
        System.out.println("วิ่ง!");
    }
}

// การใช้งาน - ปลอดภัย!
public class Main {
    public static void makeAnimalMove(Animal animal) {
        animal.move();  // ✓ ใช้ได้กับ subclass ใดๆ
    }
    
    public static void main(String[] args) {
        makeAnimalMove(new Bird());      // บิน!
        makeAnimalMove(new Penguin());   // ว่ายน้ำ!
        makeAnimalMove(new Dog());       // วิ่ง!
    }
}

Output:

textบิน!
ว่ายน้ำ!
วิ่ง!

คำอธิบาย:

  • ทุก subclass ของ Animal สามารถใช้ได้แทน Animal
  • ไม่มี exception หรือพฤติกรรมที่ไม่คาดหวัง
  • ความสัมพันธ์ IS-A ทำงานได้ถูกต้อง

4. Interface Segregation Principle (ISP)

หลักการ: “ต้องให้ client ใช้ interface เฉพาะที่ต้องการ ไม่ใช่ interface ใหญ่ที่บังคับให้ implement ของที่ไม่ต้องใช้”

❌ ไม่เป็นไปตามหลักการ

java// ❌ ไม่ดี: Interface ใหญ่เกินไป
public interface Worker {
    void work();
    void eat();
    void takeBreak();
}

// Human worker ต้อง implement ทั้งหมด
public class HumanWorker implements Worker {
    @Override
    public void work() { System.out.println("ทำงาน"); }
    
    @Override
    public void eat() { System.out.println("กิน"); }
    
    @Override
    public void takeBreak() { System.out.println("พัก"); }
}

// Robot ต้อง implement eat() ที่ไม่สำคัญ
public class Robot implements Worker {
    @Override
    public void work() { System.out.println("ทำงาน"); }
    
    @Override
    public void eat() {
        throw new UnsupportedOperationException("หุ่นยนต์ไม่กินข้าว");
    }
    
    @Override
    public void takeBreak() {
        throw new UnsupportedOperationException("หุ่นยนต์ไม่พัก");
    }
}

✓ ตามหลักการ ISP

java// ✓ ดี: แยก interface ให้เฉพาะเจาะจง

public interface Workable {
    void work();
}

public interface Eatable {
    void eat();
}

public interface Breakable {
    void takeBreak();
}

// Human: implement ทั้งหมด
public class HumanWorker implements Workable, Eatable, Breakable {
    @Override
    public void work() { System.out.println("ทำงาน"); }
    
    @Override
    public void eat() { System.out.println("กิน"); }
    
    @Override
    public void takeBreak() { System.out.println("พัก"); }
}

// Robot: implement เฉพาะที่ต้องการ
public class Robot implements Workable {
    @Override
    public void work() { System.out.println("หุ่นยนต์ทำงาน"); }
}

// การใช้งาน
public class Main {
    public static void main(String[] args) {
        Workable human = new HumanWorker();
        Workable robot = new Robot();
        
        human.work();  // ทำงาน
        robot.work();  // หุ่นยนต์ทำงาน
        
        if (human instanceof Eatable) {
            ((Eatable) human).eat();  // กิน
        }
    }
}

Output:

textทำงาน
หุ่นยนต์ทำงาน
กิน

ข้อดี:

  • class ต้อง implement เฉพาะ method ที่ต้องใช้
  • ไม่มี method ที่ throw exception ไร้สาระ
  • ยืดหยุ่นมากขึ้น

5. Dependency Inversion Principle (DIP)

หลักการ: “High-level modules ไม่ควรขึ้นอยู่กับ low-level modules ทั้งสองควรขึ้นอยู่กับ abstraction”

❌ ไม่เป็นไปตามหลักการ

java// ❌ ไม่ดี: ขึ้นอยู่ตรงๆ
public class EmailService {
    public void sendEmail(String email, String message) {
        System.out.println("ส่ง email: " + message);
    }
}

public class OrderService {
    private EmailService emailService = new EmailService();  // ← ผูกตรงๆ
    
    public void placeOrder(String email) {
        System.out.println("สั่งซื้อ");
        emailService.sendEmail(email, "สำเร็จ");
    }
}

✓ ตามหลักการ DIP

java// ✓ ดี: ขึ้นอยู่กับ abstraction

public interface NotificationService {
    void notify(String recipient, String message);
}

public class EmailService implements NotificationService {
    @Override
    public void notify(String recipient, String message) {
        System.out.println("ส่ง email: " + message);
    }
}

public class OrderService {
    private NotificationService notificationService;  // ← ใช้ interface
    
    public OrderService(NotificationService service) {
        this.notificationService = service;
    }
    
    public void placeOrder(String recipient) {
        System.out.println("สั่งซื้อ");
        notificationService.notify(recipient, "สำเร็จ");
    }
}

// การใช้งาน
public class Main {
    public static void main(String[] args) {
        OrderService service = new OrderService(new EmailService());
        service.placeOrder("[email protected]");
    }
}

Output:

textสั่งซื้อ
ส่ง email: สำเร็จ

ตัวอย่างรวม: ยา “ไม่ SOLID” กับ “SOLID”

❌ Code ที่ไม่ SOLID

javapublic class PaymentAndShipping {
    // ❌ SRP: ทำมากเกินไป
    // ❌ OCP: ต้องแก้ทุกครั้งที่เพิ่ม payment type
    // ❌ DIP: ผูกตรงๆ กับ implementation
    
    public void processPaymentAndShip(String paymentType, String address) {
        if (paymentType.equals("CreditCard")) {
            System.out.println("💳 บัตรเครดิต");
        } else if (paymentType.equals("BankTransfer")) {
            System.out.println("🏦 โอนเงิน");
        }
        System.out.println("📦 ส่งไปที่: " + address);
    }
}

✓ Code ที่ SOLID

java// SRP: แยก responsibility
public class PaymentProcessor {
    private PaymentMethod paymentMethod;
    
    public PaymentProcessor(PaymentMethod method) {
        this.paymentMethod = method;
    }
    
    public void process() {
        paymentMethod.process();
    }
}

public class ShippingService {
    public void ship(String address) {
        System.out.println("📦 ส่งไปที่: " + address);
    }
}

// OCP: เปิดขยาย ปิดแก้ไข
public interface PaymentMethod {
    void process();
}

public class CreditCardPayment implements PaymentMethod {
    public void process() { System.out.println("💳 บัตรเครดิต"); }
}

public class BankTransferPayment implements PaymentMethod {
    public void process() { System.out.println("🏦 โอนเงิน"); }
}

// DIP: ขึ้นอยู่กับ abstraction
public class OrderService {
    private PaymentProcessor paymentProcessor;
    private ShippingService shippingService;
    
    public OrderService(PaymentMethod method, ShippingService shipping) {
        this.paymentProcessor = new PaymentProcessor(method);
        this.shippingService = shipping;
    }
    
    public void placeOrder(String address) {
        paymentProcessor.process();
        shippingService.ship(address);
    }
}

public class Main {
    public static void main(String[] args) {
        OrderService order = new OrderService(
            new CreditCardPayment(),
            new ShippingService()
        );
        order.placeOrder("123 Main St");
    }
}

Output:

text💳 บัตรเครดิต
📦 ส่งไปที่: 123 Main St

สรุป

SOLID เป็น 5 หลักการพื้นฐานที่ช่วยให้ code ของเรา:

  1. S (SRP) – แต่ละ class ควรมีความรับผิดชอบเพียงเดียว → ง่ายต่อการแก้ไข
  2. O (OCP) – เปิดให้ขยาย แต่ปิดไม่ให้แก้ไข → ปลอดภัยเมื่อเพิ่มฟีเจอร์
  3. L (LSP) – Subclass ต้องแทนที่ superclass ได้ → ใช้ polymorphism ถูกต้อง
  4. I (ISP) – Interface ต้องเฉพาะเจาะจง → ไม่บังคับให้ implement ของที่ไม่ต้อง
  5. D (DIP) – ขึ้นอยู่กับ abstraction ไม่ใช่ implementation → ยืดหยุ่น

เมื่อคุณปฏิบัติตาม SOLID principles code จะกลายเป็น “สิ่งที่มีชีวิต” – สามารถเติบโต ปรับตัว และขยายได้โดยไม่เสียสมดุล คำพูดของ Uncle Bob (ผู้ริเริ่ม SOLID) ว่า “A design is SOLID when it allows a system to be easy to maintain and extend over time” นั่นคือเป้าหมายของเรา