メインコンテンツへ移動

เนื้อหาภาษาไทย / タイ語のコンテンツ

システムガイド 内容確認済み

Interface Segregation Principle (ISP): Interface ควรเฉพาะเจาะจง

บทความนี้อธิบายถึง Interface Segregation Principle (ISP) ซึ่งเน้นว่า Client ไม่ควรถูกบังคับให้ Implement Interface ที่ตนเองไม่ได้ใช้ โดยแนะนำให้แบ่ง Interface ท...

Interface Segregation Principle (ISP): Interface ควรเฉพาะเจาะจง

ในบทที่แล้ว เราได้เรียนรู้เกี่ยวกับ Liskov Substitution Principle (LSP) ซึ่งเน้นความสำคัญของการที่ Class ลูกต้องสามารถถูกใช้แทนที่ Class แม่ได้โดยไม่มีปัญหา ในบทนี้ เราจะมาทำความเข้าใจอีกหนึ่งหลักการสำคัญในการออกแบบเชิงวัตถุ นั่นคือ Interface Segregation Principle (ISP) ซึ่งจะช่วยให้เราออกแบบ Interface ได้อย่างมีประสิทธิภาพและลดความซับซ้อนของโค้ด

ความหมายของ ISP

Interface Segregation Principle (ISP) กล่าวว่า "Client ไม่ควรถูกบังคับให้ต้อง Implement Interface ที่ตนเองไม่ได้ใช้" พูดง่ายๆ คือ Interface ที่ใหญ่เกินไป (Fat Interface) ซึ่งมี Methods มากมาย อาจทำให้ Class ที่ Implement Interface นั้น ต้อง Implement Methods ที่ตนเองไม่ต้องการใช้งาน ซึ่งเป็นการละเมิดหลักการ ISP

ลองจินตนาการว่าคุณมี Interface ที่ชื่อว่า IWorker ซึ่งมี Methods สำหรับทำงานทั่วไป (work()) และกิน (eat()) หากคุณมี Class ที่ชื่อว่า Robot ซึ่งสามารถทำงานได้ แต่ไม่จำเป็นต้องกิน คุณจะพบว่า Robot ต้อง Implement Method eat() โดยที่ไม่รู้ว่าจะต้องทำอะไร ซึ่งเป็นการละเมิด ISP

ตัวอย่างการละเมิด ISP

พิจารณา Interface ต่อไปนี้:

interface IWorker {
    public function work(): void;
    public function eat(): void;
    public function sleep(): void;
}

class Human implements IWorker {
    public function work(): void {
        // ทำงาน
    }

    public function eat(): void {
        // กิน
    }

    public function sleep(): void {
        // นอน
    }
}

class Robot implements IWorker {
    public function work(): void {
        // ทำงาน
    }

    public function eat(): void {
        // หุ่นยนต์ไม่กิน
        // อาจจะ throw Exception หรือปล่อยว่างไว้
    }

    public function sleep(): void {
        // หุ่นยนต์ไม่นอน
        // อาจจะ throw Exception หรือปล่อยว่างไว้
    }
}

ในตัวอย่างนี้ IWorker เป็น Fat Interface ที่มี Methods work(), eat(), และ sleep() Class Robot ต้อง Implement Methods eat() และ sleep() ทั้งๆ ที่หุ่นยนต์ไม่จำเป็นต้องกินหรือนอน การทำเช่นนี้เป็นการละเมิด ISP และทำให้โค้ดซับซ้อนโดยไม่จำเป็น

เหตุผลที่การละเมิด ISP เป็นปัญหา:

  • Coupling ที่ไม่จำเป็น: ทำให้ Class Robot ต้องขึ้นอยู่กับ Interface ที่มี Methods ที่ตนเองไม่ต้องการ
  • ความซับซ้อน: เพิ่มความซับซ้อนให้กับ Class โดยไม่จำเป็น ต้อง Implement Methods ที่ไม่ได้ใช้
  • การบำรุงรักษาที่ยากขึ้น: เมื่อ Interface เปลี่ยนแปลง Class ที่ Implement Interface นั้นทั้งหมดจะต้องถูกแก้ไข แม้ว่าการเปลี่ยนแปลงนั้นจะไม่เกี่ยวข้องกับ Class นั้นก็ตาม

การแก้ไข ISP

วิธีแก้ไขปัญหา ISP คือการแบ่ง Interface ที่ใหญ่เกินไปออกเป็น Interface ที่เล็กลงและเฉพาะเจาะจงมากขึ้น ในตัวอย่างก่อนหน้า เราสามารถแบ่ง IWorker ออกเป็นสาม Interfaces ได้ดังนี้:

interface IWorkable {
    public function work(): void;
}

interface IEatable {
    public function eat(): void;
}

interface ISleepable {
    public function sleep(): void;
}

class Human implements IWorkable, IEatable, ISleepable {
    public function work(): void {
        // ทำงาน
    }

    public function eat(): void {
        // กิน
    }

    public function sleep(): void {
        // นอน
    }
}

class Robot implements IWorkable {
    public function work(): void {
        // ทำงาน
    }
}

ในตัวอย่างนี้ เราได้แบ่ง IWorker ออกเป็น IWorkable, IEatable, และ ISleepable Class Robot Implement เฉพาะ IWorkable ซึ่งเป็น Interface ที่เกี่ยวข้องกับพฤติกรรมของหุ่นยนต์เท่านั้น ทำให้โค้ดสะอาดขึ้นและลด Coupling ที่ไม่จำเป็น

ข้อดีของการปฏิบัติตาม ISP:

  • Decoupling: ลดการพึ่งพากันระหว่าง Class และ Interface
  • ความยืดหยุ่น: ทำให้ Class สามารถ Implement เฉพาะ Interfaces ที่จำเป็นเท่านั้น
  • การบำรุงรักษาที่ง่ายขึ้น: เมื่อ Interface เปลี่ยนแปลง จะมีผลกระทบเฉพาะ Class ที่ Implement Interface นั้นเท่านั้น
  • Code ที่เข้าใจง่ายขึ้น: Interface ที่เฉพาะเจาะจงทำให้โค้ดอ่านง่ายและเข้าใจได้ง่ายขึ้น

เหตุผลเชิงออกแบบ: Trade-offs และแนวปฏิบัติที่ดี

การตัดสินใจว่าจะแบ่ง Interface ออกเป็น Interface ที่เล็กลงหรือไม่นั้น ขึ้นอยู่กับบริบทของโปรเจ็กต์และข้อดีข้อเสียที่ต้องพิจารณา

ข้อดีของการแบ่ง Interface:

  • ความยืดหยุ่นที่มากขึ้น: ช่วยให้ Class สามารถ Implement เฉพาะส่วนที่จำเป็นเท่านั้น
  • ลดความซับซ้อน: ลดภาระของ Class ในการ Implement Methods ที่ไม่ได้ใช้
  • การบำรุงรักษาที่ดีขึ้น: การเปลี่ยนแปลง Interface มีผลกระทบน้อยลง

ข้อเสียของการแบ่ง Interface (ที่มากเกินไป):

  • จำนวน Interface ที่มากขึ้น: อาจทำให้โค้ดซับซ้อนขึ้นหากมี Interface มากเกินไป
  • ความซับซ้อนในการจัดการ Dependencies: การจัดการ Dependencies อาจซับซ้อนขึ้นหากมี Interface มากเกินไป

แนวปฏิบัติที่ดี:

  • เริ่มต้นด้วย Interface ที่เรียบง่าย: เริ่มต้นด้วย Interface ที่มี Methods ที่จำเป็นเท่านั้น และค่อยๆ เพิ่ม Methods เข้าไปเมื่อจำเป็น
  • พิจารณา Use Cases: พิจารณา Use Cases ของ Class ที่จะ Implement Interface นั้น และออกแบบ Interface ให้สอดคล้องกับ Use Cases เหล่านั้น
  • Refactor เมื่อจำเป็น: หากพบว่า Interface ที่มีอยู่ใหญ่เกินไป ให้ Refactor Interface นั้นออกเป็น Interface ที่เล็กลง
  • ใช้หลักการ YAGNI ("You Ain't Gonna Need It"): อย่าเพิ่ม Methods เข้าไปใน Interface เพียงเพราะ "อาจจะ" จำเป็นในอนาคต

สรุป

Interface Segregation Principle (ISP) เป็นหลักการสำคัญในการออกแบบเชิงวัตถุที่ช่วยให้เราสร้าง Interface ที่เฉพาะเจาะจงและเหมาะสมกับความต้องการของ Class ที่จะ Implement Interface นั้น การปฏิบัติตาม ISP ช่วยลด Coupling, เพิ่มความยืดหยุ่น, และทำให้โค้ดบำรุงรักษาง่ายขึ้น ในบทถัดไป เราจะมาเรียนรู้เกี่ยวกับ Dependency Inversion Principle (DIP) ซึ่งเป็นอีกหนึ่งหลักการสำคัญที่เกี่ยวข้องกับการจัดการ Dependencies ในโปรเจ็กต์ OOP ของเรา

Office.in.th Custom Service

この構成をもとにシステムを開発・カスタマイズしたいですか?

Office.in.th は、エンタープライズ Web アプリケーション、API 連携、官民の業務管理システムの設計・開発を承り、長期的なサポートも提供します。

ソフトウェア開発サービスを見る