Skip to main content
0

สารจากนักเขียน

S.O.L.I.D คืออะไรแล้วแต่ละตัวมีความหมายอย่างไร ไปดูวววว

เขียนโดย
Thapanon Sodngam
Junior Software Developer

บทความนี้ตีพิมพ์ และ เผยแพร่เมื่อ 24 กรกฎาคม 2566

S.O.L.I.D คืออะไรแล้วแต่ละตัวมีความหมายอย่างไร ไปดูวววว

โดยคำว่า S.O.L.I.D  นั้นเป็นการรวมกันของอักษรตัวแรกในแต่ละ principle นั่นเองและแต่ละตัวก็จะมีความหมายดังต่อไปนี้

S — Single-Responsibility principle

O — Open/Closed principle

L — Liskov Substitution principle

I — Interface Segregation principle

D — Dependency Inversion principle

 

ต่อไปก็จะขยายความของแต่ล่ะหลักการไปทีละหลักการให้ทุกคนได้ค่อย ๆ เข้าใจกันนะ

Single-Responsibility : โดยหลักการนี้จะว่าด้วยการที่ Class นึงนั้นควรจะมีหน้าท่ีของตัวเองแค่อย่างเดียวเท่านั้น

Bad Example
```
class User {
  private username: string;
  private password: string;

  constructor(username: string, password: string) {
    this.username = username;
    this.password = password;
  }

  login(): void {
    // Logic to handle user login
  }

  save(): void {
    // Logic to save user to the database
  }

  sendEmail(): void {
    // Logic to send an email to the user
  }
}

จาก Code ด้านบนจะเห็นได้ว่า Class User นั้น มี Function สำหรับการส่ง Email และสำหรับ Save User ไปยัง Database ด้วย ซึ่งว่าด้วยหลักการออกแบบของ Single-Responsibility Class User นี้ทำหน้าที่ 3 อย่างเลย ทางที่ดีควร มี Class ที่ทำหน้าที่แยกไปแต่ละอย่างเลยนั่นเอง

Good Example
```
class User {
  private username: string;
  private password: string;

  constructor(username: string, password: string) {
    this.username = username;
    this.password = password;
  }

  login(): void {
    // Logic to handle user login
  }
}

class UserRepository {
  save(user: User): void {
    // Logic to save user to the database
  }
}

class EmailService {
  sendEmail(user: User): void {
    // Logic to send an email to the user
  }
}

จาก Code ด้านบนจะเป็นการออกแบบที่แก้ไขการทำงานของ Class ตาม Single-Responsibility principle โดยแยก การ ส่ง Email มาเป็น Class EmailService และการ Save ไปยัง Database ก็คือหน้าที่ของ Class UserRepository นั่นเอง

ต่อไปเรามาดูข้อที่ 2 

Open/Closed principle : โดยข้อนี้คือการว่าด้วยการออกแบบให้ Class นั้นแก้ไขได้ยากแต่สามารถนำไปใช้งานต่อได้ง่ายนั่นเอง

Bad Eample
```
enum ShapeType {
  Circle,
  Rectangle,
  Triangle
}

class Shape {
  private type: ShapeType;

  constructor(type: ShapeType) {
    this.type = type;
  }

  calculateArea(): number {
    if (this.type === ShapeType.Circle) {
      // Calculation for circle area
    } else if (this.type === ShapeType.Rectangle) {
      // Calculation for rectangle area
    } else if (this.type === ShapeType.Triangle) {
      // Calculation for triangle area
    }
  }
}
```

จากด้านบนก็จะเป็น Code จากตัวอย่างยอดฮิตหรือนั่นก็คือ Class Shape นั่นเองโดย Class จะมี Function สำหรับ คำนวณพื้นที่แต่ใน Function สำหรับคำนวณนั้นเราจะต้อง แยก Type ของรูปทรงก่อนว่ามันคำนวณยังไงทำให้เวลาที่ Class Shape จะทำงานในแต่ละที่อาจจะมีตัวแปรที่เกี่ยวข้องเยอะมาก ๆ แล้วการจะแก้ไข สูตรในการคำนวณแต่ละที่ก็จะกระทบข้อมูลทั้งหมดใน Class อีกด้วย

Good Example
```
abstract class Shape {
  abstract calculateArea(): number;
}

class Circle extends Shape {
  private radius: number;

  constructor(radius: number) {
    super();
    this.radius = radius;
  }

  calculateArea(): number {
    return Math.PI * this.radius * this.radius;
  }
}

class Rectangle extends Shape {
  private width: number;
  private height: number;

  constructor(width: number, height: number) {
    super();
    this.width = width;
    this.height = height;
  }

  calculateArea(): number {
    return this.width * this.height;
  }
}

class Triangle extends Shape {
  private base: number;
  private height: number;

  constructor(base: number, height: number) {
    super();
    this.base = base;
    this.height = height;
  }

  calculateArea(): number {
    return (this.base * this.height) / 2;
  }
}

จาก Code ด้านบนจะเห็นได้ว่าเราสร้าง abstract class Shape ขึ้นมาเพื่อให้ Class ลูกแต่ละตัวนำไปต่อยอดตามแบบของตัวเองได้หรือจะทำออกมาในรูปแบบของ Interface ก็ได้นะ และเมื่อเวลาที่เรานั้นต้องการแก้ Code ในส่วนของ Class ลูกตัวใดตัวหนึ่ง ลูกตัวอื่น ๆ ก็จะไม่ได้รับผลกระทบไปด้วยนั่นเอง

ต่อไปข้อที่ 3
Liskov Substitution principle: โดยหลักการนี้นั้นกล่าวไว้ว่า Class ลูกนั้นจะสามารถทำหน้าที่แทน Class แม่ได้

Example
```
class Shape {
  getArea(): number {
    throw new Error('Method not implemented');
  }
}

class Rectangle extends Shape {
  private width: number;
  private height: number;

  constructor(width: number, height: number) {
    super();
    this.width = width;
    this.height = height;
  }

  getWidth(): number {
    return this.width;
  }

  getHeight(): number {
    return this.height;
  }

  getArea(): number {
    return this.width * this.height;
  }
}

class Square extends Shape {
  private side: number;

  constructor(side: number) {
    super();
    this.side = side;
  }

  getSide(): number {
    return this.side;
  }

  getArea(): number {
    return this.side * this.side;
  }
}

จาก Code จะเห็นได้ว่า Class Rectangle และ Square นั้นเป็น Class ลูกของ Shape ดังนั้น ทั้ง Class Rectangle และ Square จะมี Function ที่สืบทอดต่อมาจาก Shape นั่นเองและต่อให้เราไม่ได้ Implement getArea() ใน Class ลูกก็ตาม เราก็ยังสามารถเรียกใช้ getArea() ได้อยู่ดีแต่แค่โปรแกรมจะ Throw ออกมาว่า “`Method not implemented “` ตามที่ class แม่ได้ Implement ไว้นั่นเอง

ไปต่อกับข้อที่ 4
Interface Segregation principle: เป็นหลักการที่ว่า Class ทุกตัวนั้นไม่ควรจะโดนบังคับให้ Implement Interface ในส่วนที่ตัวเองนั้นไม่ได้มีโอกาสจะได้ใช้งาน

Bad Example
```
interface IWorker {
  doWork(): void;
  takeBreak(): void;
  attendMeeting(): void;
}

class Employee implements IWorker {
  doWork(): void {
    // Implementation of work logic
  }

  takeBreak(): void {
    // Implementation of break logic
  }

  attendMeeting(): void {
    // Implementation of meeting logic
  }
}

class Robot implements IWorker {
  doWork(): void {
    // Implementation of work logic
  }

  takeBreak(): void {
    throw new Error("Robots don't take breaks.");
  }

  attendMeeting(): void {
    throw new Error("Robots don't attend meetings.");
  }
}

จาก Code จะเห็นได้ว่า มีการสร้าง Interface ที่มี Function หลายตัวมาก ๆ แล้ว Class ที่ Implement ตาม Interface นั้นจะ Implement function เหล่านี้ทั้งหมดแม้ว่ามันจะไม่ต้องใช้เลยก็ตาม

Good Example
```
interface IWorker {
  doWork(): void;
}

interface IBreakable {
  takeBreak(): void;
}

interface IMeetingAttendee {
  attendMeeting(): void;
}

class Employee implements IWorker, IBreakable, IMeetingAttendee {
  doWork(): void {
    // Implementation of work logic
  }

  takeBreak(): void {
    // Implementation of break logic
  }

  attendMeeting(): void {
    // Implementation of meeting logic
  }
}

class Robot implements IWorker {
  doWork(): void {
    // Implementation of work logic
  }
}

และนี่ก็คือการออกแบบ โดยตามหลักการของ Interface Segregation โดยแยก Function ออกเป็น Interface ของแต่ละตัวไปเลย แล้วก็ไป Implement ตาม Interface ของ Class ที่เรากำหนดไว้เองเลย ตัวไหนที่ไม่ได้ใช้แน่ ๆ ก็ไม่ต้องดึงเอามาให้รกอีกต่อไป

และต่อมาข้อสุดท้าย

Dependency Inversion principle: โดยข้อนี้คือควรจะทำให้หน่วยของ Module หรือ Class ระดับสูงขึ้นอยู่กับ abstract Class หรือ Interface แทน จากเดิมที่จะขึ้นอยู่กับ Concrete class

 

Bad Example
```
class Order {
  private paymentProcessor: PaymentProcessor;

  constructor() {
    this.paymentProcessor = new PaymentProcessor();
  }

  processPayment(amount: number): void {
    this.paymentProcessor.process(amount);
  }
}

class PaymentProcessor {
  process(amount: number): void {
    // Logic to process the payment
  }
}

จาก Code นั้นจะเห็นได้ว่า paymentProcessor ใน class Order จะขึ้นอยู่กับ Class PaymentProcessor ที่เป็น concrete class นั่นเอง

Good Example
```
interface PaymentProcessor {
  process(amount: number): void;
}

class Order {
  private paymentProcessor: PaymentProcessor;

  constructor(paymentProcessor: PaymentProcessor) {
    this.paymentProcessor = paymentProcessor;
  }

  processPayment(amount: number): void {
    this.paymentProcessor.process(amount);
  }
}

class CreditCardPaymentProcessor implements PaymentProcessor {
  process(amount: number): void {
    // Logic to process the payment using a credit card
  }
}

class PayPalPaymentProcessor implements PaymentProcessor {
  process(amount: number): void {
    // Logic to process the payment using PayPal
  }
}

จาก Code จะเห็นได้ว่า paymentProcessor ใน Order นั้น จะเปลี่ยนมาขึ้นอยู่กับ Interface แล้ว ทำให้เวลาที่เรานำ class Order ไปใช้งาน processPayment ของ class Order จะขึ้นอยู่ กับ Class ระดับต่ำกว่าที่เรา Implement ตาม Interface ไว้นั่นเอง

และนี่ก็คือทั้งหมดของ S.O.L.I.D Principles นั่นเองหวังว่าเพื่อน ๆ จะได้ความรู้และสามารถนำไปใช้กับการทำงานของตัวเองได้นะครับบบ

ระบบฝึกทักษะ การเขียนโปรแกรม

ที่พร้อมตรวจผลงานคุณ 24 ชั่วโมง

  • โจทย์ปัญหากว่า 200 ข้อ ที่รอท้าทายคุณอยู่
  • รองรับ 9 ภาษาโปรแกรมหลัก ไม่ว่าจะ Java, Python, C ก็เขียนได้
  • ใช้งานได้ฟรี ! ครบ 20 ข้อขึ้นไป รับ Certificate ไปเลย !!
เข้าใช้งานระบบ DevLab ฟรี !เรียนรู้เพิ่มเติม

เรียนรู้ไอที “อัพสกิลเขียนโปรแกรม” จากตัวจริง
ปั้นให้คุณเป็น คนสายไอทีระดับมืออาชีพ

BorntoDev

Author BorntoDev

BorntoDev Co., Ltd.

More posts by BorntoDev

เราใช้คุกกี้เพื่อพัฒนาประสิทธิภาพ และประสบการณ์ที่ดีในการใช้เว็บไซต์ของคุณ คุณสามารถศึกษารายละเอียดได้ที่ นโยบายความเป็นส่วนตัว และสามารถจัดการความเป็นส่วนตัวเองได้ของคุณได้เองโดยคลิกที่ ตั้งค่า

ตั้งค่าความเป็นส่วนตัว

คุณสามารถเลือกการตั้งค่าคุกกี้โดยเปิด/ปิด คุกกี้ในแต่ละประเภทได้ตามความต้องการ ยกเว้น คุกกี้ที่จำเป็น

ยอมรับทั้งหมด
จัดการความเป็นส่วนตัว
  • คุกกี้ที่จำเป็น
    เปิดใช้งานตลอด

    ประเภทของคุกกี้มีความจำเป็นสำหรับการทำงานของเว็บไซต์ เพื่อให้คุณสามารถใช้ได้อย่างเป็นปกติ และเข้าชมเว็บไซต์ คุณไม่สามารถปิดการทำงานของคุกกี้นี้ในระบบเว็บไซต์ของเราได้
    รายละเอียดคุกกี้

  • คุกกี้สำหรับการติดตามทางการตลาด

    ประเภทของคุกกี้ที่มีความจำเป็นในการใช้งานเพื่อการวิเคราะห์ และ นำเสนอโปรโมชัน สินค้า รวมถึงหลักสูตรฟรี และ สิทธิพิเศษต่าง ๆ คุณสามารถเลือกปิดคุกกี้ประเภทนี้ได้โดยไม่ส่งผลต่อการทำงานหลัก เว้นแต่การนำเสนอโปรโมชันที่อาจไม่ตรงกับความต้องการ
    รายละเอียดคุกกี้

บันทึกการตั้งค่า