Ioc / DI - Tại sao tôi phải tham chiếu đến tất cả các lớp / tổ hợp trong điểm vào của ứng dụng?


123

(Liên quan đến câu hỏi này, EF4: Tại sao tính năng tạo proxy phải được bật khi kích hoạt tính năng tải chậm? ).

Tôi mới làm quen với DI, vì vậy hãy chịu đựng tôi. Tôi hiểu rằng vùng chứa chịu trách nhiệm khởi tạo tất cả các loại đã đăng ký của tôi nhưng để làm như vậy, nó yêu cầu tham chiếu đến tất cả các tệp DLL trong giải pháp của tôi và các tham chiếu của chúng.

Nếu tôi không sử dụng vùng chứa DI, tôi sẽ không phải tham chiếu đến thư viện EntityFramework trong ứng dụng MVC3 của mình, chỉ lớp nghiệp vụ của tôi, sẽ tham chiếu đến lớp DAL / Repo của tôi.

Tôi biết rằng vào cuối ngày tất cả các tệp DLL đều được đưa vào thư mục bin nhưng vấn đề của tôi là phải tham chiếu nó một cách rõ ràng thông qua "thêm tham chiếu" trong VS để có thể xuất bản WAP với tất cả các tệp cần thiết.


1
Đoạn trích này từ cuốn sách Dependency Injection in .NET, ấn bản thứ hai là một phiên bản trau chuốt hơn về các câu trả lời của cả Mark và tôi. Nó mô tả chi tiết khái niệm về Gốc thành phần và tại sao để đường dẫn khởi động của ứng dụng phụ thuộc vào mọi mô-đun khác thực sự là một điều tốt.
Steven

Tôi đã đọc qua liên kết đoạn trích đó và chương 1, tôi sẽ mua cuốn sách vì tôi thực sự thích những phép loại suy và giải thích đơn giản cho vấn đề phức tạp của DI. Tôi nghĩ bạn nên đề xuất một câu trả lời mới, hãy trả lời rõ ràng "bạn không cần phải tham chiếu đến tất cả các lớp / tổ hợp trong lớp logic đầu vào trừ khi nó cũng là gốc bố cục của bạn", liên kết đến đoạn trích và đăng hình ảnh 3, từ đoạn trích.
diegohb

Câu trả lời:


194

Nếu tôi không sử dụng vùng chứa DI, tôi sẽ không phải tham chiếu thư viện EntityFramework trong ứng dụng MVC3 của mình, chỉ lớp nghiệp vụ của tôi sẽ tham chiếu đến lớp DAL / Repo của tôi.

Vâng, đó chính xác là tình huống DI làm việc rất khó để tránh :)

Với mã được kết hợp chặt chẽ, mỗi thư viện có thể chỉ có một vài tham chiếu, nhưng những thư viện này lại có các tham chiếu khác, tạo ra một biểu đồ phụ thuộc sâu, như sau:

Đồ thị sâu

Bởi vì đồ thị phụ thuộc là sâu, nó có nghĩa rằng hầu hết các thư viện kéo theo rất nhiều phụ thuộc khác - ví dụ như trong biểu đồ, Thư viện C kéo cùng Thư viện H, Thư viện E, Thư viện J, Thư viện M, Thư viện KThư viện N . Điều này làm cho việc sử dụng lại từng thư viện một cách độc lập với phần còn lại khó hơn - ví dụ như trong kiểm thử đơn vị .

Tuy nhiên, trong một ứng dụng được kết hợp lỏng lẻo, bằng cách di chuyển tất cả các tham chiếu đến Gốc thành phần , biểu đồ phụ thuộc bị làm phẳng nghiêm trọng :

Đồ thị nông

Như được minh họa bằng màu xanh lá cây, bây giờ có thể sử dụng lại Thư viện C mà không cần kéo theo bất kỳ phụ thuộc không mong muốn nào.

Tuy nhiên, tất cả những gì đã nói, với nhiều DI Containers, bạn không cần phải thêm các tham chiếu khó vào tất cả các thư viện bắt buộc. Thay vào đó, bạn có thể sử dụng liên kết trễ ở dạng quét lắp ráp dựa trên quy ước (ưu tiên) hoặc cấu hình XML.

Tuy nhiên, khi làm điều đó, bạn phải nhớ sao chép các tập hợp vào thư mục bin của ứng dụng, vì điều đó không còn xảy ra tự động nữa. Cá nhân tôi, tôi hiếm khi thấy nó xứng đáng với nỗ lực thêm đó.

Bạn có thể tìm thấy phiên bản chi tiết hơn của câu trả lời này trong đoạn trích này từ cuốn sách Sự phụ thuộc, Nguyên tắc, Thực hành, Mẫu của tôi .


3
Cảm ơn rất nhiều, điều này bây giờ rất hợp lý .. tôi cần biết liệu đây có phải là do thiết kế. Đối với việc thực thi việc sử dụng đúng các phụ thuộc, tôi đã triển khai một dự án riêng biệt với bộ khởi động DI của mình như Steven đã đề cập bên dưới, nơi tôi tham khảo phần còn lại của các thư viện. Dự án này được tham chiếu bởi ứng dụng điểm vào và ở cuối bản dựng đầy đủ, điều này khiến tất cả các hình nền cần thiết nằm trong thư mục bin. cảm ơn!
diegohb

2
@Mark Seemann Câu hỏi / câu trả lời này có dành riêng cho Microsoft không? Tôi muốn biết liệu ý tưởng di chuyển tất cả các phần phụ thuộc đến "điểm vào của ứng dụng" có phù hợp với dự án Java EE / Spring sử dụng Maven hay không… cảm ơn!
Grégoire C

5
Câu trả lời này áp dụng ngoài .NET. Bạn có thể muốn tham khảo Robert C. Martin nguyên tắc thiết kế trọn gói chương trong ví dụ phát triển Agile Software, nguyên tắc, Patterns, và thực tiễn
Đánh dấu SEEMANN

7
@AndyDangerGagne Gốc thành phần là một mẫu DI - đối lập với Định vị dịch vụ . Từ quan điểm của Gốc cấu tạo, không có loại nào là đa hình; Gốc thành phần coi tất cả các loại là loại bê tông, và do đó, Nguyên tắc thay thế Liskov không áp dụng cho nó.
Mark Seemann

4
Theo nguyên tắc chung, các giao diện phải được xác định bởi các máy khách sử dụng chúng ( APP, chương 11 ), vì vậy nếu Thư viện J cần một giao diện, thì giao diện đó nên được xác định trong Thư viện J. Đó là hệ quả của Nguyên tắc Đảo ngược Phụ thuộc.
Mark Seemann

65

Nếu tôi không sử dụng vùng chứa DI, tôi sẽ không phải tham chiếu thư viện EntityFramework trong ứng dụng MVC3 của mình

Ngay cả khi sử dụng DI container, bạn không phải để dự án MVC3 của mình tham chiếu EF, nhưng bạn (mặc nhiên) chọn làm điều này bằng cách triển khai Gốc cấu phần (đường dẫn khởi động nơi bạn soạn đồ thị đối tượng) bên trong dự án MVC3 của mình. Nếu bạn rất nghiêm khắc về việc bảo vệ ranh giới kiến ​​trúc của mình bằng cách sử dụng các cụm, bạn có thể chuyển logic bản trình bày của mình sang một dự án khác.

Khi bạn di chuyển tất cả logic liên quan đến MVC (bộ điều khiển, v.v.) từ dự án khởi động sang thư viện lớp, nó cho phép tổ hợp lớp bản trình bày này không bị ngắt kết nối với phần còn lại của ứng dụng. Bản thân dự án ứng dụng web của bạn sẽ trở thành một lớp vỏ rất mỏng với logic khởi động bắt buộc. Dự án ứng dụng web sẽ là Gốc thành phần tham chiếu đến tất cả các hợp ngữ khác.

Trích xuất logic bản trình bày vào thư viện lớp có thể làm phức tạp mọi thứ khi làm việc với MVC. Sẽ khó hơn để kết nối mọi thứ, vì bộ điều khiển không nằm trong dự án khởi động (trong khi các chế độ xem, hình ảnh, tệp css, có thể phải ở trong dự án khởi động). Điều này có thể làm được nhưng sẽ mất nhiều thời gian hơn để thiết lập.

Vì những nhược điểm, tôi thường khuyên bạn chỉ nên giữ Gốc thành phần trong dự án web. Nhiều nhà phát triển không muốn lắp ráp MVC của họ phụ thuộc vào lắp ráp DAL, nhưng đó không thực sự là vấn đề. Đừng quên rằng các tập hợp là một tạo tác triển khai ; bạn chia mã thành nhiều tập hợp để cho phép mã được triển khai riêng biệt. Mặt khác, một lớp kiến ​​trúc là một tác phẩm hợp lý . Rất có thể (và phổ biến) có nhiều lớp trong cùng một tổ hợp.

Trong trường hợp này, chúng ta sẽ có Gốc Thành phần (lớp) và Lớp Trình bày trong cùng một dự án ứng dụng web (do đó trong cùng một hội đồng). Và mặc dù hợp ngữ đó tham chiếu đến hợp ngữ chứa DAL, Lớp trình bày vẫn không tham chiếu Lớp truy cập dữ liệu . Đây là một sự khác biệt lớn.

Tất nhiên, khi chúng tôi làm điều này, chúng tôi sẽ mất khả năng trình biên dịch kiểm tra quy tắc kiến ​​trúc này tại thời điểm biên dịch, nhưng đây không phải là vấn đề. Hầu hết các quy tắc kiến ​​trúc thực sự không thể được trình biên dịch kiểm tra và luôn có một cái gì đó giống như lẽ thường. Và nếu không có ý thức chung trong nhóm của bạn, bạn luôn có thể sử dụng đánh giá mã (mà mọi đội IMO luôn nên làm btw). Bạn cũng có thể sử dụng một công cụ như NDepend (thương mại), giúp bạn xác minh các quy tắc kiến ​​trúc của mình. Khi bạn tích hợp NDepend với quy trình xây dựng của mình, nó có thể cảnh báo bạn khi ai đó kiểm tra mã vi phạm quy tắc kiến ​​trúc đó.

Bạn có thể đọc một cuộc thảo luận chi tiết hơn về cách hoạt động của Gốc Thành phần trong chương 4 của cuốn sách Tiêm phụ thuộc, Nguyên tắc, Thực hành, Mẫu của tôi .


Một dự án riêng biệt cho bootstrapping là giải pháp của tôi vì chúng tôi không có ndepend và tôi chưa bao giờ sử dụng nó trước đây. Tuy nhiên, tôi sẽ xem xét nó vì nó có vẻ là một cách tốt hơn để hoàn thành những gì tôi đang cố gắng làm khi sẽ chỉ có một ứng dụng cuối.
diegohb

1
Đoạn cuối cùng là một đoạn tuyệt vời và bắt đầu giúp tôi thay đổi suy nghĩ về mức độ nghiêm ngặt của tôi trong việc giữ các lớp trong các tập hợp riêng biệt. Có hai hoặc nhiều lớp logic trong một lắp ráp thực sự tốt nếu bạn sử dụng các quy trình khác xung quanh việc viết mã (chẳng hạn như đánh giá mã) để đảm bảo không có tham chiếu đến các lớp DAL trong mã giao diện người dùng của bạn và ngược lại.
BenM

6

Nếu tôi không sử dụng vùng chứa DI, tôi sẽ không phải tham chiếu thư viện EntityFramework trong ứng dụng MVC3 của mình, chỉ lớp nghiệp vụ của tôi sẽ tham chiếu đến lớp DAL / Repo của tôi.

Bạn có thể tạo một dự án riêng biệt được gọi là "DependencyResolver". Trong dự án này, bạn phải tham khảo tất cả các thư viện của mình.

Bây giờ Lớp giao diện người dùng không cần NHibernate / EF hoặc bất kỳ thư viện nào khác không liên quan đến giao diện người dùng ngoại trừ Castle Windsor để được tham chiếu.

Nếu bạn muốn ẩn Castle Windsor và DependencyResolver khỏi lớp UI của mình, bạn có thể viết một HttpModule gọi nội dung đăng ký IoC.

Tôi chỉ có một ví dụ cho StructureMap:

public class DependencyRegistrarModule : IHttpModule
{
    private static bool _dependenciesRegistered;
    private static readonly object Lock = new object();

    public void Init(HttpApplication context)
    {
        context.BeginRequest += (sender, args) => EnsureDependenciesRegistered();
    }

    public void Dispose() { }

    private static void EnsureDependenciesRegistered()
    {
        if (!_dependenciesRegistered)
        {
            lock (Lock)
            {
                if (!_dependenciesRegistered)
                {
                    ObjectFactory.ResetDefaults();

                    // Register all you dependencies here
                    ObjectFactory.Initialize(x => x.AddRegistry(new DependencyRegistry()));

                    new InitiailizeDefaultFactories().Configure();
                    _dependenciesRegistered = true;
                }
            }
        }
    }
}

public class InitiailizeDefaultFactories
{
    public void Configure()
    {
        StructureMapControllerFactory.GetController = type => ObjectFactory.GetInstance(type);
          ...
    }
 }

DefaultControllerFactory không sử dụng vùng chứa IoC trực tiếp, nhưng nó ủy quyền cho các phương thức vùng chứa IoC.

public class StructureMapControllerFactory : DefaultControllerFactory
{
    public static Func<Type, object> GetController = type =>
    {
        throw new  InvalidOperationException("The dependency callback for the StructureMapControllerFactory is not configured!");
    };

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            return base.GetControllerInstance(requestContext, controllerType);
        }
        return GetController(controllerType) as Controller;
    }
}

Đại GetControllerbiểu được đặt trong Cơ quan đăng ký Sơ đồ cấu trúc (trong Windsor, nó phải là Trình cài đặt).


1
tôi thích điều này thậm chí còn tốt hơn những gì tôi đã làm, các mô-đun rất tuyệt. vậy thì tôi sẽ thực hiện cuộc gọi đến Container.Dispose () ở đâu? Sự kiện ApplicationEnd hoặc EndRequest trong mô-đun ...?
diegohb

1
@Steven Vì Global.asax nằm trong Lớp giao diện người dùng MVC của bạn. HttpModule sẽ nằm trong Dự án DependencyResolver.
Rookian

1
Lợi ích nhỏ là không ai có thể sử dụng vùng chứa IoC trong giao diện người dùng. Tức là không ai có thể sử dụng IoC Container làm bộ định vị dịch vụ trong giao diện người dùng.
Rookian

1
Ngoài ra, nó không cho phép các nhà phát triển vô tình sử dụng mã DAL trong lớp giao diện người dùng vì không có tham chiếu cố định đến hợp ngữ trong giao diện người dùng.
diegohb

1
Tôi đã tìm ra cách làm điều tương tự bằng cách sử dụng API đăng ký chung của Bootstrapper. Dự án giao diện người dùng của tôi tham chiếu đến Bootstrapper, dự án giải quyết sự phụ thuộc nơi tôi sắp xếp các đăng ký của mình và các dự án trong Core của tôi (cho các giao diện) chứ không có gì khác, thậm chí không phải DI Framework (SimpleInjector) của tôi. Tôi đang sử dụng nuget OutputTo để sao chép dlls vào thư mục bin.
diegohb

0
  • Có một sự phụ thuộc: nếu một đối tượng khởi tạo một đối tượng khác.
  • Không có sự phụ thuộc: nếu một đối tượng mong đợi một sự trừu tượng (chèn contructor, chèn phương thức ...)
  • Assembly References (tham chiếu dll, webservices ..) độc lập với khái niệm phụ thuộc, vì để giải quyết một sự trừu tượng và có thể biên dịch mã, lớp phải tham chiếu đến nó.
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.