メインコンテンツへ移動

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

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

Dependency Inversion Principle (DIP): ขึ้นอยู่กับ Abstraction ไม่ใช่ Concrete

บทความนี้อธิบายถึงหลักการ Dependency Inversion Principle (DIP) ซึ่งเน้นให้ Modules ระดับสูงและระดับต่ำขึ้นอยู่กับ Abstractions แทนที่จะขึ้นต่อกันโดยตรง เพื่อลดก...

Dependency Inversion Principle (DIP): ขึ้นอยู่กับ Abstraction ไม่ใช่ Concrete

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

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

Dependency Inversion Principle (DIP) กล่าวว่า:

  1. Modules ระดับสูง (High-level modules) ไม่ควรขึ้นอยู่กับ Modules ระดับต่ำ (Low-level modules) ทั้งสองควรขึ้นอยู่กับ Abstractions
  2. Abstractions ไม่ควรขึ้นอยู่กับ Details (Concretions) Details ควรขึ้นอยู่กับ Abstractions

ฟังดูอาจจะซับซ้อน แต่ใจความสำคัญคือ แทนที่จะให้ Modules ระดับสูง (เช่น Class ที่จัดการ Business Logic หลัก) ขึ้นอยู่กับ Modules ระดับต่ำ (เช่น Class ที่จัดการการเชื่อมต่อฐานข้อมูล หรือการส่งอีเมล) โดยตรง เราควรให้ทั้งสองขึ้นอยู่กับ Abstractions (เช่น Interfaces หรือ Abstract Classes) แทน

ทำไมต้องทำเช่นนั้น?

การทำเช่นนี้ช่วยลด Coupling หรือการพึ่งพากันระหว่าง Modules ต่างๆ ทำให้เราสามารถเปลี่ยนแปลง Modules ระดับต่ำได้โดยไม่กระทบต่อ Modules ระดับสูง และยังช่วยให้เราสามารถทดสอบโค้ดได้ง่ายขึ้นอีกด้วย

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

ลองพิจารณาตัวอย่าง Class ที่ชื่อว่า `OrderProcessor` ซึ่งมีหน้าที่ประมวลผลคำสั่งซื้อ และต้องบันทึกข้อมูลการสั่งซื้อลงในฐานข้อมูล

class OrderProcessor {
    private $database;

    public function __construct() {
        $this->database = new MySQLDatabase(); // ขึ้นอยู่กับ Concrete Class โดยตรง
    }

    public function processOrder(Order $order) {
        // ประมวลผลคำสั่งซื้อ

        $this->database->saveOrder($order); // ขึ้นอยู่กับ Concrete Class โดยตรง
    }
}

class MySQLDatabase {
    public function saveOrder(Order $order) {
        // บันทึกข้อมูลลงในฐานข้อมูล MySQL
        echo "Saving order to MySQL database...\n";
    }
}

class Order {
    // ข้อมูลคำสั่งซื้อ
}

$orderProcessor = new OrderProcessor();
$order = new Order();
$orderProcessor->processOrder($order);

ในตัวอย่างนี้ `OrderProcessor` ขึ้นอยู่กับ Concrete Class `MySQLDatabase` โดยตรง หากเราต้องการเปลี่ยนไปใช้ฐานข้อมูลอื่น เช่น PostgreSQL เราจะต้องแก้ไข `OrderProcessor` ซึ่งเป็นการละเมิดหลักการ Open/Closed Principle (OCP) ที่เราได้เรียนรู้ไปก่อนหน้านี้

นอกจากนี้ การทดสอบ `OrderProcessor` ก็เป็นเรื่องยาก เพราะเราไม่สามารถจำลอง (Mock) `MySQLDatabase` ได้ง่ายๆ

การแก้ไข DIP

เพื่อแก้ไขปัญหา เราจะใช้ Dependency Inversion โดยสร้าง Interface ที่กำหนดพฤติกรรมการบันทึกข้อมูล และให้ทั้ง `OrderProcessor` และ `MySQLDatabase` ขึ้นอยู่กับ Interface นี้

interface DatabaseInterface {
    public function saveOrder(Order $order);
}

class MySQLDatabase implements DatabaseInterface {
    public function saveOrder(Order $order) {
        // บันทึกข้อมูลลงในฐานข้อมูล MySQL
        echo "Saving order to MySQL database...\n";
    }
}

class PostgreSQLDatabase implements DatabaseInterface {
    public function saveOrder(Order $order) {
        // บันทึกข้อมูลลงในฐานข้อมูล PostgreSQL
        echo "Saving order to PostgreSQL database...\n";
    }
}

class OrderProcessor {
    private $database;

    public function __construct(DatabaseInterface $database) { // รับ Interface เป็น Dependency
        $this->database = $database;
    }

    public function processOrder(Order $order) {
        // ประมวลผลคำสั่งซื้อ

        $this->database->saveOrder($order); // ขึ้นอยู่กับ Interface
    }
}

class Order {
    // ข้อมูลคำสั่งซื้อ
}

// การใช้งาน
$mysqlDatabase = new MySQLDatabase();
$orderProcessor = new OrderProcessor($mysqlDatabase); // Inject Dependency ผ่าน Constructor
$order = new Order();
$orderProcessor->processOrder($order);

$postgresDatabase = new PostgreSQLDatabase();
$orderProcessor = new OrderProcessor($postgresDatabase); // Inject Dependency ผ่าน Constructor
$order = new Order();
$orderProcessor->processOrder($order);

สิ่งที่เปลี่ยนไป:

  • เราสร้าง Interface ชื่อ `DatabaseInterface` ที่กำหนด Method `saveOrder`
  • `MySQLDatabase` และ `PostgreSQLDatabase` Implement `DatabaseInterface`
  • `OrderProcessor` รับ `DatabaseInterface` เป็น Dependency ผ่าน Constructor

ผลลัพธ์:

  • `OrderProcessor` ไม่ได้ขึ้นอยู่กับ Concrete Class ใดๆ อีกต่อไป ขึ้นอยู่กับ `DatabaseInterface` แทน
  • เราสามารถเปลี่ยนไปใช้ฐานข้อมูลอื่นได้ง่ายๆ โดยไม่ต้องแก้ไข `OrderProcessor`
  • เราสามารถทดสอบ `OrderProcessor` ได้ง่ายขึ้น โดยสร้าง Mock Object ที่ Implement `DatabaseInterface`

Dependency Injection (DI)

ในตัวอย่างข้างต้น เราใช้ Constructor Injection เพื่อ Inject Dependency เข้าไปใน `OrderProcessor` นี่เป็นรูปแบบหนึ่งของ Dependency Injection (DI) ซึ่งเป็นเทคนิคที่ใช้ในการ Implement DIP

Dependency Injection คือกระบวนการส่ง Dependencies (สิ่งที่ Class ต้องการเพื่อทำงาน) เข้าไปใน Class แทนที่จะให้ Class สร้าง Dependencies เอง

มีหลายรูปแบบของ DI เช่น:

  • Constructor Injection: ส่ง Dependencies ผ่าน Constructor (ดังตัวอย่างข้างต้น)
  • Setter Injection: ส่ง Dependencies ผ่าน Setter Methods
  • Interface Injection: ส่ง Dependencies ผ่าน Interface

Dependency Injection ช่วยให้เราสามารถเขียนโค้ดที่ Loose Coupling, Testable, และ Reusable ได้มากขึ้น

สรุป

Dependency Inversion Principle (DIP) เป็นหลักการสำคัญที่ช่วยให้เราออกแบบซอฟต์แวร์ที่ยืดหยุ่นและบำรุงรักษาง่าย โดยการลด Coupling ระหว่าง Modules ต่างๆ และส่งเสริมการขึ้นอยู่กับ Abstractions แทนที่จะเป็น Concretions

การใช้ Dependency Injection (DI) เป็นวิธีที่นิยมในการ Implement DIP และช่วยให้เราเขียนโค้ดที่ Testable และ Reusable ได้มากขึ้น

ในบทความถัดไป เราจะมาเรียนรู้เกี่ยวกับ Design Patterns ซึ่งเป็นแนวทางการแก้ปัญหาที่พบบ่อยในการออกแบบซอฟต์แวร์ OOP

Office.in.th Custom Service

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

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

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