Dependency Injection

Chào mọi người, hôm nay chúng ta sẽ tìm hiểu về Dependency Injection (DI). Nếu bạn thuộc nhóm những người đã sử dụng Dependency Injection nhưng không thực sự hiểu cách nó hoạt động bên trong, hoặc bạn muốn biết thêm về reflection, hoặc bạn không hài lòng khi chỉ sử dụng nó trong công việc hàng ngày mà vẫn còn thiếu hiểu biết sâu sắc, thì bạn đã đến đúng nơi. Hôm nay, chúng ta sẽ xây dựng một container Dependency Injection rất đơn giản của riêng mình và sẽ xem xét các thành phần bên trong, bắt đầu từ từ và xây dựng dần từng bước.

Thông thường, bạn sẽ bắt đầu với một loại dịch vụ nào đó. Tôi sẽ tạo một dịch vụ mới gọi là `HelloService`, và nhân tiện, tôi đang sử dụng một chương trình gọi là LINQPad cho tutorial này, liên kết sẽ được đặt trong phần mô tả. Đây không phải là một liên kết liên kết đối tác, tôi chỉ rất thích sử dụng công cụ cơ bản này để thực hiện các bài học của mình. Vậy thì, tình huống điển hình là chúng ta có một dịch vụ nào đó mà chúng ta có thể sử dụng dịch vụ này trước đây. Bây giờ, đây sẽ là một lớp siêu đơn giản với một hàm `Print`, nơi chúng ta sẽ chỉ đơn giản gọi "Hello World" và xuất nó ra. Sau đó, trong dịch vụ này, chúng ta có thể gọi hàm `Print` và kết quả sẽ được xuất ra màn hình. Điều này rất tự giải thích, những gì chúng ta đang làm ở đây là chúng ta đang khởi tạo các lớp một cách thủ công, nghĩa là chúng ta đang sử dụng từ khóa `new` để tạo các thể hiện của các lớp này. Điều chính mà chúng ta muốn loại bỏ là việc sử dụng từ khóa `new`.

Cách chính mà chúng ta có thể loại bỏ việc này là tạo ra một container, đó là một container Dependency Injection, nơi chúng ta đăng ký tất cả các dịch vụ mà chúng ta sẽ sử dụng và container đó sẽ quản lý việc tạo ra các thể hiện. Vì vậy, từ khóa `new` sẽ không còn cần thiết nữa. Trong ứng dụng nhỏ này, lý do tôi gọi nó là `Activator` ở đầu là vì tôi muốn cho bạn thấy cách chúng ta có thể kích hoạt các dịch vụ này một cách tự nhiên hơn. `Activator` là một lớp tĩnh thuộc API reflection và chúng ta có thể tạo một thể hiện ở đây bằng cách cung cấp kiểu của lớp mà chúng ta muốn tạo. Và ở đây, chúng ta sẽ sử dụng `Activator.CreateInstance`, nhưng vì nó trả về một kiểu `object`, chúng ta cần ép kiểu nó sang kiểu chúng ta muốn. Nhưng thực sự, khi chúng ta tiến tới container Dependency Injection, chúng ta sẽ không cần phải ép kiểu nữa. Thay vào đó, chúng ta sẽ có một container nơi chúng ta đăng ký tất cả các kiểu và một resolver để lấy các kiểu từ container đó và tạo thể hiện chúng. Điều này giúp giảm bớt việc khởi tạo thủ công và quản lý các phụ thuộc một cách hiệu quả hơn.

Nhưng trong ví dụ này, chúng ta có dịch vụ mà không khởi tạo nó theo cách khác và vì cái gì nó trả về là kiểu `object`, chúng ta thực sự muốn ép kiểu nó ngay lúc này, nhưng thực ra, khi chúng ta tiếp cận container Dependency Injection, chúng ta sẽ không cần phải ép kiểu nữa. Vì vậy, chúng ta sẽ có dịch vụ này và hãy tiếp tục và tạo consumer. Vậy nên, chúng ta cung cấp kiểu `ServiceConsumer` và chúng ta muốn tạo một `ServiceConsumer`. Được rồi, vậy là chúng ta đang chạy vào một ngoại lệ: "Không có constructor không có tham số được định nghĩa". Bởi vì chúng ta có một constructor, nếu chúng ta không có constructor với một tham số, chúng ta sẽ không gặp vấn đề gì trong việc giải quyết đối tượng này, nhưng chúng ta đang gặp lỗi ở đây vì nó mong đợi một tham số `HelloService`. Vì vậy, chúng ta làm gì? Với `Activator`, chúng ta có thể cung cấp các overload khác nhau của hàm này, chúng ta có thể cung cấp một dịch vụ như một tham số và bây giờ điều này sẽ inject nó vào. Vậy nên, chúng ta đang ở cùng một điểm như trước đây, nhưng bây giờ chúng ta đang sử dụng API reflection để tạo thể hiện của nó. Được rồi, vậy đây chỉ là một bước tiến về phía trước. Chúng ta vẫn đang khởi tạo thủ công, nhưng giờ chúng ta đang làm nó theo cách mà code khởi tạo chúng tại runtime dựa trên kiến thức về chính nó.

Vậy nên, điều chúng ta muốn làm bây giờ là đi theo hướng tạo ra một container nơi chúng ta lưu trữ tất cả các kiểu và một resolver sẽ nhìn vào container đó và nói: "Đây là các kiểu mà chúng ta đã đăng ký, hãy cố gắng sử dụng `Activator` để khởi tạo chúng." Vậy nên, bạn có thể thấy rằng nếu chúng ta nhìn vào hàm `CreateInstance` của `Activator`, chúng ta có kiểu, và cách bạn lấy kiểu là bạn sử dụng `typeof` và sau đó bạn có thể lấy một loạt thông tin về kiểu đó. Ví dụ, nếu tôi không... Lý do tôi giải thích ở đây là nếu bạn đang thắc mắc tại sao tôi dành thời gian vào điều này - một số người không hiểu đầy đủ vì họ xem lần đầu tiên. Về cơ bản, hàm `typeof` trả về một đối tượng kiểu `Type`. Nếu chúng ta hover vào `type`, có thể không được, nhưng nếu chúng ta lưu nó vào một biến `a` và hover vào `a`, chúng ta có thể thấy nó là một lớp `System.Type`. Và lớp `Type` này về cơ bản là code dưới dạng dữ liệu, đúng vậy. Vậy nên, đây là code của bạn trong dạng dữ liệu của nó và đây là cách nó trông trong bộ nhớ, và đây là tất cả thông tin về code của bạn hoặc về lớp này. Điều này mà chúng ta có thể tạo ra và nó bao gồm những gì và nó là gì, đó là định nghĩa và liên kết của nó. Điều này rất tuyệt vời, giống như vậy để bạn có thể xem nó và tất cả các thuộc tính khác nhau của nó. Bây giờ, chúng ta sẽ tiếp tục. Hãy để tôi lưu cái này, tôi sẽ gọi nó là `Resolver` và `Container`. Đây chính là những gì chúng ta sẽ xây dựng. Bước đầu tiên là container, chúng ta muốn có thể đăng ký các dịch vụ, chỉ cần nói với nó như cách chúng ta làm trong .NET Core, đúng không? Vậy nên, hãy tạo một lớp công khai và chúng ta sẽ gọi nó là `DependencyContainer`. Vậy container này sẽ có gì? Chúng ta sẽ có một danh sách các kiểu, đúng không? Bạn biết cách chúng ta lấy một kiểu, chúng ta chỉ muốn biết về bất kỳ kiểu nào mà chúng ta có thể tạo ra. Vì vậy, nếu chúng ta muốn tạo ra một `ServiceConsumer`, chúng ta sẽ cần một `HelloService` để cung cấp cho nó, đúng không? Vậy nên, chúng ta cần biết về cả hai kiểu và chúng ta sẽ lưu trữ các kiểu này trong danh sách này và chúng ta sẽ gọi chúng là `Dependencies`. Được rồi, tất cả đây là dependencies và hãy tạo hàm `AddDependency`.

Chúng ta muốn làm cho nó giống hơn với .NET Core, sau này chúng ta sẽ xem xét các pattern về lifetime như `Singleton` và `Transient` cho các dependencies này, nhưng không sao, nếu bạn đã làm việc với C#, bạn đang xem video này về Dependency Injection thì tôi giả định bạn biết một chút về C#, vậy chúng ta sẽ thêm vào đây và chúng ta chỉ đơn giản truyền một giá trị cũ vào đây và chúng ta chỉ thêm vào danh sách này, đúng vậy. Vậy nên, đây chỉ là thông tin về một kiểu nào đó, giống như một lớp là một kiểu, một hàm cũng có thể là một kiểu, vv, nhưng chúng ta đang thêm dependency vào. Bây giờ, chúng ta cũng muốn có thể lấy dependency, vậy nên chúng ta muốn có thể trả về một kiểu và lấy dependency. Tôi gặp khó khăn trong việc đánh vần từ 'dependency' và chúng ta muốn có thể lấy một kiểu nào đó. Những gì tôi sẽ làm chính là đối với dependencies của mình, tôi sẽ chọn cái đầu tiên, tôi sẽ mong đợi có nó và tôi sẽ so sánh theo tên vì về cơ bản đây là một tham chiếu đối tượng sẽ được lưu trữ ở đây và một tham chiếu đối tượng mà tôi sẽ truyền vào đây, nó sẽ là hai đối tượng khác nhau, vậy nên tôi không thể so sánh hai đối tượng đó, tôi cần so sánh tên của chúng để thực sự biết xem nó có phải là cùng một kiểu hay không.

Vậy nên, bây giờ những gì tôi có thể làm là tôi có thể đăng ký các thứ với container Dependency Injection của mình. Vậy hãy tạo container của chúng ta, `new DependencyContainer`, và đối với container của mình, tôi có thể thêm một dependency và chúng ta có thể cung cấp nó như thế này hoặc chúng ta thậm chí có thể thêm một cách tuyệt vời khác là chỉ cung cấp một kiểu ở đây và sau đó chúng ta sẽ phải nói `typeof(T)`. Vậy điều này cung cấp cho chúng ta có thể gọi nó là một cách thêm dependency trông đẹp mắt hơn, đúng không? Vậy là chúng ta đã có `ServiceConsumer` rồi. Và nhanh chóng, nếu tôi đặt một breakpoint ở đây và chạy, thiếu một dấu chấm phẩy ở đây, được rồi, tham chiếu đối tượng không hợp lệ và luôn khởi tạo các danh sách của bạn. Vậy nên, chúng ta sẽ tạo một kiểu ở đây, đúng không? Vậy chúng ta đã đạt được breakpoint, tất cả những gì tôi thực sự muốn là tôi muốn xem container này và đảm bảo rằng nếu tôi nhìn vào kết quả cho bạn, bạn có thể thấy tôi có `HelloService` và tôi có `ServiceConsumer` và tôi nghĩ mình ổn, hãy zoom vào debugger một chút, xin lỗi, nhưng về cơ bản, vâng, chúng ta có `HelloService` và `ServiceConsumer`, vậy nên chúng ta có thể sử dụng cách này hoặc cách kia để đăng ký các kiểu của chúng ta, đúng không? Vậy nên, chỉ có hai cách khác nhau về việc có 'novelty', đúng không? Chúng ta đăng ký dependencies, vậy còn việc giải quyết dependencies thì sao? Vậy nên, tôi sẽ xóa đi thực ra, chúng ta có thể sẽ cần xem xét điều này để có ý tưởng về những gì chúng ta cần viết. Vậy hãy tạo một `DependencyResolver`, và ở đây trong constructor, tôi chỉ cần truyền container của mình. Tôi sẽ tạo một trường toàn cục cho container của mình, tôi sẽ đảm bảo lưu nó ở đây, đúng vậy. Vậy ở đây tôi có container của mình, hãy tiếp tục và chỉ cần lấy một service, đúng không? Vậy chúng ta có hai services, chúng ta phải inject vào một trong số chúng. Những gì tôi muốn làm là giải quyết `HelloWorldService` vì đó sẽ là một điều dễ dàng hơn. Ai đó sẽ comment nó ra một chút, nhưng những gì chúng ta muốn làm là chúng ta muốn lấy một kiểu nào đó và lấy một service, đúng không? Và trong đây, chúng ta không thực sự cần truyền gì vào constructor, vì vậy nó sẽ giống như sự khác biệt như chúng ta có ở đây, chúng ta cung cấp kiểu mà chúng ta muốn thêm vào đây, đó là cung cấp kiểu mà chúng ta muốn kết quả là một cách dễ dàng mà chúng ta sẽ làm ở đây là chúng ta sẽ lấy kiểu từ container của chúng ta. Vậy nên, tại điểm này, chúng ta chỉ đơn giản đang nói: "Liệu chúng ta có kiểu này trong container của mình không?" Vậy nên, chúng ta sẽ lấy một dependency của T, chúng ta không thể chỉ truyền T, vì vậy chúng ta phải nói `typeof(T)` và đây sẽ lấy kiểu của chúng ta. Đây là một kiểu 'pass-off' và ở đây chúng ta có thể sử dụng service này, bạn có thể sử dụng `Activator` để ép kiểu sang kiểu của hàm mà chúng ta đang cung cấp ở đây và kiểu của chúng ta có thể chỉ đơn giản cung cấp kiểu ở đây, đúng không? Vậy nên, chúng ta đang lấy kiểu từ container và chúng ta đang khởi tạo và ép kiểu nó sang kiểu mà chúng ta sẽ đáp ứng, đúng vậy. Vậy nên, chỉ cần tạo một chút không gian, hãy tạo resolver của chúng ta, một `DependencyResolver` trong .NET Core. Tôi nghĩ đây gần như được gọi là `ServiceProvider` hay gì đó vậy. Bạn có `IServiceContainer` và `IServiceProvider`, tôi khá chắc chắn rằng cùng một đối tượng kế thừa từ cả hai và bạn đang phụ thuộc vào vai trò mà nó cần thực hiện, đúng không? Vâng, chúng ta có resolver, hãy thay thế phần này vì chúng ta đã có nó ở dưới đã dịch sang service resolver. Chúng ta muốn lấy resolver và lấy một service của `HelloService`, đúng không? Và điều này, chúng ta không thực sự cần nó, phần consumer, chúng ta đã biết rằng chúng ta chỉ cung cấp các tham số đã được xếp chồng, vậy nên chúng ta không thực sự cần xem xét nó nữa. Vậy nên, hãy chạy nó và ở đây bạn có thể thấy lại chúng ta chỉ nhận được "Hello World" khiêm tốn và đây là nơi mà chúng ta muốn nâng cao hơn, chúng ta sẽ thêm `ServiceConsumer` và chúng ta sẽ đảm bảo rằng chúng ta sẽ kiểm tra xem dịch vụ này cần constructor có tham số hay không, đúng không? Vậy nên, hãy tiếp tục và chúng ta đã có dependencies, chỉ là chúng ta thực sự muốn giải quyết nó. Vậy nên, hãy giải quyết `ServiceConsumer`. Vẫn là hàm `Print`, đúng không? Không có constructor không tham số được định nghĩa, đúng vậy. Vậy nên, chúng ta đang ở cùng một giỏ như trước đây, vậy nên hãy đưa nó về đó nhanh chóng, tôi chỉ cần trả về ở đây, vậy nên không có gì khác được thực thi khi tôi chạy. Những gì tôi muốn làm là tôi muốn cho bạn thấy cách bạn có thể kiểm tra và tìm những thứ mà bạn đang tìm kiếm. Về cơ bản, bởi vì vào thời điểm này, chúng ta đang tìm kiếm một constructor, như tôi đã nói trước đó, `typeof` chứa thông tin về kiểu, vậy nên chúng ta sẽ dump nó, chúng ta sẽ xem danh sách này và chúng ta sẽ làm việc, bạn sẽ chờ đợi, những gì bạn sẽ làm là bạn sẽ lướt qua toàn bộ danh sách này và cố gắng tìm một thứ gì đó đại diện cho bất kỳ thông tin nào về một constructor. Và những gì bạn sẽ vấp phải là một constructor được khai báo, các constructor properties, đúng không? Và rồi ở đây bạn sẽ thấy một constructor và khi bạn xem qua đây, bạn sẽ nghĩ: 'Hmm, tôi không thấy bất kỳ constructor nào trong các thuộc tính hoặc trường mà nó có, đúng không? Chắc chắn sẽ có một hàm nào đó cho phép bạn lấy nó vì kết quả của các hàm không được hiển thị ở đây vì bạn không bao giờ kích hoạt chúng.' Vậy nên, hãy tiếp tục và thực sự lấy constructors vì có thể có nhiều constructor, đúng vậy, chúng ta có thể lấy constructors và lại, chúng là các `ConstructorInfo` và đây sẽ là một danh sách. Vậy nếu nó có nhiều constructors, chúng ta sẽ có nhiều constructors ở đây, đúng không? Được rồi, vậy giờ chúng ta có thể chọn, chẳng hạn như X là một constructor cụ thể và đối với X này, chúng ta có thể tiếp tục và chỉ cần lướt qua tất cả các hàm ở đây và tìm một thứ gì đó giống như bất kỳ thông tin nào về các tham số trong constructor vì đây là kiểu `ConstructorInfo`, chúng ta chỉ muốn tìm tất cả thông tin về các tham số trên constructor này, đúng không? Và chúng ta có một hàm gọi là `GetParameters`, đúng vậy. Vậy nên, ở đây chúng ta thực thi nó và bạn có thể thấy rằng chúng ta có thể thấy kiểu tham số là `HelloService` trên `ServiceConsumer` của chúng ta. Và nếu chúng ta thêm gì đó như `string` vào đây và chạy lại, chúng ta có thể thấy rằng chúng ta có thể thấy tên của service và chúng ta có thể thấy kiểu của service. Vậy nên, điều chính mà chúng ta quan tâm là lấy kiểu của service, đúng không? Vậy đó là cách chúng ta biết được dependency nào khác cần lấy từ container để gắn vào đó, đúng không?

Vậy nên, chúng ta sẽ có một service cần phải tồn tại trước khi chúng ta giải quyết một service khác, đúng không? Vậy nếu bạn xem video về Middleware, nó cũng tương tự, nơi chúng ta cần biết về toàn bộ pipe trước khi biết về tất cả các phần của pipe trước khi chúng ta xây dựng toàn bộ pipe, đúng không? Vì vậy, chúng ta cần biết về service sẽ được đưa vào service đó trước khi thực sự khởi tạo service đó. Vậy nên, những gì chúng ta muốn làm là khởi tạo kiểu `HelloService` này trước khi tạo `ServiceConsumer`.

Vậy nên, một cách hợp lý, chúng ta cần phải kiểm tra xem dependency mà chúng ta đã trích xuất hiện tại, đúng không? Hãy đổi tên biến này thành `dependency` và chúng ta sẽ cung cấp dependency này vào đây. Một lần nữa, những gì chúng ta muốn làm là biết cái nào và thay vì 'cái nào', chúng ta muốn biết nếu nó chỉ có một constructor, chúng ta muốn lấy constructor đó. Vì vậy, chúng ta sẽ vào `dependency`, chúng ta sẽ lấy constructors và những gì chúng ta sẽ làm là lấy một constructor đơn lẻ. Và điều này sẽ cho phép chúng ta kiểm tra, chúng ta có thể thêm nhiều kiểm tra lỗi hơn về chủ đề này hoặc có thể không, nhưng về cơ bản, điều này làm gì? Bạn chỉ có thể có một constructor vì nếu không, vào thời điểm runtime, làm sao chúng ta biết phải resolve constructor nào, đúng không? Vậy nên, bạn chỉ có thể lấy một constructor và hàm `Single` của chúng ta, nếu chúng ta không nhận được một response đơn lẻ từ hàm này, nó sẽ ném ngoại lệ. Vậy nên, chúng ta đảm bảo rằng chúng ta nhận được một constructor đơn lẻ và sau đó chúng ta sẽ sử dụng constructor đó, đúng vậy.

Sau đó, chúng ta sẽ lấy constructor của mình và lấy tất cả các tham số. Vì chúng ta có thể có nhiều tham số, chúng ta muốn khởi tạo mọi service trước khi khởi tạo service tiếp theo. Và một lần nữa, tôi sẽ sử dụng đệ quy cho việc này. Cách làm recursion của chúng ta rất tốt trong trường hợp này. Những gì tôi muốn làm là tạo một vòng lặp for, chúng ta có các parameters, tôi sẽ đi qua từng parameter cho đến khi độ dài của chúng ta kết thúc và những gì tôi muốn làm là tăng biến đếm chuẩn cho vòng lặp, không có gì quá phức tạp.

Giờ đây, những gì tôi muốn làm là lưu trữ các implementations của những parameters đó, các services mà tôi sẽ lấy sẽ giải quyết bằng cách sử dụng `Activator`. Tôi cần lưu trữ chúng ở đâu đó trước khi tôi truyền chúng vào `Activator` cuối cùng, đúng vậy. Vậy nên, hãy tiếp tục và tạo `parameterImplementations`, và đây sẽ là một mảng `object` mới vì khi chúng ta khởi tạo các parameters, chúng sẽ là `object`, chúng thực chất sẽ là các thể hiện, chúng ta chỉ không biết chúng thuộc kiểu nào, nhưng không sao.

Vậy nên, parameters top length và chúng ta sẽ có số lượng thể hiện như số lượng parameters chúng ta có, vì vậy điều này có lý tưởng. Sau đó, chúng ta muốn điền vào chúng, đúng vậy. Vậy nên, hãy tiếp tục vì khi chúng ta có các parameters, đó là `ParameterInfo` và nó là một mảng. Vậy nên, những gì chúng ta có thể làm là chúng ta có thể lấy `Activator`, nó trả về một `object`, vì vậy không sao nếu chúng ta lưu nó ở đó, đúng vậy. Chúng ta sẽ tạo một instance và thay vì dependency, hãy để tôi ẩn phần output của mình. Vậy nên, chúng ta sẽ vào parameters và chúng ta sẽ lấy cùng một index mà chúng ta đang lưu vào và chúng ta chỉ lấy kiểu của chúng ta, đúng vậy. Nó sẽ là một function, ô và tôi nghĩ thực ra nó không phải là `GetType` vì tôi muốn, vâng, `GetType` sẽ lấy cho tôi kiểu đối tượng của `ParameterInfo`, cái tôi muốn là tôi muốn kiểu tham số, vậy là bất kỳ tham số nào đại diện, tôi muốn lấy kiểu tham số đó, đúng không? Vậy nên, trong trường hợp của chúng ta, kiểu tham số sẽ là `HelloService`, đúng vậy, và chúng ta sẽ tạo một instance của `HelloService`, chúng ta sẽ có các services khác, mặc dù hiện tại chỉ có một, nên điều đó cũng ổn. Những gì chúng ta muốn bây giờ là chúng ta muốn truyền vào, nhưng như bạn có thể thấy, chúng ta sẽ mất service không có tham số. Vậy nên, chúng ta muốn làm gì là chúng ta muốn thêm một câu lệnh `if` để kiểm tra xem độ dài của các tham số có lớn hơn 0 không, chúng ta có các tham số hay không? Nếu chúng ta có, thì hãy tiếp tục và làm toàn bộ việc này để giải quyết thêm services. Và ở cuối cùng ở đây, chúng ta có thể làm giống như chúng ta đã làm ở đây, tuy nhiên chúng ta cũng sẽ cung cấp các parameters ở cuối, đúng vậy. Và bây giờ, hãy tiếp tục và loại bỏ phần này ở đầu, hoặc thực ra, tôi sẽ comment nó đi để ai đó cần nó cho bất kỳ lý do nào và tôi sẽ lấy lại "Hello World", đúng vậy.

Vậy nên, những gì bạn có thể làm là đặt breakpoint vào đây, không biết tại đây, chạy nó, bạn sẽ thấy dependency là `ServiceConsumer`, các `parameterImplementations`, chúng ta có thể xem `parameterImplementations`, bạn có thể thấy rằng nó là `HelloService` vì chúng ta xem các parameters, chúng ta sẽ thấy rằng đại diện tham số là `HelloService`, là tham số của constructor của lớp này, và ở đây chúng ta có thể thấy kiểu tham số là `HelloService`, service mà chúng ta sẽ cố gắng giải quyết và nếu chúng ta kiểm tra, chúng ta có `Container.Dependencies`, và một lần nữa, nếu chúng ta xem các dependencies, chúng ta đã đăng ký `HelloService`, và đó là lý do tại sao chúng ta có thể lấy nó, đúng vậy. Vậy nên, điều này rất tốt, nó đang hoạt động vì hiện tại chúng ta chỉ có hai dependencies. Hãy tiếp tục và thêm một lớp nữa, và điều này thực sự sẽ thử thách nó.

Bây giờ, hãy tạo một lớp công khai và chúng ta sẽ tạo một `MessageService`, và nó sẽ làm điều tương tự như `HelloService`, là một hàm `Print`, nhưng thay vì xuất ra "Hello World", chúng ta sẽ xuất ra "Message". Vậy nên, hãy tiếp tục và thay đổi điều này. Được rồi, vậy chúng ta sẽ đặt nó vào trong `HelloService` và chúng ta sẽ đặt `MessageService` vào trong `HelloService`, chúng ta sẽ gọi nó là `Message`, và những gì tôi mong đợi là vì để cho bạn thấy cách chúng ta kích hoạt bằng cách sử dụng `Activator` ở đây sẽ bị phá vỡ ngay lập tức khi chúng ta vượt qua cấp độ thứ hai. Vì vậy, chúng ta đang thực hiện ở đây là chúng ta đang tạo một implementation cụ thể của service nếu nó có các parameters, chúng ta chỉ mong đợi các parameters cho service sẽ không có tham số, đúng vậy. Vậy còn nếu service mà chúng ta inject vào đây cũng sẽ có các parameters, nó sẽ bị phá vỡ ngay tại đây, đúng không?"

"Vậy đây là những gì tôi sẽ chỉ cho bạn cách để thực chất vượt qua nó và đây là nơi tôi sẽ sử dụng đệ quy để thực chất khởi tạo service này trước, sau đó là service này và sau đó là service tiếp theo, được rồi. Và ở đây cho phần message, hãy đặt nó ở đây và đảm bảo rằng chúng ta xuất nó không phải với một thứ gì đó dạng phần trăm mà là dấu đô la, được rồi. Vậy nên bạn có thể thấy nó bị phá vỡ ngay tại đây. Vậy nên chúng ta muốn làm gì ở đây vì ở đây chúng ta đang thực chất resolve một kiểu cụ thể, chúng ta muốn resolve một object thay vì kiểu, đúng không? Chúng ta vẫn có thể ép kiểu, nhưng về cơ bản, đây là sự khác biệt giống nhau, nhưng vì chúng ta đang void trả về void ở đây, chúng ta thực sự muốn trả về một object ở đây. Vậy nên đối với việc lấy service, chúng ta sẽ cung cấp kiểu. Được rồi, và chúng ta sẽ di chuyển toàn bộ thứ chúng ta có ở đây xuống đó, được rồi. Hãy tiếp tục và đặt nó ở đây, kiểu của một kiểu T, chúng ta không còn cần cái đó nữa. Hãy tiếp tục và đặt nó ở đây, không phải là lớp kiểu mà là biến kiểu, việc chuyển đổi, chúng ta không còn cần nó nữa vì chúng ta trả về một object. Những gì chúng ta sẽ làm ở đây không phải là float, chúng ta sẽ gọi `GetService<TypeOfT>` và chúng ta sẽ ép kiểu nó sang một kiểu, được rồi? Đó là service, đúng vậy, và đừng quên dấu chấm phẩy. Nhưng bây giờ điều này, về cơ bản, điều này cho phép chúng ta làm gì bây giờ là resolve việc tạo instance này vì parameter của chúng ta, service mà chúng ta sẽ cố gắng resolve, sẽ không có parameter hoặc sẽ có parameter, và nếu nó có, chúng ta muốn lại resolve một service cho đến khi nó không còn parameter nữa, được rồi? Vậy nên, những gì chúng ta sẽ làm là chúng ta sẽ truyền `GetService` và chúng ta sẽ lấy kiểu parameter mà chúng ta sẽ cung cấp vào đó, được rồi? Dấu chấm phẩy ở đây, đóng lại, chạy nó, vậy sequen không chứa phần tử nào phù hợp, đây thực chất là lỗi 'Không tìm thấy trong dependencies, chưa được đăng ký với container Dependency Injection'. Hãy tiếp tục và lấy `MessageService` của chúng ta vào đây và bây giờ chúng ta có thể thêm nó như thế này. Một điều chúng ta làm là chúng ta đang lấy `UserQuery` từ `MessageService`, đây là vì trên `MessageService`, tôi thực sự không gọi hàm `Message`. Hãy tiếp tục và chạy nó và ở đây chúng ta nhận được "Hello World", được rồi, "Double Hello, double the world". Vậy nên, tại điểm này, chúng ta đã gần như lắp ráp một container Dependency Injection nhỏ và chúng ta cũng có một resolver cho dependencies, nơi chúng ta có thể sử dụng resolver để lấy một service cụ thể, đúng không? Và sau đó gọi bất kỳ hàm nào trên service đó. Và nhớ rằng, chương trình cần bắt đầu từ đâu đó, vì vậy ở nền, đây là những gì nó sẽ sử dụng và khởi tạo, những gì nó đang cố gắng giảm bớt cho chúng ta là việc sử dụng từ khóa `new`. Tôi không nói rằng nó xấu, nhưng bạn chỉ nên sử dụng từ khóa `new` khi có lý do chính đáng để khởi tạo thủ công, được rồi? Vậy nên, hãy tiếp tục và tạo một phần quản lý lifetimes khác. Vậy nên, lifetimes cho các services của chúng ta, tôi sẽ di chuyển cái này vì nó tồn tại trong cái trước và tôi sẽ gọi nó là `Lifetimes`. Được rồi, vậy thay vì chỉ lưu trữ một kiểu cho dependency, tôi muốn cho nó một object. Tôi muốn biết thêm một chút về object mà chúng ta đang đăng ký. Vậy nên, service, dependency mà chúng ta đang đăng ký, tôi muốn biết về lifetime của nó. Hãy tạo một enum và chúng ta sẽ tạo một `DependencyLifetime` và hãy tiếp tục và chạy với `Singleton` là 0 và `Transient` là 1, được rồi? Vậy chúng ta có `Singleton` và `Transient`. `Singleton` sẽ tồn tại lâu dài như chúng ta sẽ thấy trong một phút nữa và `Transient` sẽ là một instance mới mỗi lần, vậy nên thực ra trước khi chúng ta đi sâu, tôi sẽ cho bạn thấy cách tôi sẽ sử dụng để kiểm tra điều này. Vậy ở đây, tôi sẽ tạo một parameter trong constructor, tôi sẽ tạo một random và tôi sẽ gọi `Next` trong constructor, đúng không? Và [Music] Tôi nghĩ họ sẽ ổn, hãy tiếp tục và cái này sẽ là một integer, tôi nghĩ đúng rồi. Vậy nên chúng ta có một random integer và chúng ta sẽ trả về một message với integer ngẫu nhiên này, được rồi? Vậy nên, điều quan trọng là không quên rằng đây là những gì chúng ta thực sự đang làm. Vậy nên, những gì tôi sẽ làm ở đây là tôi sẽ resolve service một lần nữa, tôi sẽ làm điều này ba lần, vậy nên chúng ta có một instance mới mỗi lần, yeah, vậy hãy làm một, hai, ba, cùng một thứ với cái này và ba, được rồi, vậy chúng ta nhận được một số ngẫu nhiên mỗi lần và về cơ bản, điều này có nghĩa là chúng ta nhận được một instance ngẫu nhiên vì constructor được gọi mỗi lần một service cần được tạo và đó là lúc chúng ta thiết lập giá trị random, được rồi? Vậy nên, chúng ta đang cố gắng làm phẳng nó, chúng ta cố gắng có cùng một số trên tất cả các `MessageService` sẽ được resolve vì sau đó chúng ta biết rằng nó sẽ là một `Singleton`. Vậy nên, đối với `MessageService`, chúng ta đang cố gắng đăng ký nó như một `Singleton`, được rồi? Vậy dependency nào, dependency cụ thể mà chúng ta sẽ và đó là nơi tôi muốn nói rằng dependency này cũng mang theo lifetime của nó, đúng vậy? Vậy nên, hãy tiếp tục và tạo một lớp và chúng ta sẽ gọi nó là `Dependency`. Chúng ta sẽ có một public `Type Type`, đúng vậy. Vậy kiểu của service, kiểu của dependency và chúng ta sẽ có một `DependencyLifetime`, đúng không? Vậy bây giờ, khi chúng ta thêm một dependency, hãy tiếp tục và nói rằng chúng ta sẽ sử dụng các hàm này vì chúng trông đẹp hơn, đúng không? Thay vì dependency của chúng ta ở đây, chúng ta sẽ là một `Singleton` và ở đây chúng ta sẽ nói là `Transient`. Vậy nên, bây giờ chúng ta sẽ làm gì là tạo một constructor và tôi chỉ đặt những tên thật ngu ngốc, đừng làm điều này, tôi chỉ làm vì tôi không muốn gõ hết câu và lười biếng để tiết kiệm thời gian. Chúng ta chỉ muốn thêm một dependency mới mỗi lần chúng ta đăng ký nó, đúng không? Vậy nên, dependency và chúng ta có kiểu, và khi chúng ta thêm `Singleton`, chúng ta chỉ nói rằng đây là những gì chúng ta thực sự gọi, chúng ta chỉ muốn thêm một `Singleton`, đúng không? Vậy nên, dependency với lifetime, hãy tiếp tục và đặt nó là `Singleton` và lý do nó không compile là vì nó là kiểu `Type`, hãy tiếp tục và chỉ định rằng nó là kiểu `Dependency`. Bây giờ, nó thấy tôi đã xóa phần tử, tôi lẽ ra nên khởi tạo danh sách dependencies trong constructor thay vì bán hàng, lỗi từ tôi, tôi lẽ ra nên nhận ra sớm hơn hoặc muộn hơn, dù sao thì. Vậy nên, và cái giống như cái này chúng ta muốn làm ở đây, vậy để tôi đóng cái đó, tôi sẽ đặt nó ở đây và thay vào đó chúng ta chỉ nói rằng đây là `Transient`, đúng không? Và rồi bây giờ tất cả những gì chúng ta muốn làm là chỉ tham chiếu đến kiểu ở đây mà chúng ta muốn lấy và thay vì kiểu, chúng ta sẽ trả về một dependency. Vậy nên, điều này sẽ phá vỡ một số đoạn code ở đây và về cơ bản, điều tôi muốn làm là loại bỏ `ServiceConsumer` này, chúng ta muốn thêm một service khác ở đây và cả hai sẽ được thêm như `Transient` và cái cuối cùng sẽ là `Singleton`. Và tôi nghĩ tôi đã đánh vần sai nó, có thể cái đó là `Singleton`, tôi thậm chí không thể đánh vần `Singleton`, được rồi? Vậy nên, chúng ta thêm `Transient` và chúng ta thêm `Singleton`. Vậy nên, `MessageService`, đây là phần mà chúng ta sẽ cần xuất cùng một số, vì vậy về cơ bản, ở đây bạn có thể thấy rằng chúng ta có các số khác nhau, chúng ta muốn đặt cùng một số, vậy nên đây là nơi tôi sẽ tạo các implementations, đúng không? Vậy nên, đây là nơi tôi muốn thực chất nói hoặc kiểm tra, dependency này có phải là `Singleton` không, nếu có, nó đã được tạo ra trước đó chưa? Nếu chưa, hãy tạo nó và lưu nó lại và sau đó sau này chúng ta có thể lấy cùng một implementation và tôi sẽ lưu implementation vào object dependency nữa, đúng không? Vậy nên, để làm cho cuộc sống của tôi dễ dàng hơn một chút, tôi sẽ biến nó thành một object, nó sẽ là một implementation. [Music] Làm một bowl của implementation để thực chất làm cho cuộc sống của tôi dễ dàng hơn một chút về việc kiểm tra nó đã được implement hay chưa, đúng không? Vậy nên, chúng ta sẽ đi với constructor không có tham số trước vì nó dễ hơn và sau đó chúng ta có thể thay đổi điều này sau. Vậy nên, constructor, những gì chúng ta làm ở đây là chúng ta đang lấy một dependency, vậy nên những gì chúng ta cần làm ở đây là lấy một dependency từ một kiểu, vậy nên chúng ta sẽ lấy một constructor và nhân tiện, điều này không phải là một implementation đúng và nó không nhất thiết phải đúng, chúng ta có thể làm những thứ khác nhau ở đây, chúng ta có thể một cách cơ bản khi chúng ta đăng ký dependency, chúng ta cũng có thể lưu thông tin về dependency liệu nó có constructor không hoặc không hoặc có parameter hay không hoặc gì đó như vậy khi chúng ta đăng ký nó và điều đó tiết kiệm thời gian của chúng ta khi resolve, có thể sẽ hiệu quả hơn một chút nhưng dù sao thì đây chỉ là một ví dụ để giải thích cách nó hoạt động, được rồi? Vậy nên, chúng ta có kiểu dependency của chúng ta hoặc thay vào đó, lớp dependency chứa kiểu và chúng ta vẫn kiểm tra constructor không có tham số hoặc không, đúng không? Vậy nên, về cơ bản, chúng ta không cần resolve gì khác, chúng ta có thể tiếp tục và tạo instance này, vậy nên chúng ta sẽ tiếp tục và tạo kiểu, đúng không? Vậy nên, đây là nơi chúng ta muốn kiểm tra dependency của mình một chút, dependency của chúng ta, chúng ta muốn kiểm tra nó đã được implement chưa, đúng không? Vậy nên, nếu nó đã được implement, chúng ta muốn tiếp tục và lấy dependency của mình và trả về implementation, đúng không? Nếu nó đã được implement, không cần thiết phải re-implement nó nữa, dù lifetime của nó là gì, nó cũng không quan trọng. Vậy thì chúng ta có thể kiểm tra nếu nó chưa được implement, chúng ta cần implement nó vì bây giờ chúng ta có thể kiểm tra lifetime, đúng không? Vậy nên, nếu lifetime là `Singleton`, được rồi, chúng ta sẽ cần lưu trữ nó, vậy nên hãy tiếp tục và làm `ImplementationBlendMeditation` và đối với dependency của chúng ta, chúng ta sẽ nhận diện bây giờ, vậy hãy tiếp tục và tạo một hàm công khai `AddImplementation`, nó sẽ nhận một `object implementation` và implementation, rất nhiều implementations, được rồi, vậy nên khi tôi thêm implementation, tôi thực chất đang lưu trữ implementation, tôi chỉ đặt một flag rằng nó đã được implement, đừng implement lại lần nữa, đúng không? Và rõ ràng, điều này, tôi sẽ nhắc lại vì tôi đang nghĩ về tất cả các cách khác nhau để làm điều này, đây không phải là implementation đúng nhất và một lần nữa, đây chỉ là một quá trình sáng tạo để giải thích cách nó có thể hoạt động, được rồi? Vì vậy, tôi sẽ thêm implementation ở đây, đúng không? Vậy nên, đã implement trước đó chưa? Vâng, trả về implementation, đây có phải là singleton không? Nếu có, hãy tạo implementation và lưu nó lại. Vậy nên, sau này nếu chúng ta cố gắng resolve lại nó, chúng ta chỉ lấy cache, đúng không? Về cơ bản, một khi chúng ta đã thêm implementation, chúng ta có thể tiếp tục và trả về nó ở đây nữa, hoặc thay vào đó, có lẽ hãy lấy nó ra đây và trả về implementation ở đây, được rồi? Quá nhiều chữ trong từ 'implementation', được rồi? Vậy nên, về cơ bản, nhìn vào điều này, tôi nghĩ nó sẽ hoạt động. Một điều mà tôi đang nghĩ là chúng ta sẽ cần làm, chúng ta sẽ cần làm cùng một loại bước ở đây, đúng không? Vậy nên, những gì tôi sẽ làm là tôi sẽ lấy cái này ra thành một hàm, đúng không? Vậy nên, chúng ta sẽ tạo một hàm, nơi tôi sẽ thực chất tạo implementation, đây là những gì nó đang làm. Vậy nên, từ một dependency, chúng ta muốn resolve một implementation, nó sẽ là một object mà chúng ta sẽ trả về và nhớ rằng lý do chúng ta trả về một object là vì đó là những gì hàm `CreateInstance` của `Activator` trả về. Vậy nên, tạo implementation, cung cấp dependency, được rồi? Vậy nên, ở đây chúng ta có dependency, hãy tiếp tục và di chuyển toàn bộ code này vào đây và chúng ta gần như đã có tất cả ở đây. Vậy nên, một điều hiện tại là `CreateInstance`, chúng ta không thể hoán đổi nó với cái này được, vậy nên những gì chúng ta muốn làm là truyền một factory nhỏ. Vậy nên, theo cùng một cách chúng ta có thể, chúng ta có thể cơ bản định nghĩa cách chúng ta muốn sử dụng hàm `CreateInstance` hoặc cách chúng ta muốn tạo dependency của mình. Vậy nên, tôi sẽ tạo một lớp hàm và nó sẽ nhận kiểu mà chúng ta muốn tạo và nó sẽ trả về một object, đúng không? Tôi chỉ định tên là `Factory`, được rồi? Và `Factory` này, chúng ta sẽ tiếp tục và thay thế nó ở đây và tôi vẫn sẽ truyền dependency type, vậy nên, bạn biết thứ tự này trông như thế nào, chúng ta sẽ thực chất tạo implementation, quá nhiều từ tôi không thể đối phó với nó, được rồi? Vậy nên, dependency và hàm. Vậy nên, chúng ta sẽ có kiểu và tôi muốn sử dụng `Activator.CreateInstance` và chỉ làm nó cho kiểu và sau đó ở đây, tôi thực chất muốn làm giống như nó đang làm, chỉ là kiểu mà tôi sẽ cung cấp ở đây và các tham số còn lại, thì về cơ bản, đây là cùng một thứ. Tôi thực chất đã làm, nếu bạn xem thiết kế nhỏ, nếu bạn xem video về middleware, những gì tôi thực chất đã làm là tôi đã tạo một middleware nhỏ ở đây, đây giống như nhiều middleware trong functional programming, đúng không? Vậy nên, nếu bạn không biết nhiều về functional programming để có thể làm những thứ như thế này, để mở rộng tư duy đó, tôi khuyên bạn nên xem một ngôn ngữ lập trình gọi là Closure, nhưng vâng, đây thực chất là những gì sẽ xảy ra bây giờ. Vậy nên, hãy tiếp tục và chạy nó và những gì bạn sẽ thấy là chúng ta đang nhận cùng một số cho tất cả chúng, đúng không? Và chỉ để đảm bảo rằng điều này đang xảy ra, vậy nên chúng ta thực chất có thể định nghĩa một `Singleton` cho `HelloService` của chúng ta nữa. Hãy tiếp tục và làm điều đó và cái này thực sự phản ánh điều đó nhiều nhưng những gì chúng ta có thể làm là chúng ta có thể thêm cùng một random ở đây và chúng ta có thể chỉnh sửa hoặc không, vậy nên số `Hello` và chúng ta có random ở đây. Vậy nên, số random đầu tiên sẽ từ `HelloService`, số random thứ hai sẽ từ `MessageService`, đúng không? Vậy nên, bạn có thể thấy những số này giống nhau trong cột này và cột khác, vậy nên nếu tôi đổi `HelloService` thành `Transient`, bạn sẽ thấy những số này bắt đầu thay đổi vì lifetime khác nhau. Vậy nên, chúng ta cần lấy một `HelloService` mới mỗi khi chúng ta chạy, khi chúng ta resolve service loại `ServiceConsumer`, vậy nên một `HelloService` mới được tạo ra cho mỗi chúng nhưng cùng một services được inject vào tất cả các services khác nhau đó, đúng không? Vậy nên, đây thực chất giống như một implementation cho container Dependency Injection, nếu bạn thực sự đang cố gắng áp dụng kiến thức này vào cách nó được chuyển dịch sang Model-View-Controller (MVC), vậy MVC làm thế nào để nó được inject vào các controllers và các actions, những nơi cũng sử dụng reflection nhưng ở một cấp độ hơi khác bởi vì MVC tự nó được xây dựng bằng reflection giống như middleware được xây dựng và Dependency Injection của chúng ta được xây dựng. Nếu bạn muốn tôi giải thích Model-View-Controller theo cách mà tôi giải thích hai thứ này, tôi sẽ để lại bình luận, đúng không? Nếu không, tôi sẽ không thực sự chỉ cho bạn cách nó được ánh xạ cho đến khi tôi có thể tạo video đó. Nhưng vâng, đừng nói quá nhiều, đúng không? Vâng, đây sẽ là kết thúc cho video này. Nếu bạn thích xem nó, hãy nhấn like và đăng ký. Nếu bạn có bất kỳ câu hỏi nào, hãy chắc chắn để lại chúng trong phần bình luận. Đừng quên tham gia kênh Discord, tôi sẽ cập nhật khá nhiều ở đó. Tôi cũng có một giveaway sắp tới để kỷ niệm 10k, tôi sẽ tổ chức trên stream Twitch của mình có thể, vậy đừng quên theo dõi stream Twitch của tôi và vâng, hy vọng tôi sẽ gặp bạn ở đâu đó, bye bye."

Nhận xét

Bài đăng phổ biến từ blog này

Generics

Channels