ข้ามไปยังเนื้อหาหลัก
คู่มือระบบ เนื้อหาผ่านการตรวจสอบแล้ว

การใช้ Dependency Injection: ลด Coupling

บทความนี้แนะนำ Dependency Injection (DI) ซึ่งเป็นเทคนิคการออกแบบซอฟต์แวร์ที่ช่วยลด Coupling ระหว่างคลาส ทำให้โค้ดมีความยืดหยุ่นและทดสอบได้ง่ายขึ้น โดย DI ทำงานโ...

การใช้ Dependency Injection: ลด Coupling

ในบทที่แล้ว เราได้เรียนรู้เกี่ยวกับการจัดการ Exceptions เพื่อให้โปรแกรม PHP ของเรามีความแข็งแกร่งและสามารถรับมือกับข้อผิดพลาดที่ไม่คาดฝันได้ ในบทนี้ เราจะมาพูดถึงเทคนิคการออกแบบซอฟต์แวร์ที่สำคัญอีกอย่างหนึ่ง นั่นคือ Dependency Injection (DI) ซึ่งจะช่วยให้โค้ดของเรามีความยืดหยุ่น, ทดสอบได้ง่าย, และลดความสัมพันธ์ (Coupling) ระหว่างคลาสต่างๆ

Dependency Injection คืออะไร

Dependency (การพึ่งพา) หมายถึง เมื่อคลาสหนึ่งต้องการใช้งานฟังก์ชันการทำงานของอีกคลาสหนึ่ง คลาสแรกจะ "พึ่งพา" คลาสที่สอง Dependency Injection คือรูปแบบการออกแบบที่คลาสหนึ่งได้รับ Dependencies ของมันจากภายนอก แทนที่จะสร้าง Dependencies เหล่านั้นขึ้นมาเองภายในคลาส

ลองพิจารณาตัวอย่างง่ายๆ สมมติว่าเรามีคลาส User ที่ต้องการบันทึกข้อมูลผู้ใช้ลงในฐานข้อมูล:

class User {
    private $database;

    public function __construct() {
        $this->database = new Database(); // สร้าง Database object ภายในคลาส
    }

    public function register(string $username, string $password): void {
        // ... ทำงานบางอย่าง ...
        $this->database->insert('users', ['username' => $username, 'password' => $password]);
    }
}

ในตัวอย่างนี้ คลาส User สร้างอินสแตนซ์ของคลาส Database ภายใน Constructor เอง ซึ่งทำให้เกิดปัญหาหลายอย่าง:

  • Coupling สูง: คลาส User ผูกติดอยู่กับคลาส Database อย่างแน่นหนา หากเราต้องการเปลี่ยนไปใช้ฐานข้อมูลอื่น (เช่น MySQL เป็น PostgreSQL) เราจะต้องแก้ไขโค้ดภายในคลาส User
  • ทดสอบยาก: ในการทดสอบคลาส User เราจะต้องมี Database จริงๆ (หรือจำลองขึ้นมา) ซึ่งอาจจะยุ่งยากและใช้เวลานาน

Dependency Injection เข้ามาช่วยแก้ปัญหาเหล่านี้ได้ โดยการให้เราส่ง Dependencies (ในที่นี้คือ Database object) เข้ามาในคลาส User จากภายนอก:

class User {
    private $database;

    public function __construct(Database $database) {
        $this->database = $database; // รับ Database object ผ่าน Constructor
    }

    public function register(string $username, string $password): void {
        // ... ทำงานบางอย่าง ...
        $this->database->insert('users', ['username' => $username, 'password' => $password]);
    }
}

ตอนนี้ คลาส User ไม่ได้สร้าง Database object เองอีกต่อไป แต่ได้รับมันผ่าน Constructor ซึ่งทำให้:

  • Coupling ต่ำ: คลาส User ไม่ได้ผูกติดอยู่กับคลาส Database โดยตรง เราสามารถส่ง Database object ประเภทอื่นๆ เข้ามาได้ ตราบใดที่มัน Implement Interface ที่ User ต้องการ (เราจะพูดถึง Interfaces ในบทถัดไป)
  • ทดสอบง่าย: ในการทดสอบคลาส User เราสามารถส่ง Mock Database object เข้ามาได้ ซึ่งจะทำให้เราสามารถควบคุมพฤติกรรมของ Database ได้อย่างเต็มที่

Dependency Injection ช่วยให้โค้ดของเรามีความยืดหยุ่นและทดสอบได้ง่ายขึ้น เพราะเราสามารถสับเปลี่ยน Dependencies ได้ตามต้องการ โดยไม่ต้องแก้ไขโค้ดภายในคลาส

Constructor Injection

Constructor Injection คือรูปแบบของ Dependency Injection ที่เราส่ง Dependencies ผ่าน Constructor ของคลาส ดังที่เราได้เห็นในตัวอย่างข้างต้น:

class User {
    private $database;

    public function __construct(Database $database) {
        $this->database = $database; // Constructor Injection
    }

    // ...
}

// ตัวอย่างการใช้งาน
$database = new Database();
$user = new User($database); // Inject Database object เข้าไป

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

ข้อดีของ Constructor Injection:

  • บังคับให้ Dependencies ถูกส่งเข้ามาเสมอ
  • ทำให้ Dependencies ชัดเจนและง่ายต่อการเข้าใจ
  • ทำให้คลาส Immutable (ไม่เปลี่ยนแปลง) ได้ง่ายขึ้น

ข้อเสียของ Constructor Injection:

  • อาจทำให้ Constructor ยาวและซับซ้อน หากคลาสมี Dependencies จำนวนมาก

Setter Injection

Setter Injection คือรูปแบบของ Dependency Injection ที่เราส่ง Dependencies ผ่าน Setter method:

class User {
    private $database;

    public function setDatabase(Database $database): void {
        $this->database = $database; // Setter Injection
    }

    // ...
}

// ตัวอย่างการใช้งาน
$user = new User();
$user->setDatabase(new Database()); // Inject Database object ผ่าน Setter method

Setter Injection เหมาะสำหรับ Dependencies ที่ Optional หรือ Dependencies ที่อาจเปลี่ยนแปลงได้ในภายหลัง

ข้อดีของ Setter Injection:

  • สามารถ Inject Dependencies ได้หลังจากสร้างอินสแตนซ์ของคลาสแล้ว
  • เหมาะสำหรับ Dependencies ที่ Optional

ข้อเสียของ Setter Injection:

  • ไม่บังคับให้ Dependencies ถูกส่งเข้ามา ทำให้คลาสอาจทำงานไม่ถูกต้องหากไม่มี Dependencies
  • ทำให้ Dependencies ไม่ชัดเจนเท่า Constructor Injection

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

การเลือกใช้ Constructor Injection หรือ Setter Injection ขึ้นอยู่กับสถานการณ์และความต้องการของโปรเจกต์

  • Constructor Injection: ควรใช้สำหรับ Dependencies ที่จำเป็นต่อการทำงานของคลาส และไม่ควรเปลี่ยนแปลงในภายหลัง
  • Setter Injection: ควรใช้สำหรับ Dependencies ที่ Optional หรือ Dependencies ที่อาจเปลี่ยนแปลงได้ในภายหลัง

นอกจากนี้ การใช้ Interface ร่วมกับ Dependency Injection เป็นแนวปฏิบัติที่ดี เพราะจะช่วยให้โค้ดของเรามีความยืดหยุ่นมากยิ่งขึ้น เราสามารถเปลี่ยน Implementation ของ Dependency ได้ง่ายๆ โดยไม่ต้องแก้ไขโค้ดที่ใช้งาน Dependency (เราจะพูดถึง Interfaces ในบทถัดไป)

ตัวอย่าง:

interface DatabaseInterface {
    public function insert(string $table, array $data): void;
    public function select(string $table, array $conditions): array;
}

class MySQLDatabase implements DatabaseInterface {
    // ... Implement methods ...
}

class PostgreSQLDatabase implements DatabaseInterface {
    // ... Implement methods ...
}

class User {
    private $database;

    public function __construct(DatabaseInterface $database) {
        $this->database = $database;
    }

    // ...
}

// ตัวอย่างการใช้งาน
$mysqlDatabase = new MySQLDatabase();
$user = new User($mysqlDatabase); // Inject MySQLDatabase

$postgresqlDatabase = new PostgreSQLDatabase();
$user = new User($postgresqlDatabase); // Inject PostgreSQLDatabase (ไม่ต้องแก้ไข User class)

ในตัวอย่างนี้ คลาส User รับ DatabaseInterface เป็น Dependency ซึ่งทำให้เราสามารถ Inject MySQLDatabase หรือ PostgreSQLDatabase เข้ามาได้ โดยไม่ต้องแก้ไขโค้ดในคลาส User

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

Office.in.th Custom Service

ต้องการพัฒนาระบบหรือปรับแต่งจากโครงสร้างนี้?

Office.in.th รับพัฒนาและออกแบบ Enterprise Web Application, API Integration และระบบบริหารงานภาครัฐ/เอกชน พร้อมการสนับสนุนระยะยาว

ดูบริการรับพัฒนาซอฟต์แวร์