# SOLID Principles ฉบับอธิบายละเอียด

## 1. 📌 Single Responsibility Principle (SRP)

**"คลาสหนึ่งควรมีเหตุผลเดียวในการเปลี่ยนแปลง"**

### คำอธิบาย

หมายความว่าแต่ละคลาสควรทำหน้าที่เดียว มีความรับผิดชอบเดียว ไม่ควรรับผิดชอบหลายเรื่องในคลาสเดียว

### ❌ ตัวอย่างที่ผิด

```javascript
class UserManager {
  constructor(db) {
    this.db = db;
  }

  // รับผิดชอบหลายเรื่อง: Business Logic + Database + Email + Validation
  async createUser(userData) {
    // 1. Validate data
    if (!userData.email || !userData.email.includes("@")) {
      throw new Error("Invalid email");
    }

    // 2. Hash password
    const hashedPassword = this.hashPassword(userData.password);

    // 3. Save to database
    const user = await this.db.users.create({
      ...userData,
      password: hashedPassword,
    });

    // 4. Send welcome email
    const emailContent = `
      <h1>Welcome ${user.name}!</h1>
      <p>Thank you for joining us.</p>
    `;
    await this.sendEmail(user.email, "Welcome!", emailContent);

    // 5. Log activity
    console.log(`User created: ${user.id}`);

    return user;
  }

  hashPassword(password) {
    /* ... */
  }
  sendEmail(to, subject, content) {
    /* ... */
  }
}
```

**ปัญหา:**

- เปลี่ยน email template → ต้องแก้ UserManager
- เปลี่ยน validation rule → ต้องแก้ UserManager
- เปลี่ยน database → ต้องแก้ UserManager
- เปลี่ยน logging → ต้องแก้ UserManager

### ✅ ตัวอย่างที่ถูก

```javascript
// แยกความรับผิดชอบออกเป็นคลาสละคลาส

// 1. รับผิดชอบ Validation เท่านั้น
class UserValidator {
  validate(userData) {
    if (!userData.email || !userData.email.includes("@")) {
      throw new Error("Invalid email");
    }
    if (!userData.password || userData.password.length < 8) {
      throw new Error("Password too short");
    }
    return true;
  }
}

// 2. รับผิดชอบ Database เท่านั้น
class UserRepository {
  constructor(db) {
    this.db = db;
  }

  async save(userData) {
    return await this.db.users.create(userData);
  }

  async findByEmail(email) {
    return await this.db.users.findOne({ email });
  }
}

// 3. รับผิดชอบ Email เท่านั้น
class EmailService {
  async sendWelcomeEmail(user) {
    const template = new WelcomeEmailTemplate(user);
    await this.send(user.email, template.subject, template.content);
  }

  async send(to, subject, content) {
    /* ... */
  }
}

// 4. รับผิดชอบ Security เท่านั้น
class PasswordHasher {
  hash(password) {
    // bcrypt or other hashing algorithm
    return /* hashed password */;
  }
}

// 5. รับผิดชอบประสานงานเท่านั้น (Orchestration)
class UserService {
  constructor(validator, repository, emailService, passwordHasher) {
    this.validator = validator;
    this.repository = repository;
    this.emailService = emailService;
    this.passwordHasher = passwordHasher;
  }

  async createUser(userData) {
    // ใช้ service ต่างๆ ทำงานของมันเอง
    this.validator.validate(userData);

    const hashedPassword = this.passwordHasher.hash(userData.password);

    const user = await this.repository.save({
      ...userData,
      password: hashedPassword,
    });

    await this.emailService.sendWelcomeEmail(user);

    return user;
  }
}
```

**ข้อดี:**

- ✅ แก้ email template → แก้แค่ EmailService
- ✅ แก้ validation → แก้แค่ UserValidator
- ✅ เปลี่ยน database → แก้แค่ UserRepository
- ✅ Test ง่าย เพราะแยกส่วนชัดเจน

---

## 2. 🔓 Open/Closed Principle (OCP)

**"เปิดให้ขยาย แต่ปิดการแก้ไข"**

### คำอธิบาย

เมื่อต้องการฟีเจอร์ใหม่ ควร**เพิ่ม**โค้ดใหม่ ไม่ควร**แก้ไข**โค้ดเก่า

### ❌ ตัวอย่างที่ผิด

```javascript
class PaymentProcessor {
  processPayment(amount, method) {
    if (method === "credit_card") {
      // Credit card logic
      console.log(`Processing ${amount} via Credit Card`);
      // validate card number
      // charge card
    } else if (method === "paypal") {
      // PayPal logic
      console.log(`Processing ${amount} via PayPal`);
      // redirect to PayPal
      // handle callback
    } else if (method === "bank_transfer") {
      // Bank transfer logic
      console.log(`Processing ${amount} via Bank Transfer`);
      // generate QR code
      // wait for confirmation
    }
    // ถ้าต้องการเพิ่ม LINE Pay, PromptPay ต้องมาแก้ if-else ตรงนี้เรื่อยๆ!
  }
}
```

**ปัญหา:**

- ทุกครั้งที่เพิ่ม payment method ใหม่ → ต้องแก้โค้ดเดิม
- เสี่ยงทำให้ payment method เดิมพัง
- ยาก test แยกส่วน

### ✅ ตัวอย่างที่ถูก

```javascript
// สร้าง Interface/Abstract class (ในภาษาที่รองรับ)
class PaymentMethod {
  process(amount) {
    throw new Error("Must implement process method");
  }
}

// แต่ละ payment method เป็นคลาสของตัวเอง
class CreditCardPayment extends PaymentMethod {
  constructor(cardNumber, cvv) {
    super();
    this.cardNumber = cardNumber;
    this.cvv = cvv;
  }

  process(amount) {
    console.log(`Processing ${amount} via Credit Card`);
    // validate card
    // charge card
    return { success: true, transactionId: "CC123" };
  }
}

class PayPalPayment extends PaymentMethod {
  constructor(email) {
    super();
    this.email = email;
  }

  process(amount) {
    console.log(`Processing ${amount} via PayPal`);
    // redirect to PayPal
    // handle callback
    return { success: true, transactionId: "PP456" };
  }
}

class BankTransferPayment extends PaymentMethod {
  constructor(bankAccount) {
    super();
    this.bankAccount = bankAccount;
  }

  process(amount) {
    console.log(`Processing ${amount} via Bank Transfer`);
    // generate QR code
    // wait for confirmation
    return { success: true, transactionId: "BT789" };
  }
}

// ตอนนี้ PaymentProcessor ไม่ต้องแก้ไขอีกแล้ว!
class PaymentProcessor {
  processPayment(paymentMethod, amount) {
    return paymentMethod.process(amount);
  }
}

// การใช้งาน
const processor = new PaymentProcessor();

// ใช้ credit card
processor.processPayment(new CreditCardPayment("1234-5678", "123"), 1000);

// ใช้ PayPal
processor.processPayment(new PayPalPayment("user@email.com"), 2000);

// ต้องการเพิ่ม LINE Pay? แค่สร้างคลาสใหม่ ไม่ต้องแก้โค้ดเดิม!
class LinePayPayment extends PaymentMethod {
  constructor(phoneNumber) {
    super();
    this.phoneNumber = phoneNumber;
  }

  process(amount) {
    console.log(`Processing ${amount} via LINE Pay`);
    return { success: true, transactionId: "LP999" };
  }
}
```

**ข้อดี:**

- ✅ เพิ่ม payment method ใหม่ → สร้างคลาสใหม่เท่านั้น
- ✅ ไม่กระทบโค้ดเดิม → ปลอดภัย
- ✅ Test แยกส่วนได้

---

## 3. 🔄 Liskov Substitution Principle (LSP)

**"Subclass ต้องใช้แทน Parent class ได้โดยไม่ทำให้โปรแกรมพัง"**

### คำอธิบาย

ถ้ามี parent class และ child class แล้ว เราควรใช้ child class แทน parent class ได้เสมอโดยไม่มีปัญหา

### ❌ ตัวอย่างที่ผิด

```javascript
class Bird {
  fly() {
    console.log("Flying in the sky!");
  }
}

class Eagle extends Bird {
  fly() {
    console.log("Eagle soaring high!");
  }
}

class Penguin extends Bird {
  fly() {
    throw new Error("Penguins cannot fly!"); // ❌ ทำลายความคาดหวัง!
  }
}

// การใช้งาน
function makeBirdFly(bird) {
  bird.fly(); // คาดหวังว่าทุก bird จะบินได้
}

const eagle = new Eagle();
const penguin = new Penguin();

makeBirdFly(eagle); // ✅ OK
makeBirdFly(penguin); // ❌ Error! ทำลาย LSP
```

**ปัญหา:**

- เพนกวินไม่สามารถแทนที่นกได้ เพราะมันบินไม่ได้
- ทำให้โค้ดที่ใช้ Bird ต้อง check type ก่อนใช้

### ✅ ตัวอย่างที่ถูก

```javascript
// แยกความสามารถออกมา
class Bird {
  eat() {
    console.log("Eating...");
  }
}

class FlyingBird extends Bird {
  fly() {
    console.log("Flying in the sky!");
  }
}

class SwimmingBird extends Bird {
  swim() {
    console.log("Swimming in water!");
  }
}

// Eagle บินได้
class Eagle extends FlyingBird {
  fly() {
    console.log("Eagle soaring high!");
  }
}

// Penguin ว่ายน้ำได้
class Penguin extends SwimmingBird {
  swim() {
    console.log("Penguin swimming fast!");
  }
}

// การใช้งานที่ถูกต้อง
function makeFlyingBirdFly(bird) {
  if (bird instanceof FlyingBird) {
    bird.fly(); // ปลอดภัย เพราะรู้ว่าบินได้แน่นอน
  }
}

function makeSwimmingBirdSwim(bird) {
  if (bird instanceof SwimmingBird) {
    bird.swim(); // ปลอดภัย เพราะรู้ว่าว่ายน้ำได้แน่นอน
  }
}

const eagle = new Eagle();
const penguin = new Penguin();

makeFlyingBirdFly(eagle); // ✅ OK
makeSwimmingBirdSwim(penguin); // ✅ OK
```

### ตัวอย่างจริง: Rectangle vs Square

```javascript
// ❌ ผิด: Square ละเมิด LSP
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  setWidth(width) {
    this.width = width;
    this.height = width; // บังคับให้เท่ากัน!
  }

  setHeight(height) {
    this.width = height;
    this.height = height; // บังคับให้เท่ากัน!
  }
}

// ทดสอบ
function testRectangle(rectangle) {
  rectangle.setWidth(5);
  rectangle.setHeight(4);
  console.log(rectangle.getArea()); // คาดหวัง 20
}

testRectangle(new Rectangle(0, 0)); // ✅ 20
testRectangle(new Square(0, 0)); // ❌ 16 (ไม่ใช่ 20!)

// ✅ ถูก: แยกออกมา
class Shape {
  getArea() {
    throw new Error("Must implement getArea");
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Shape {
  constructor(side) {
    super();
    this.side = side;
  }

  getArea() {
    return this.side * this.side;
  }
}
```

---

## 4. 🔌 Interface Segregation Principle (ISP)

**"อย่าบังคับให้ใช้ interface ที่ไม่จำเป็น"**

### คำอธิบาย

แทนที่จะมี interface ใหญ่ๆ อันเดียวที่มีทุกอย่าง ควรแยกเป็น interface เล็กๆ หลายอันที่เฉพาะเจาะจง

### ❌ ตัวอย่างที่ผิด

```javascript
// Interface ใหญ่เกินไป
class Worker {
  work() {
    throw new Error("Must implement");
  }

  eat() {
    throw new Error("Must implement");
  }

  sleep() {
    throw new Error("Must implement");
  }

  getSalary() {
    throw new Error("Must implement");
  }
}

// พนักงานมนุษย์ - ใช้ได้หมด
class HumanWorker extends Worker {
  work() {
    console.log("Human working...");
  }

  eat() {
    console.log("Human eating lunch...");
  }

  sleep() {
    console.log("Human sleeping...");
  }

  getSalary() {
    return 30000;
  }
}

// หุ่นยนต์ - ไม่กิน ไม่นอน แต่ถูกบังคับให้ implement!
class RobotWorker extends Worker {
  work() {
    console.log("Robot working 24/7...");
  }

  eat() {
    throw new Error("Robot does not eat!"); // ❌ ต้อง implement แต่ไม่ใช้
  }

  sleep() {
    throw new Error("Robot does not sleep!"); // ❌ ต้อง implement แต่ไม่ใช้
  }

  getSalary() {
    return 0; // ❌ ต้อง implement แต่ไม่ make sense
  }
}
```

**ปัญหา:**

- Robot ต้อง implement method ที่ไม่ใช้
- ถ้าเรียก `robot.eat()` จะ error

### ✅ ตัวอย่างที่ถูก

```javascript
// แยก interface เล็กๆ ตามความต้องการ

class Workable {
  work() {
    throw new Error("Must implement work");
  }
}

class Eatable {
  eat() {
    throw new Error("Must implement eat");
  }
}

class Sleepable {
  sleep() {
    throw new Error("Must implement sleep");
  }
}

class Payable {
  getSalary() {
    throw new Error("Must implement getSalary");
  }
}

// มนุษย์ implement ทุกอย่างที่จำเป็น
class HumanWorker extends Workable {
  work() {
    console.log("Human working...");
  }
}

// มนุษย์สามารถกิน นอน และได้เงินเดือน
class Human extends HumanWorker {
  constructor() {
    super();
    this.eatable = new EatableBehavior();
    this.sleepable = new SleepableBehavior();
    this.payable = new PayableBehavior(30000);
  }

  eat() {
    this.eatable.eat();
  }

  sleep() {
    this.sleepable.sleep();
  }

  getSalary() {
    return this.payable.getSalary();
  }
}

// หุ่นยนต์ implement เฉพาะที่จำเป็น
class RobotWorker extends Workable {
  work() {
    console.log("Robot working 24/7...");
  }

  charge() {
    console.log("Robot charging battery...");
  }
}

// Concrete implementations
class EatableBehavior extends Eatable {
  eat() {
    console.log("Eating lunch...");
  }
}

class SleepableBehavior extends Sleepable {
  sleep() {
    console.log("Sleeping...");
  }
}

class PayableBehavior extends Payable {
  constructor(salary) {
    super();
    this.salary = salary;
  }

  getSalary() {
    return this.salary;
  }
}

// การใช้งาน
const human = new Human();
human.work(); // ✅
human.eat(); // ✅
human.sleep(); // ✅
console.log(human.getSalary()); // ✅

const robot = new RobotWorker();
robot.work(); // ✅
robot.charge(); // ✅
// robot.eat() ไม่มี method นี้ ไม่มีปัญหา!
```

### ตัวอย่างจริงจาก React Component

```javascript
// ❌ ผิด: Props ใหญ่เกินไป
interface UserProfileProps {
  // Personal info
  name: string;
  email: string;
  avatar: string;

  // Settings
  theme: string;
  language: string;
  notifications: boolean;

  // Admin only
  role: string;
  permissions: string[];
  lastLogin: Date;

  // Actions
  onSave: () => void;
  onDelete: () => void;
  onPromote: () => void; // แค่ admin ใช้
  onDemote: () => void;  // แค่ admin ใช้
}

// User ธรรมดาต้องส่ง props ที่ไม่ใช้
<UserProfile
  name="John"
  email="john@example.com"
  onSave={handleSave}
  onDelete={null}    // ❌ ไม่ใช้
  onPromote={null}   // ❌ ไม่ใช้
  onDemote={null}    // ❌ ไม่ใช้
/>

// ✅ ถูก: แยก component เล็กๆ
interface BasicUserInfoProps {
  name: string;
  email: string;
  avatar: string;
}

interface UserSettingsProps {
  theme: string;
  language: string;
  notifications: boolean;
  onSave: () => void;
}

interface AdminControlsProps {
  role: string;
  permissions: string[];
  onPromote: () => void;
  onDemote: () => void;
}

// ใช้เฉพาะที่จำเป็น
<BasicUserInfo name="John" email="john@example.com" avatar="..." />
<UserSettings theme="dark" language="th" onSave={handleSave} />
// Admin Controls แสดงเฉพาะ admin เท่านั้น
```

---

## 5. 🔁 Dependency Inversion Principle (DIP)

**"อย่าพึ่งพา concrete class ให้พึ่งพา abstraction/interface แทน"**

### คำอธิบาย

- High-level module (โค้ดสำคัญ) ไม่ควรพึ่งพา Low-level module (โค้ดรายละเอียด)
- ทั้งคู่ควรพึ่งพา abstraction (interface) เท่านั้น

### ❌ ตัวอย่างที่ผิด

```javascript
// Low-level: Database implementation
class MySQLDatabase {
  connect() {
    console.log("Connecting to MySQL...");
  }

  query(sql) {
    console.log(`MySQL Query: ${sql}`);
    return [{ id: 1, name: "John" }];
  }
}

// High-level: UserService พึ่งพา MySQL โดยตรง
class UserService {
  constructor() {
    this.database = new MySQLDatabase(); // ❌ พึ่งพา concrete class
  }

  getUsers() {
    this.database.connect();
    return this.database.query("SELECT * FROM users");
  }
}

// การใช้งาน
const userService = new UserService();
userService.getUsers();

// ❌ ปัญหา: ถ้าต้องการเปลี่ยนเป็น PostgreSQL ต้องแก้ UserService!
```

**ปัญหา:**

- UserService ผูกติดกับ MySQLDatabase
- เปลี่ยน database ต้องแก้ UserService
- Test ยากเพราะต้องใช้ MySQL จริง

### ✅ ตัวอย่างที่ถูก

```javascript
// 1. สร้าง Abstraction (Interface)
class Database {
  connect() {
    throw new Error("Must implement connect");
  }

  query(sql) {
    throw new Error("Must implement query");
  }
}

// 2. Concrete implementations
class MySQLDatabase extends Database {
  connect() {
    console.log("Connecting to MySQL...");
  }

  query(sql) {
    console.log(`MySQL Query: ${sql}`);
    return [{ id: 1, name: "John" }];
  }
}

class PostgreSQLDatabase extends Database {
  connect() {
    console.log("Connecting to PostgreSQL...");
  }

  query(sql) {
    console.log(`PostgreSQL Query: ${sql}`);
    return [{ id: 1, name: "Jane" }];
  }
}

class MongoDatabase extends Database {
  connect() {
    console.log("Connecting to MongoDB...");
  }

  query(filter) {
    console.log(`MongoDB Query: ${JSON.stringify(filter)}`);
    return [{ id: 1, name: "Bob" }];
  }
}

// 3. High-level: UserService พึ่งพา Interface เท่านั้น
class UserService {
  constructor(database) {
    // ✅ Dependency Injection
    this.database = database;
  }

  getUsers() {
    this.database.connect();
    return this.database.query("SELECT * FROM users");
  }
}

// 4. การใช้งาน - ยืดหยุ่นมาก!
const mysqlDB = new MySQLDatabase();
const postgresDB = new PostgreSQLDatabase();
const mongoDB = new MongoDatabase();

// ใช้ MySQL
const userService1 = new UserService(mysqlDB);
userService1.getUsers();

// เปลี่ยนเป็น PostgreSQL ได้ทันที ไม่ต้องแก้ UserService
const userService2 = new UserService(postgresDB);
userService2.getUsers();

// เปลี่ยนเป็น MongoDB ก็ได้
const userService3 = new UserService(mongoDB);
userService3.getUsers();
```

### ตัวอย่างจริง: Logger System

```javascript
// ❌ ผิด
class OrderService {
  constructor() {
    this.logger = console; // ❌ พึ่งพา console โดยตรง
  }

  createOrder(order) {
    this.logger.log("Creating order..."); // ต้องใช้ console เท่านั้น
    // create order logic
    this.logger.log("Order created");
  }
}

// ✅ ถูก
class Logger {
  log(message) {
    throw new Error("Must implement log");
  }

  error(message) {
    throw new Error("Must implement error");
  }
}

class ConsoleLogger extends Logger {
  log(message) {
    console.log(`[LOG] ${message}`);
  }

  error(message) {
    console.error(`[ERROR] ${message}`);
  }
}

class FileLogger extends Logger {
  constructor(filename) {
    super();
    this.filename = filename;
  }

  log(message) {
    // เขียนลง file
    console.log(`Writing to ${this.filename}: ${message}`);
  }

  error(message) {
    console.log(`Writing ERROR to ${this.filename}: ${message}`);
  }
}

class CloudLogger extends Logger {
  log(message) {
    // ส่งไป cloud service
    console.log(`Sending to Cloud: ${message}`);
  }

  error(message) {
    console.log(`Sending ERROR to Cloud: ${message}`);
  }
}

// OrderService ไม่สนว่า logger เป็นอะไร
class OrderService {
  constructor(logger) {
    // ✅ DI
    this.logger = logger;
  }

  createOrder(order) {
    this.logger.log("Creating order...");
    // create order logic
    this.logger.log("Order created");
  }
}

// ใช้งานยืดหยุ่น
const devService = new OrderService(new ConsoleLogger());
const prodService = new OrderService(new CloudLogger());
const testService = new OrderService(new FileLogger("test.log"));
```

---

## 📊 สรุปเปรียบเทียบ

| หลักการ | จุดประสงค์              | คำถามที่ต้องถาม                             |
| ------- | ----------------------- | ------------------------------------------- |
| **SRP** | แยกความรับผิดชอบ        | คลาสนี้ทำหลายเรื่องเกินไปหรือเปล่า?         |
| **OCP** | ขยายได้ แก้ไม่ได้       | เพิ่มฟีเจอร์ใหม่แล้วต้องแก้โค้ดเก่าหรือไม่? |
| **LSP** | Subclass แทน Parent ได้ | ใช้ child class แทน parent แล้วพังไหม?      |
| **ISP** | Interface เล็ก เฉพาะ    | บังคับ implement สิ่งที่ไม่ใช้หรือไม่?      |
| **DIP** | พึ่งพา Interface        | พึ่งพา concrete class โดยตรงหรือเปล่า?      |

## 🎯 ข้อควรระวัง

1. **อย่า over-engineering**: ไม่ต้องใช้ SOLID ทุกที่ โค้ดง่ายๆ อาจไม่ต้องการ
2. **ใช้เมื่อจำเป็น**: โค้ดที่ซับซ้อน มีการเปลี่ยนแปลงบ่อย ค่อยใช้ SOLID
3. **Balance**: หาจุดสมดุลระหว่างความยืดหยุ่นกับความซับซ้อน
4. **Refactoring**: โค้ดเก่าไม่ต้องทำใหม่หมด ค่อยๆ refactor ตามความจำเป็น
