跳到主要内容

เนื้อหาภาษาไทย / 泰语内容

系统指南 内容已审核

การใช้ Dependency Injection

การใช้ Dependency Injection

ในบทเรียนก่อนหน้านี้ เราได้เรียนรู้เกี่ยวกับการ Monitor API เพื่อให้มั่นใจว่า API ของเราทำงานได้อย่างราบรื่น ในบทนี้ เราจะมาทำความเข้าใจกับแนวคิดที่สำคัญอีกอย่างหนึ่งในการพัฒนาซอฟต์แวร์ นั่นคือ Dependency Injection (DI) ซึ่งเป็นเทคนิคที่ช่วยให้โค้ดของเรามีความยืดหยุ่น ทดสอบได้ง่ายขึ้น และบำรุงรักษาได้ดีขึ้น

DI คืออะไร?

Dependency Injection (DI) คือรูปแบบการออกแบบ (Design Pattern) ที่มีจุดมุ่งหมายเพื่อลดการพึ่งพา (Dependency) ระหว่างคลาสต่างๆ ในโปรแกรมของเรา พูดง่ายๆ คือ แทนที่การที่คลาสหนึ่งสร้างหรือค้นหา dependencies (วัตถุที่คลาสต้องการเพื่อทำงาน) ด้วยตัวเอง เราจะทำการ "inject" หรือ "ส่งมอบ" dependencies เหล่านั้นมาจากภายนอก

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

แบบที่ 1: คลาสสร้าง Dependency เอง (Tight Coupling)

class UserController {
    private $userRepository;

    public function __construct() {
        $this->userRepository = new UserRepository(); // สร้าง UserRepository เอง
    }

    public function getUser($id) {
        return $this->userRepository->getUserById($id);
    }
}

ในตัวอย่างนี้ UserController สร้าง UserRepository เองภายใน constructor ซึ่งทำให้เกิดการพึ่งพา (dependency) ที่แน่นแฟ้น (tight coupling) นั่นคือ UserController ผูกติดอยู่กับ UserRepository โดยตรง หากเราต้องการเปลี่ยนไปใช้ UserRepository ตัวอื่น (เช่น MockUserRepository สำหรับการทดสอบ) เราจะต้องแก้ไขโค้ดภายใน UserController

แบบที่ 2: Dependency Injection (Loose Coupling)

class UserController {
    private $userRepository;

    public function __construct(UserRepository $userRepository) {
        $this->userRepository = $userRepository; // รับ UserRepository ผ่าน constructor
    }

    public function getUser($id) {
        return $this->userRepository->getUserById($id);
    }
}

// ตัวอย่างการใช้งาน:
$userRepository = new UserRepository();
$userController = new UserController($userRepository); // Inject UserRepository เข้าไป

$user = $userController->getUser(1);

ในตัวอย่างนี้ UserController ไม่ได้สร้าง UserRepository เอง แต่รับ UserRepository ผ่าน constructor แทน การทำเช่นนี้เรียกว่า Constructor Injection ซึ่งเป็นรูปแบบที่นิยมใช้ในการ Implement DI

สังเกตว่า UserController ไม่สนใจว่า UserRepository ถูกสร้างขึ้นอย่างไร หรือมาจากไหน สิ่งที่สนใจคือ UserController ต้องการ UserRepository เพื่อทำงานเท่านั้น

ประโยชน์ของ DI

การใช้ DI มีประโยชน์มากมาย ดังนี้:

  • ลดการพึ่งพา (Loose Coupling): คลาสต่างๆ มีอิสระจากกันมากขึ้น ทำให้ง่ายต่อการเปลี่ยนแปลงหรือแทนที่ dependencies
  • เพิ่มความสามารถในการทดสอบ (Testability): เราสามารถ inject mock objects (วัตถุจำลอง) เข้าไปในคลาสที่ต้องการทดสอบ ทำให้เราสามารถทดสอบคลาสได้อย่าง isolated (แยกส่วน)
  • เพิ่มความยืดหยุ่น (Flexibility): เราสามารถเปลี่ยน dependencies ได้ง่ายโดยไม่ต้องแก้ไขโค้ดภายในคลาส
  • เพิ่มความสามารถในการบำรุงรักษา (Maintainability): โค้ดที่ใช้ DI มักจะอ่านง่ายขึ้นและเข้าใจได้ง่ายขึ้น ทำให้ง่ายต่อการบำรุงรักษา
  • ส่งเสริมการนำกลับมาใช้ใหม่ (Reusability): คลาสที่ไม่พึ่งพา dependencies มากเกินไป สามารถนำกลับมาใช้ใหม่ได้ง่ายขึ้นในโครงการอื่นๆ

Trade-offs: ถึงแม้ DI จะมีประโยชน์มากมาย แต่ก็มีข้อเสียที่ต้องพิจารณาเช่นกัน คือ อาจทำให้โค้ดมีความซับซ้อนมากขึ้นในช่วงแรก และต้องใช้ความเข้าใจในเรื่องของการออกแบบเชิงวัตถุ (Object-Oriented Design) มากขึ้น

การ Implement DI

เราสามารถ Implement DI ได้หลายวิธี แต่ที่นิยมใช้กันมี 3 วิธีหลักๆ:

  1. Constructor Injection: Inject dependencies ผ่าน constructor (ดังตัวอย่างข้างต้น)
  2. Setter Injection: Inject dependencies ผ่าน setter methods
  3. Interface Injection: กำหนด interface ที่มี setter method สำหรับ inject dependencies

Constructor Injection เป็นวิธีที่แนะนำ เนื่องจากทำให้เห็น dependencies ของคลาสได้อย่างชัดเจนตั้งแต่แรก และบังคับให้ dependencies ถูก inject เข้ามาเมื่อสร้าง object

Setter Injection เหมาะสำหรับ dependencies ที่ optional (ไม่จำเป็นต้องมีเสมอไป)

Interface Injection เป็นวิธีที่ซับซ้อนกว่า แต่มีความยืดหยุ่นสูง และมักใช้ใน framework ขนาดใหญ่

ตัวอย่างการใช้ Setter Injection:

class UserController {
    private $userRepository;

    public function setUserRepository(UserRepository $userRepository) {
        $this->userRepository = $userRepository;
    }

    public function getUser($id) {
        return $this->userRepository->getUserById($id);
    }
}

// ตัวอย่างการใช้งาน:
$userController = new UserController();
$userRepository = new UserRepository();
$userController->setUserRepository($userRepository); // Inject UserRepository ผ่าน setter

$user = $userController->getUser(1);

ตัวอย่างการใช้ Interface Injection:

interface UserRepositoryInterface {
    public function setUserRepository(UserRepository $userRepository);
}

class UserController implements UserRepositoryInterface {
    private $userRepository;

    public function setUserRepository(UserRepository $userRepository) {
        $this->userRepository = $userRepository;
    }

    public function getUser($id) {
        return $this->userRepository->getUserById($id);
    }
}

// ตัวอย่างการใช้งาน:
$userController = new UserController();
$userRepository = new UserRepository();
$userController->setUserRepository($userRepository); // Inject UserRepository ผ่าน interface

$user = $userController->getUser(1);

Dependency Injection Container

เมื่อโปรเจ็กต์ของเรามีขนาดใหญ่ขึ้น การจัดการ dependencies ด้วยตัวเองอาจเป็นเรื่องที่ยุ่งยาก ดังนั้นจึงมีเครื่องมือที่เรียกว่า Dependency Injection Container (DIC) หรือ Inversion of Control (IoC) Container เข้ามาช่วยจัดการ dependencies ให้เรา

DIC คือ container ที่รู้จัก dependencies ทั้งหมดในโปรเจ็กต์ของเรา และสามารถสร้างและ inject dependencies เหล่านั้นให้กับคลาสต่างๆ ได้โดยอัตโนมัติ

ใน PHP มี DIC หลายตัวให้เลือกใช้ เช่น:

  • PHP-DI
  • Pimple
  • Symfony DependencyInjection Component
  • Laravel Service Container (สำหรับโปรเจ็กต์ Laravel)

การใช้งาน DIC จะช่วยลด boilerplate code (โค้ดส่วนเกิน) และทำให้โค้ดของเรามีความสะอาดและเป็นระเบียบมากขึ้น

ตัวอย่างการใช้ PHP-DI:

require 'vendor/autoload.php'; // โหลด Composer autoloader

use DI\ContainerBuilder;

// สร้าง ContainerBuilder
$containerBuilder = new ContainerBuilder();

// กำหนด dependencies
$containerBuilder->addDefinitions([
    UserRepository::class => DI\autowire(), // สร้าง UserRepository โดยอัตโนมัติ
]);

// สร้าง Container
$container = $containerBuilder->build();

// ดึง UserController จาก Container
$userController = $container->get(UserController::class); // Container จะ inject UserRepository ให้โดยอัตโนมัติ

$user = $userController->getUser(1);

ในตัวอย่างนี้ เราใช้ PHP-DI เพื่อสร้าง UserRepository และ inject เข้าไปใน UserController โดยอัตโนมัติ ทำให้เราไม่ต้องเขียนโค้ดเพื่อสร้างและ inject dependencies เอง

สรุป

Dependency Injection เป็นเทคนิคที่สำคัญในการพัฒนาซอฟต์แวร์ที่ช่วยให้โค้ดของเรามีความยืดหยุ่น ทดสอบได้ง่ายขึ้น และบำรุงรักษาได้ดีขึ้น การใช้ DI จะช่วยลดการพึ่งพา (coupling) ระหว่างคลาสต่างๆ และส่งเสริมการนำกลับมาใช้ใหม่ (reusability) ถึงแม้ว่า DI อาจทำให้โค้ดมีความซับซ้อนมากขึ้นในช่วงแรก แต่ในระยะยาว DI จะช่วยให้เราพัฒนาซอฟต์แวร์ที่มีคุณภาพสูงขึ้นได้

ในบทเรียนถัดไป เราจะมาเรียนรู้เกี่ยวกับการใช้ Composer เพื่อจัดการ dependencies ในโปรเจ็กต์ PHP ของเรา

Office.in.th Custom Service

想基于此结构开发或定制系统?

Office.in.th 承接企业级 Web Application、API Integration 以及政府/企业管理系统的设计与开发,并提供长期支持。

查看软件开发服务