Khi nào chúng ta nên sử dụng tiêm phụ thuộc (C #) [đã đóng]


8

Tôi muốn chắc chắn rằng tôi hiểu khái niệm tiêm phụ thuộc (DI). Chà, tôi thực sự hiểu khái niệm, DI không phức tạp: bạn tạo một giao diện sau đó bạn chuyển việc thực hiện giao diện của tôi cho lớp sử dụng nó. Cách phổ biến để vượt qua nó là bởi constructor nhưng bạn cũng có thể truyền nó bằng setter hoặc phương thức khác.

Điều tôi không chắc là hiểu khi nào nên sử dụng DI.

Cách sử dụng 1: Tất nhiên sử dụng DI trong trường hợp bạn có nhiều triển khai giao diện của bạn có vẻ logic. Bạn có một kho lưu trữ cho Máy chủ SQL của mình, sau đó một kho lưu trữ khác cho cơ sở dữ liệu Oracle của bạn. Cả hai đều có chung giao diện và bạn "tiêm" (đây là thuật ngữ được sử dụng) giao diện bạn muốn trong thời gian chạy. Điều này thậm chí không phải là DI, đây là lập trình OO cơ bản ở đây.

Cách sử dụng 2: Khi bạn có một lớp nghiệp vụ với nhiều dịch vụ với tất cả các phương thức cụ thể, có vẻ như cách tốt nhất là tạo giao diện cho mỗi dịch vụ và cũng thực hiện việc triển khai ngay cả khi dịch vụ này là duy nhất. Bởi vì điều này là tốt hơn để bảo trì. Đây là cách sử dụng thứ hai tôi không hiểu.

Tôi có khoảng 50 lớp kinh doanh. Không có gì là phổ biến giữa họ. Một số kho lưu trữ nhận hoặc lưu dữ liệu trong 3 cơ sở dữ liệu khác nhau. Một số đọc hoặc ghi tập tin. Một số làm hành động kinh doanh thuần túy. Ngoài ra còn có trình xác nhận và trợ giúp cụ thể. Thách thức là quản lý bộ nhớ vì một số lớp được điều khiển từ các vị trí khác nhau. Trình xác thực có thể gọi một số kho lưu trữ và các trình xác nhận khác có thể gọi lại cùng một kho lưu trữ.

Ví dụ: Lớp nghiệp vụ

public class SiteService : Service, ICrud<Site>
{
    public Site Read(Item item, Site site)
    {
        return beper4DbContext.Site
            .AsNoTracking()
            .SingleOrDefault(y => y.SiteId == site.Id && y.ItemId == item.Id)
    }

    public Site Read(string itemCode, string siteCode)
    {       
        using (var itemService = new ItemService())
        {
            var item = itemService.Read(itemCode);
            return Read(item, site);
        }
    }
}
public class ItemSiteService : Service, ICrud<Site>
{
    public ItemSite Read(Item item, Site site)
    {
        return beper4DbContext.ItemSite
            .AsNoTracking()
            .SingleOrDefault(y => y.SiteId == site.Id && y.ItemId == item.Id)
    }

    public ItemSite Read(string itemCode, string siteCode)
    {        
        using (var itemService = new ItemService())
        using (var siteService = new SiteService())
        {
            var item = itemService.Read(itemCode);
            var site = siteService.Read(itemCode, siteCode);
            return Read(item, site);
        }
    }
}

Bộ điều khiển

public class ItemSiteController : BaseController
{
    [Route("api/Item/{itemCode}/ItemSite/{siteCode}")]
    public IHttpActionResult Get(string itemCode, string siteCode)
    {
        using (var service = new ItemSiteService())
        {
            var itemSite = service.Read(itemCode, siteCode);
            return Ok(itemSite);
        }
    }
}

Ví dụ này rất cơ bản nhưng bạn thấy cách tôi có thể dễ dàng tạo 2 phiên bản của itemService để lấy itemSite. Sau đó, mỗi dịch vụ đi kèm với bối cảnh DB của mình. Vì vậy, cuộc gọi này sẽ tạo ra 3 DbContext. 3 kết nối.

Ý tưởng đầu tiên của tôi là tạo ra singleton để viết lại tất cả mã này như dưới đây. Mã dễ đọc hơn và quan trọng nhất là hệ thống singleton chỉ tạo một phiên bản của mỗi dịch vụ được sử dụng và tạo nó trong cuộc gọi đầu tiên. Hoàn hảo, ngoại trừ tôi vẫn có bối cảnh khác biệt nhưng tôi có thể làm cùng một hệ thống cho bối cảnh của mình. Vậy là xong.

Lớp kinh doanh

public class SiteService : Service, ICrud<Site>
{
    public Site Read(Item item, Site site)
    {
        return beper4DbContext.Site
            .AsNoTracking()
            .SingleOrDefault(y => y.SiteId == site.Id && y.ItemId == item.Id)
    }

    public Site Read(string itemCode, string siteCode)
    {       
            var item = ItemService.Instance.Read(itemCode);
            return Read(item, site);
    }
}
public class ItemSiteService : Service, ICrud<Site>
{
    public ItemSite Read(Item item, Site site)
    {
        return beper4DbContext.ItemSite
            .AsNoTracking()
            .SingleOrDefault(y => y.SiteId == site.Id && y.ItemId == item.Id)
    }

    public ItemSite Read(string itemCode, string siteCode)
    {        
            var item = ItemService.Instance.Read(itemCode);
            var site = SiteService.Instance.Read(itemCode, siteCode);
            return Read(item, site);       
    }
}

Bộ điều khiển

public class ItemSiteController : BaseController
{
    [Route("api/Item/{itemCode}/ItemSite/{siteCode}")]
    public IHttpActionResult Get(string itemCode, string siteCode)
    {
            var itemSite = service.Instance.Read(itemCode, siteCode);
            return Ok(itemSite);
    }
}

Một số người nói với tôi theo thông lệ tốt, tôi nên sử dụng DI với một trường hợp duy nhất và sử dụng singleton là thực hành xấu. Tôi nên tạo một giao diện cho mỗi lớp nghiệp vụ và khởi tạo nó với sự trợ giúp của container DI. Có thật không? DI này đơn giản hóa mã của tôi. Khó tin.


4
DI chưa bao giờ về khả năng đọc.
Robert Harvey

Câu trả lời:


14

Trường hợp sử dụng "phổ biến" nhất cho DI (ngoài cách sử dụng mẫu "chiến lược" mà bạn đã mô tả) có lẽ là thử nghiệm đơn vị.

Ngay cả khi bạn nghĩ rằng sẽ chỉ có một triển khai "thực" cho giao diện được chèn, trong trường hợp bạn thực hiện kiểm thử đơn vị, thường có một cách thứ hai: triển khai "giả" với mục đích duy nhất là thực hiện thử nghiệm tách biệt. Điều đó mang lại cho bạn lợi ích của việc không phải đối phó với sự phức tạp, các lỗi có thể xảy ra và có thể là tác động hiệu suất của thành phần "thực".

Vì vậy, DI không phải để tăng khả năng đọc, nó được sử dụng để tăng khả năng kiểm tra (- tất nhiên, không độc quyền).

Đây không phải là một kết thúc trong chính nó. Nếu lớp của bạn ItemServicelà một lớp rất đơn giản, không thực hiện bất kỳ truy cập mạng hoặc cơ sở dữ liệu bên ngoài nào, do đó, nó không cản trở việc viết các bài kiểm tra đơn vị cho một cái gì đó như thế SiteService, kiểm tra cách ly sau có thể đáng nỗ lực, do đó DI sẽ không cần thiết. Tuy nhiên, nếu ItemServiceđang truy cập các trang web khác bằng mạng, có thể bạn sẽ muốn kiểm tra đơn vị SiteServicetách rời khỏi nó, điều này có thể được thực hiện bằng cách thay thế "thực" ItemServicebằng a MockItemService, cung cấp một số mặt hàng giả được mã hóa cứng.

Hãy để tôi chỉ ra một điều khác: trong các ví dụ của bạn, người ta có thể lập luận rằng người ta sẽ không cần DI ở đây để kiểm tra logic kinh doanh cốt lõi - các ví dụ luôn hiển thị hai biến thể của Readphương thức, một biến thể có logic kinh doanh thực sự (có thể là đơn vị được kiểm tra mà không có DI) và một đơn vị chỉ là mã "keo" để kết nối ItemServicevới logic cũ. Trong trường hợp được hiển thị, đó thực sự là một đối số hợp lệ chống lại DI - trên thực tế, nếu DI có thể tránh được mà không phải hy sinh khả năng kiểm tra theo cách đó, thì hãy tiếp tục. Nhưng không phải tất cả các mã trong thế giới thực đều đơn giản và thường DI là giải pháp đơn giản nhất để đạt được khả năng kiểm tra đơn vị "đủ".


1
Tôi thực sự hiểu nó nhưng nếu tôi không làm thử nghiệm đơn vị. Tôi làm thử nghiệm tích hợp với một cơ sở dữ liệu giả mạo. Thay đổi được vận hành quá thường xuyên để duy trì kiểm tra đơn vị và dữ liệu sản xuất của chúng tôi luôn thay đổi. Vì vậy, kiểm tra với bản sao dữ liệu thực là cách duy nhất chúng ta có thể kiểm tra nghiêm túc. Thay cho Kiểm thử đơn vị, chúng tôi bảo vệ tất cả các phương thức của mình bằng các hợp đồng mã. Tôi biết điều này không giống nhau và tôi biết kiểm thử đơn vị cộng nhưng tôi không có thời gian để ưu tiên kiểm tra đơn vị. Đó là một thực tế, không phải là một sự lựa chọn.
Bastien Vandamme

1
@BastienVandamme: có một khoảng trống lớn giữa "không có thử nghiệm đơn vị nào cả" và "chúng tôi thử nghiệm mọi thứ". Và tôi chưa bao giờ thấy một dự án lớn mà không thể tìm thấy một số lợi ích trong việc sử dụng các bài kiểm tra đơn vị ít nhất là cho một số phần. Xác định các bộ phận đó, sau đó kiểm tra xem DI có bắt buộc phải làm cho chúng có thể kiểm tra được không.
Doc Brown

4

Bằng cách không sử dụng phép nội xạ phụ thuộc, bạn cho phép bản thân tạo các kết nối vĩnh viễn đến các đối tượng khác. Các kết nối bạn có thể ẩn bên trong nơi họ sẽ làm mọi người ngạc nhiên. Các kết nối mà họ chỉ có thể thay đổi bằng cách viết lại những gì bạn đang tạo.

Thay vào đó, bạn có thể sử dụng phép tiêm phụ thuộc (hoặc chuyển tham chiếu nếu bạn học cũ như tôi) để làm cho những gì đối tượng cần rõ ràng mà không buộc nó phải xác định cách đáp ứng nhu cầu của nó.

Điều này không buộc bạn phải chấp nhận nhiều tham số. Ngay cả những người có mặc định rõ ràng. Trong C #, các sod may mắn đã đặt tên và các đối số tùy chọn . Điều đó có nghĩa là bạn có các đối số mặc định. Nếu bạn không bị ràng buộc tĩnh với các mặc định của mình, ngay cả khi bạn không sử dụng chúng, bạn có thể cho phép DI mà không bị quá nhiều tùy chọn. Điều này tuân theo quy ước về cấu hình .

Kiểm tra không phải là một biện minh tốt cho DI. Khoảnh khắc bạn nghĩ rằng có ai đó sẽ bán cho bạn một khung mô phỏng wiz bang sử dụng phép phản chiếu hoặc một số phép thuật khác để thuyết phục bạn rằng bạn có thể quay lại cách bạn làm việc trước đây và sử dụng phép thuật để làm phần còn lại.

Được sử dụng chính xác, thử nghiệm có thể là một cách tốt để hiển thị nếu một thiết kế bị cô lập. Nhưng đó không phải là điểm chính của nó. Nó không ngăn cản nhân viên bán hàng cố gắng chứng minh rằng với đủ phép thuật, mọi thứ đều bị cô lập. Giữ phép thuật ở mức tối thiểu.

Điểm của sự cô lập này là để quản lý sự thay đổi. Thật tuyệt nếu một thay đổi có thể được thực hiện ở một nơi. Thật không hay khi phải theo dõi nó qua tập tin sau khi tập tin hy vọng rằng sự điên rồ sẽ chấm dứt.

Đưa tôi vào một cửa hàng từ chối làm thử nghiệm đơn vị và tôi vẫn sẽ làm DI. Tôi làm điều đó bởi vì nó cho phép tôi tách những gì cần thiết khỏi cách nó được thực hiện. Thử nghiệm hoặc không thử nghiệm tôi muốn sự cô lập đó.


2
Bạn có thể giải thích lý do tại sao thử nghiệm không phải là một biện minh tốt cho DI? Bạn dường như đang kết hợp nó với các khung phản chiếu ma thuật - và tôi hiểu rằng không thích những thứ đó - nhưng không làm cho bất kỳ ý kiến ​​trái ngược với ý tưởng.
Jacob Raihle

1
Đối số chế giễu có vẻ như một con dốc trơn trượt. Tôi không sử dụng các khung mô phỏng, nhưng tôi hiểu lợi ích của DI.
Robert Harvey

2
Nó chỉ là một cái đuôi cảnh báo. Khi mọi người nghĩ cho phép thử nghiệm tự động là lý do duy nhất để thực hiện DI, họ không chỉ bỏ lỡ việc hiểu các lợi ích để thiết kế linh hoạt mà họ còn trở nên dễ bị tổn thương khi bán hàng cho các sản phẩm hứa hẹn lợi ích mà không cần làm việc. DI là công việc. Không có bữa trưa miễn phí.
candied_orange

3
Nói cách khác, tách rời là lý do thực sự cho DI. Kiểm tra chỉ đơn giản là một trong những lĩnh vực phổ biến nhất mà lợi ích của việc tách rời là rõ ràng.
StackOverthrow

2
Này, không đùa đâu. Nếu DI không giúp bạn duy trì mã của mình thì đừng sử dụng nó.
candied_orange

2

Quan điểm trực thăng của DI chỉ đơn giản là khả năng trao đổi một triển khai cho một giao diện . Trong khi điều này tất nhiên là một lợi ích để thử nghiệm, có những lợi ích tiềm năng khác:

Phiên bản triển khai của một đối tượng

Nếu các phương thức của bạn chấp nhận các tham số giao diện ở các lớp ở giữa, bạn có thể tự do chuyển bất kỳ triển khai nào bạn muốn ở lớp trên cùng, điều này làm giảm số lượng mã cần viết để trao đổi các triển khai. Phải thừa nhận rằng dù sao đây cũng là một lợi ích của giao diện, nhưng nếu mã được viết bằng DI trong tâm trí, bạn sẽ đạt được lợi ích này ngay lập tức.

Giảm số lượng các đối tượng cần phải đi qua các lớp

Mặc dù điều này chủ yếu áp dụng cho các khung DI, nhưng nếu đối tượng A yêu cầu một thể hiện của đối tượng B , thì có thể truy vấn kernel (hoặc bất cứ thứ gì) để tạo đối tượng B một cách nhanh chóng thay vì chuyển qua các lớp. Điều này làm giảm số lượng mã cần được viết và kiểm tra. Nó cũng giữ cho các lớp không quan tâm đến đối tượng B sạch sẽ.


2

Không cần thiết phải sử dụng giao diện để sử dụng DI. Mục đích chính của DI là tách biệt việc xây dựng và sử dụng các đối tượng.

Sử dụng singletons là đúng đắn trong hầu hết các trường hợp. Một trong những lý do là rất khó để có được một cái nhìn tổng quan về những phụ thuộc mà một lớp có.

Trong ví dụ của bạn, ItemSiteControll có thể chỉ cần lấy ItemSiteService làm đối số của hàm tạo. Điều này cho phép bạn tránh mọi chi phí tạo đối tượng, nhưng tránh tính không linh hoạt của một đơn. Điều tương tự cũng đúng với ItemSiteService, nếu nó cần ItemService và SiteService, hãy tiêm chúng vào hàm tạo.

Lợi ích là lớn nhất khi tất cả các đối tượng sử dụng tiêm phụ thuộc. Điều này cho phép bạn tập trung xây dựng vào một mô-đun chuyên dụng hoặc ủy thác nó cho một container DI.

Một hệ thống phân cấp phụ thuộc có thể trông giống như thế này:

public interface IStorage
{
}

public class DbStorage : IStorage
{
    public DbStorage(string connectionString){}
}

public class FileStorage : IStorage
{
    public FileStorage(FileInfo file){}
}

public class MemoryStorage : IStorage
{
}

public class CachingStorage : IStorage
{
    public CachingStorage(IStorage storage) { }
}

public class MyService
{
    public MyService(IStorage storage){}
}

public class Controller
{
    public Controller(MyService service){}
}

Lưu ý rằng chỉ có một lớp không có bất kỳ tham số hàm tạo nào và chỉ có một giao diện. Khi định cấu hình bộ chứa DI, bạn có thể quyết định sử dụng bộ lưu trữ nào hoặc có nên sử dụng bộ đệm không, v.v. Việc kiểm tra sẽ dễ dàng hơn vì bạn có thể quyết định sử dụng cơ sở dữ liệu nào hoặc sử dụng một số loại lưu trữ khác. Bạn cũng có thể định cấu hình bộ chứa DI để coi các đối tượng là singletons nếu cần, trong bối cảnh của đối tượng chứa.


"Trong ví dụ của bạn, ItemSiteControll có thể chỉ cần lấy ItemSiteService làm đối số của hàm tạo." Tất nhiên nhưng một bộ điều khiển có thể sử dụng từ 1 đến 10 dịch vụ. Sau đó, đôi khi tôi có thể thêm một dịch vụ mới buộc tôi phải thay đổi chữ ký của nhà xây dựng. Làm thế nào bạn có thể nói điều này là tốt hơn để bảo trì? Và không, điều này có thể sẽ khiến tôi mất nhiều thời gian để tạo đối tượng vì tôi không còn cơ chế đơn lẻ nữa. Vì vậy, tôi sẽ phải chuyển nó cho từng bộ điều khiển bằng cách sử dụng nó. Làm thế nào tôi có thể đảm bảo bất kỳ nhà phát triển nào không phải lúc nào cũng tạo lại nó. Hệ thống nào có thể đảm bảo tôi chỉ tạo ra nó một lần?
Bastien Vandamme

Tôi nghĩ, giống như hầu hết mọi người, bạn đang trộn mẫu DI với Tính năng Container. Ví dụ của bạn là Cách sử dụng của tôi 1. Tôi không thách thức điều này. Khi bạn có nhiều triển khai giao diện, bạn phải sử dụng cái mà bạn gọi là DI. Tôi gọi đó là OOP. Bất cứ điều gì. Tôi thách thức Cách sử dụng 2 nơi hầu hết mọi người dường như sử dụng nó để thử nghiệm và bảo trì. Tôi hiểu nhưng điều này không giải quyết được yêu cầu cá thể của tôi. Hy vọng rằng Container dường như cung cấp một giải pháp. Container không chỉ DI. Tôi nghĩ rằng tôi cần DI với một tính năng cá thể duy nhất và tôi có thể tìm thấy điều này trong các thùng chứa (thư viện của bên thứ 3).
Bastien Vandamme

@ bastien-vandamme Nếu bạn cần thêm dịch vụ mới thì bạn nên thêm chúng vào hàm tạo, đây không phải là vấn đề. Nếu bạn cần tiêm nhiều dịch vụ, điều này có thể chỉ ra một vấn đề với kiến ​​trúc, chứ không phải ý tưởng để tiêm phụ thuộc. Bạn đảm bảo rằng ví dụ tương tự được các nhà phát triển khác sử dụng lại bằng cách thiết kế tốt và đào tạo các đồng nghiệp của bạn. Nhưng nói chung nên có thể có nhiều phiên bản của một lớp. Ví dụ, tôi có thể có nhiều phiên bản cơ sở dữ liệu và có một dịch vụ cho từng trường hợp.
JonasH

Euh không. Tôi có một cơ sở dữ liệu với 50 bảng, tôi cần 50 kho lưu trữ, 50 dịch vụ. Tôi có thể làm việc với các kho lưu trữ chung nhưng cơ sở dữ liệu của tôi không được chuẩn hóa và sạch sẽ vì vậy bất cứ kho lưu trữ hoặc dịch vụ nào của tôi tại một số điểm phải có mã cụ thể vì lịch sử. Vì vậy, tôi không thể làm tất cả chung chung. Mỗi dịch vụ có quy tắc kinh doanh cụ thể tôi cần duy trì riêng.
Bastien Vandamme

2

Bạn cô lập các hệ thống bên ngoài.

Cách sử dụng 1: Tất nhiên sử dụng DI trong trường hợp bạn có nhiều triển khai giao diện của bạn có vẻ logic. Bạn có một kho lưu trữ cho Máy chủ SQL của mình, sau đó một kho lưu trữ khác cho cơ sở dữ liệu Oracle của bạn. Cả hai đều có chung giao diện và bạn "tiêm" (đây là thuật ngữ được sử dụng) giao diện bạn muốn trong thời gian chạy. Điều này thậm chí không phải là DI, đây là lập trình OO cơ bản ở đây.


Có, sử dụng DI ở đây. Nếu nó đang đi đến mạng, cơ sở dữ liệu, hệ thống tập tin, một quá trình khác, đầu vào của người dùng, v.v. Bạn muốn cô lập nó.

Sử dụng DI sẽ dễ dàng kiểm tra vì bạn sẽ dễ dàng chế giễu các hệ thống bên ngoài này. Không, tôi không nói rằng đó là bước đầu tiên để thử nghiệm đơn vị. Cũng không phải là bạn không thể làm kiểm tra mà không làm điều này.

Hơn nữa, ngay cả khi bạn chỉ có một cơ sở dữ liệu, sử dụng DI sẽ giúp bạn vào ngày bạn muốn di chuyển. Vì vậy, vâng, DI.

Cách sử dụng 2: Khi bạn có một lớp nghiệp vụ với nhiều dịch vụ với tất cả các phương thức cụ thể, có vẻ như cách tốt nhất là tạo giao diện cho mỗi dịch vụ và cũng thực hiện việc triển khai ngay cả khi dịch vụ này là duy nhất. Bởi vì điều này là tốt hơn để bảo trì. Đây là cách sử dụng thứ hai tôi không hiểu.

Chắc chắn, DI có thể giúp bạn. Tôi sẽ tranh luận về các container.

Có lẽ, một điều đáng chú ý, là tiêm phụ thuộc với các loại bê tông vẫn là tiêm phụ thuộc. Điều quan trọng là bạn có thể tạo các trường hợp tùy chỉnh. Nó không phải là tiêm giao diện (mặc dù giao diện tiêm linh hoạt hơn, điều đó không có nghĩa là bạn nên sử dụng nó ở mọi nơi).

Ý tưởng tạo ra một giao diện rõ ràng cho mỗi và mọi lớp phải chết. Trên thực tế, nếu bạn chỉ có một triển khai giao diện ... YAGNI . Thêm một giao diện tương đối rẻ, có thể được thực hiện khi bạn cần. Trên thực tế, tôi sẽ đề nghị đợi cho đến khi bạn có hai hoặc ba ứng cử viên triển khai để bạn có ý tưởng tốt hơn về những điều phổ biến giữa họ.

Tuy nhiên, mặt trái của điều đó là bạn có thể tạo các giao diện phù hợp hơn với những gì mã khách hàng cần. Nếu mã máy khách chỉ cần một vài thành viên của một lớp, bạn có thể có một giao diện chỉ dành cho điều đó. Điều đó sẽ dẫn đến sự phân biệt giao diện tốt hơn .


Hộp đựng?

Bạn biết bạn không cần chúng.

Hãy để chúng tôi đưa nó xuống để đánh đổi. Có những trường hợp họ không xứng đáng. Bạn sẽ có lớp của bạn lấy các phụ thuộc mà nó cần trên hàm tạo. Và điều đó có thể là đủ tốt.

Tôi thực sự không phải là người thích chú thích các thuộc tính cho "setter tiêm", ít hơn các bên thứ ba, tôi nhận thấy có thể cần thiết cho việc triển khai ngoài kiểm soát của bạn, tuy nhiên, nếu bạn quyết định thay đổi thư viện thì phải thay đổi.

Cuối cùng, bạn sẽ bắt đầu xây dựng các thói quen để tạo ra các đối tượng này, bởi vì để tạo ra nó, trước tiên bạn cần tạo những đối tượng này và đối với những người bạn cần thêm ...

Chà, khi điều đó xảy ra, bạn muốn đặt tất cả logic đó vào một nơi duy nhất và tái sử dụng nó. Bạn muốn có một nguồn sự thật duy nhất về cách bạn tạo đối tượng của mình. Và bạn có được nó bằng cách không lặp lại chính mình . Điều đó sẽ đơn giản hóa mã của bạn. Có ý nghĩa, phải không?

Vâng, bạn đặt logic đó ở đâu? Bản năng đầu tiên sẽ là có Bộ định vị dịch vụ . Một triển khai đơn giản là một singleton với một từ điển chỉ đọc của các nhà máy . Việc triển khai phức tạp hơn có thể sử dụng sự phản chiếu để tạo ra các nhà máy khi bạn chưa cung cấp.

Tuy nhiên, sử dụng một bộ định vị dịch vụ đơn hoặc dịch vụ tĩnh sẽ có nghĩa là bạn sẽ làm một cái gì đó giống như var x = IoC.Resolve<?>mọi nơi bạn cần để tạo một cá thể. Đó là thêm một khớp nối mạnh mẽ để định vị dịch vụ / container / kim phun dịch vụ của bạn. Điều đó thực sự có thể làm cho thử nghiệm đơn vị khó hơn.

Bạn muốn một kim phun mà bạn khởi tạo và chỉ giữ nó để sử dụng trên bộ điều khiển. Bạn không muốn nó đi sâu vào mã. Điều đó thực sự có thể làm cho thử nghiệm khó hơn. Nếu một phần nào đó trong mã của bạn cần nó để khởi tạo một cái gì đó, thì nó sẽ mong đợi một thể hiện (hoặc hầu hết là một nhà máy) trên hàm tạo của nó.

Và nếu bạn có nhiều tham số trên hàm tạo ... hãy xem bạn có các tham số di chuyển cùng nhau không. Có thể bạn có thể hợp nhất các tham số thành các loại mô tả (loại giá trị lý tưởng).


Cảm ơn bạn đã giải thích rõ ràng này. Vì vậy, theo bạn, tôi nên tạo một lớp người tiêm có chứa danh sách tất cả các dịch vụ của tôi. Tôi không tiêm dịch vụ theo dịch vụ. Tôi tiêm lớp tiêm của tôi. Lớp này cũng quản lý trường hợp duy nhất của mỗi dịch vụ của tôi?
Bastien Vandamme

@BastienVandamme bạn có thể ghép đôi bộ điều khiển với bộ tiêm. Vì vậy, nếu bạn có nghĩa là chuyển nó cho bộ điều khiển, thì tôi đồng ý. Và có, nó có thể xử lý việc có một phiên bản duy nhất của dịch vụ. Mặt khác, tôi sẽ lo lắng nếu bộ điều khiển đi qua kim phun xung quanh ... nó có thể vượt qua các nhà máy xung quanh nếu cần. Trong thực tế, ý tưởng đang tách các dịch vụ khỏi việc khởi tạo các phụ thuộc của chúng. Vì vậy, lý tưởng nhất, các dịch vụ không nhận thức được người tiêm.
Theraot

"Sử dụng DI sẽ dễ dàng kiểm tra vì bạn sẽ dễ dàng chế giễu các hệ thống bên ngoài này." Tôi đã thấy rằng việc dựa vào việc chế nhạo các hệ thống này trong thử nghiệm đơn vị mà không giảm thiểu mã đầu tiên có bất kỳ kết nối nào với chúng làm giảm nghiêm trọng tiện ích của việc kiểm tra đơn vị. Đầu tiên tối đa hóa số lượng mã có thể được kiểm tra thông qua kiểm tra đầu vào / đầu ra. Sau đó xem xét việc chế giễu là cần thiết.
jpmc26

@ jpmc26 đồng ý. Phụ lục: dù sao chúng ta cũng nên cách ly những hệ thống bên ngoài đó.
Theraot

@BastienVandamme Tôi đã suy nghĩ về điều này. Tôi nghĩ rằng nếu bạn không mất khả năng tạo để tạo ra các kim phun tùy chỉnh để vượt qua, thì làm như vậy sẽ ổn thôi.
Theraot
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.