บทนำ: ทำไมต้องมี “หลักการ” ในการเขียน 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 ของเรา:
- S (SRP) – แต่ละ class ควรมีความรับผิดชอบเพียงเดียว → ง่ายต่อการแก้ไข
- O (OCP) – เปิดให้ขยาย แต่ปิดไม่ให้แก้ไข → ปลอดภัยเมื่อเพิ่มฟีเจอร์
- L (LSP) – Subclass ต้องแทนที่ superclass ได้ → ใช้ polymorphism ถูกต้อง
- I (ISP) – Interface ต้องเฉพาะเจาะจง → ไม่บังคับให้ implement ของที่ไม่ต้อง
- 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” นั่นคือเป้าหมายของเรา
