ข้ามไปยังเนื้อหา

หลักการ SOLID

SOLID เป็นตัวย่อของห้าหลักการในการออกแบบเชิง object ทั้งหมดมีจุดมุ่งหมายเดียวกัน: code ที่ดูดซับการเปลี่ยนแปลงได้โดยไม่พัง และที่คุณสามารถทดสอบแยกเดี่ยวได้ เกือบทุก pattern ในคอร์สนี้ในเวลาต่อมาแท้จริงแล้วก็คือหนึ่งในหลักการเหล่านี้ที่นำไปประยุกต์กับปัญหาเฉพาะ ดังนั้นการเรียนรู้หลักการเหล่านี้ตอนนี้หมายความว่า pattern ต่าง ๆ จะให้ความรู้สึกคุ้นเคยมากกว่าจะดูเหมือนถูกตั้งขึ้นมาลอย ๆ

class ควรมีเหตุผลเดียวที่จะเปลี่ยนแปลง ถ้า class หนึ่งทั้งจัดรูปแบบรายงานและส่งรายงานนั้นทางอีเมล การเปลี่ยนแปลงตรรกะของอีเมลก็เสี่ยงที่จะทำให้การจัดรูปแบบพัง และในทางกลับกันก็เช่นกัน การแยกความกังวลเหล่านั้นออกเป็น class แยกกันหมายความว่าแต่ละอันมีงานที่ชัดเจนเพียงงานเดียว — และมีแรงผลักดันเดียวที่จะทำให้ต้องเปลี่ยนแปลง

ซอฟต์แวร์ควรเปิดสำหรับการต่อขยายแต่ปิดสำหรับการแก้ไข คุณควรสามารถเพิ่มพฤติกรรมใหม่ได้ด้วยการเพิ่ม code ใหม่ ไม่ใช่ด้วยการแก้ไข code ที่ผ่านการทดสอบและพิสูจน์มาแล้ว payment processor ที่ switch บน string ของประเภทการชำระเงินต้องถูกแก้ไขสำหรับทุกวิธีใหม่ ส่วนตัวที่รับ interface PaymentMethod จะได้วิธีใหม่ ๆ มาด้วยการเพิ่ม class โดยปล่อยให้ตัว processor ไม่ถูกแตะต้อง

subtype ใด ๆ ต้องสามารถใช้งานได้ในทุกที่ที่คาดหวัง base type ของตัวเอง โดยไม่สร้างความประหลาดใจให้ผู้เรียก ถ้า subclass Square ของ Rectangle เปลี่ยนทั้งสองมิติอย่างเงียบ ๆ เมื่อคุณตั้งค่าความกว้าง code ที่ใช้งานได้กับ rectangle ก็จะพังกับ square subtype นั้นทำลายสัญญา การ substitution ต้องซื่อตรง

client ไม่ควรถูกบังคับให้ต้องพึ่งพา method ที่ตัวเองไม่ได้ใช้ interface Machine ที่อ้วนเทอะทะด้วย print, scan และ fax บังคับให้เครื่องพิมพ์ธรรมดาต้อง implement fax ที่ทำไม่ได้ interface ที่เล็กและโฟกัสหลาย ๆ อันช่วยให้แต่ละ client พึ่งพาสิ่งที่ต้องการเป๊ะ ๆ

นโยบายระดับสูงไม่ควรพึ่งพารายละเอียดระดับล่าง ทั้งสองควรพึ่งพา abstraction ด้านล่างนี้ NotificationService พึ่งพา interface MessageSender แทนที่จะเป็น class อีเมลที่เป็นรูปธรรม การสลับไปใช้ SMS — หรือตัวปลอมสำหรับการทดสอบ — ไม่ต้องแก้ตัว service เลยแม้แต่น้อย

interface MessageSender {
send(to: string, body: string): void;
}
class EmailSender implements MessageSender {
send(to: string, body: string): void {
console.log(`email to ${to}: ${body}`);
}
}
class NotificationService {
// Depends on the abstraction, not a concrete sender.
constructor(private readonly sender: MessageSender) {}
notify(user: string): void {
this.sender.send(user, 'Your order has shipped');
}
}
const service = new NotificationService(new EmailSender());
service.notify('[email protected]');

ไดอะแกรมด้านล่างเปรียบเทียบเวอร์ชันที่แข็งทื่อ ซึ่งนโยบายระดับสูงชี้ตรงไปที่รายละเอียดที่เป็นรูปธรรม กับเวอร์ชันที่กลับทิศแล้ว ซึ่งทั้งสองฝั่งพึ่งพา abstraction

classDiagram
  class NotificationService
  class MessageSender {
    <<interface>>
    +send(to, body)
  }
  class EmailSender
  class SmsSender
  NotificationService --> MessageSender : depends on abstraction
  EmailSender ..|> MessageSender : implements
  SmsSender ..|> MessageSender : implements
Dependency Inversion: นโยบายและรายละเอียดต่างพึ่งพา interface
Single Responsibility Principle ระบุว่าอะไร?
Open/Closed Principle บอกว่าซอฟต์แวร์ควรจะ:
ในตัวอย่าง Dependency Inversion NotificationService พึ่งพาอะไร?
หลักการใดถูกละเมิดเมื่อ subtype ทำลายพฤติกรรมที่ผู้เรียกคาดหวังจาก base type ของตัวเอง?