Dependency injection - Những thứ có thể bạn bỏ qua
White

Nguyễn Thế Huy viết ngày 17/07/2021

Chào các bạn, hôm nay chúng ta sẽ đề cập Dependency injection và một số khía cạnh mà có thể bạn chưa biết về nó :D

Chúng ta hãy cùng nhau xem xét một đề bài khá phổ biến: Xây dựng một ứng dụng nhắn tin. Ứng dụng của chúng ta sẽ giúp khách hàng kết nối vào nền tảng Messenger của Facebook và giúp người dùng nhắn tin trên đó. Đầu tiên thì chúng ta sẽ dựng một base class trông như thế này

class BaseMessage
{
        public function send()
        {
                doSomethingToSend();
        }
}

Và để kết nối Facebook, chúng ta sẽ extends Base class trên và làm trông giống như thế này

class FacebookMessage extends BaseMessage
{
        public function send()
        {
                $fbService = new FbService();
                $fbService->doSomethingWithFacebook();
                doSomethingToSend();
        }
}

Một ngày đẹp trời, khách hàng có nhu cầu kết nối vào nền tảng của Zalo. "Quá đơn giản, extends class Base là done". Và thế là chúng ta có một class ZaloMessage trông như thế này

class ZaloMessage extends BaseMessage
{
        public function send()
        {
                $zaloService = new ZaloService();
                $zaloService->doSomethingWithZalo();
                doSomethingToSend();
        }
}

Mọi thứ trông rất ổn đúng không :D. Bạn đang tuân thủ rất đúng nguyên lý trong SOLID, đó là "Open for extension, Close for modification". Sau đó nếu phát sinh các yêu cầu kết nối sang các nền tảng khác như Instagram, Slack, ... bạn vẫn chỉ cần extends tiếp và làm như trên là được rồi.

Tuy nhiên mọi thứ không đơn giản như vậy

Bẫy kế thừa

Chúng ta thấy rằng nếu mỗi lần phát sinh nhu cầu mới, bạn lại phải tạo ra một class mới. Nôm na rằng, ứng dụng của bạn sẽ sớm tràn ngập các class khác nhau để xử lý cùng một vấn đề. Code của bạn tái sử dụng rất kém, lặp đi lặp lại một việc giữa các class.

Tính linh hoạt và mở rộng của các đoạn code kia cũng thấp, khi nó đang phụ thuộc cứng vào các service bên trong nó, vi phạm vào quy tắc loosely coupling (phụ thuộc mềm, tức phụ thuộc vào các đối tượng trừu tượng).

Ngoài ra, nếu để ý kỹ hơn, chúng ta sẽ thấy các đoạn code trên rất khó để mocking / testing. Làm sao để bạn có thể giả lập các đầu vào cho zalo, cho facebook khi viết unit test ?.

Và rõ ràng rằng, chúng ta đã vi phạm tính Single responsibility, khi class của chúng ta ngoài việc send tin nhắn, còn chịu trách nhiệm khởi tạo những thứ nó cần để làm việc đó ?.

Đây chính là Bẫy kế thừa khi thiết kế OOP. Trong ví dụ trên, chúng ta đang thể hiện tính đa hình (polymorphism) giữa các đối tượng thông qua kế thừa. Hành vi của các đối tượng con hoàn toàn dựa trên những gì chúng kế thừa lại, và tự chúng kiểm soát hành vi đó (ở đây là phương thức send()). Về cơ bản chúng ta không có cách nào để tác động được hành vi của chúng từ bên ngoài.

Composition over inheritance

Điều này dẫn đến một nguyên lý thiết kế rằng:
"Tính đa hình của hành vi đối tượng và tái sử dụng các thành phần nên được thể hiện qua các phụ thuộc mà nó nhận được, chứ không phải thông qua kế thừa"

Trong ví dụ trên, chúng ta có thể hiểu rằng
Class Message chỉ nên làm việc duy nhất là gửi tin nhắn, còn các phụ thuộc đều được tiêm (inject) từ bên ngoài vào. Các phụ thuộc này (Như Facebook, ZaloService) sẽ quyết định hành vi gửi tin nhắn

Đến đây, có lẽ các bạn đã hiểu cốt lõi Dependency injection là gì. Điều này cũng dẫn đến khái niệm khá phổ biến: Inversion of Control (IoC). Dịch nôm na ra, nó là "đảo chiều kiểm soát": thay vì tự các đối tượng quyết định hành vi của nó, hành vi được kiểm soát thông qua các phụ thuộc.

Ví dụ của chúng ta có thể được viết lại như thế này:

class Message
{
        public $service;

        public function __construct(Service $service)
        {
                $this->service = $service;
        }

        public function send()
        {
                $this->service->doSomethingWithService();
                doSomethingToSend()
        }
}


//Và chúng ta sẽ thể hiện từng dịch vụ như sau

$facebookMessage = new Message(new FacebookService());
$zaloMessage = new Message(new ZaloService());

Sau đó thì như mọi người đều biết, chúng ta có thể abstract hóa các service thông qua một interface, đăng ký thể hiện với Service Container để hoàn thành trọn vẹn DI.

Đến đây chúng ta cơ bản đã hoàn thành Dependency Injection. Mình sẽ không bàn tới Service Container trong các framework, vì cái đó đã ở bên ngoài khái niệm DI cơ bản (Nhiều người vẫn có thể coi Service Container là một phần của DI).

Không phải khi nào DI cũng đúng

Không có cái gì trên đời là viên đạn bạc cả. Nghĩa là bạn không thể tìm ra một solution cho mọi vấn đề

Nếu bạn chỉ có một thể hiện duy nhất cho một interface, DI có thể ko cần thiết. DI sẽ rất thích hợp khi bạn cần thể hiện tính đa hình. Nếu bạn không cần điều này, hãy cân nhắc sử dụng DI.

DI có thể khiến ứng dụng của bạn trở nên cồng kềnh với quá nhiều các interface, các thể hiện gắn với các interface đó. Đôi khi, sự phụ thuộc "cứng" cũng có những lợi ích nhất định, và khiến code dễ maintain hơn. Inject everything không phải khi nào cũng hiệu quả. Chúng ta cần cân nhắc kỹ bài toán của mình trước khi triển khai một mẫu thiết kế nào đó.

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Nguyễn Thế Huy

13 bài viết.
68 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
32 8
Hi cả nhà, đây là bài viết đầu tiên của mình trên Kipalog nên có gì không hay mong các bạn thông cảm :D Realtime là gì ? Như chúng ta đều đã biế...
Nguyễn Thế Huy viết hơn 3 năm trước
32 8
White
28 6
Xin chào mọi người :D Trong bài viết này mình sẽ trình bày một cách cơ bản để ứng dụng kỹ thuật Http Live Streaming (HLS) để play video trên web, ...
Nguyễn Thế Huy viết hơn 3 năm trước
28 6
White
27 6
Bài toán Machine Learning và những ứng dụng của nó đang ngày càng trở nên nổi bật trong những năm trở lại đây. Với sự phát triển thần tốc của cấu ...
Nguyễn Thế Huy viết hơn 2 năm trước
27 6
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
13 bài viết.
68 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!