Làm thế nào để tránh sự điên rồ của người xây dựng phụ thuộc?


300

Tôi thấy rằng các nhà xây dựng của tôi đang bắt đầu trông như thế này:

public MyClass(Container con, SomeClass1 obj1, SomeClass2, obj2.... )

với danh sách tham số ngày càng tăng. Vì "Container" là container tiêm phụ thuộc của tôi, tại sao tôi không thể làm điều này:

public MyClass(Container con)

cho mọi lớp học? Nhược điểm là gì? Nếu tôi làm điều này, có cảm giác như tôi đang sử dụng một tĩnh được tôn vinh. Hãy chia sẻ suy nghĩ của bạn về sự điên rồ của IoC và Dependency Injection.


64
tại sao bạn đi qua container Tôi nghĩ bạn có thể hiểu nhầm IOC
Paul Creasey

33
Nếu các nhà xây dựng của bạn đang yêu cầu nhiều hoặc nhiều tham số, bạn có thể đang làm quá nhiều trong các lớp đó.
Austin Salonen

38
Đó không phải là cách bạn thực hiện tiêm xây dựng. Các đối tượng không biết gì về container IoC, họ cũng không nên.
duffymo

Bạn chỉ có thể tạo một hàm tạo trống, trong đó bạn gọi DI trực tiếp yêu cầu những thứ bạn cần. Điều đó sẽ loại bỏ các nhà xây dựng madnes nhưng bạn cần chắc chắn rằng bạn đang sử dụng Giao diện DI .. trong trường hợp bạn thay đổi hệ thống DI của mình trong quá trình phát triển. Thành thật mà nói .. không ai sẽ quay lại làm theo cách này, mặc dù đây là những gì DI làm để đưa vào công cụ xây dựng của bạn. doh
Piotr Kula

Câu trả lời:


409

Bạn đúng rằng nếu bạn sử dụng bộ chứa làm Công cụ định vị dịch vụ, thì ít nhiều nó sẽ là một nhà máy tĩnh được tôn vinh. Vì nhiều lý do tôi coi đây là một mô hình chống .

Một trong những lợi ích tuyệt vời của Con Contortor tiêm là nó làm cho việc vi phạm Nguyên tắc Trách nhiệm Đơn lẻ trở nên rõ ràng.

Khi điều đó xảy ra, đã đến lúc cấu trúc lại Dịch vụ Mặt tiền . Nói tóm lại, tạo ra một, mới hơn thô-hạt giao diện mà da sự tương tác giữa một số hoặc tất cả các phụ thuộc hạt mịn bạn đang yêu cầu.


8
+1 để định lượng nỗ lực tái cấu trúc thành một khái niệm duy nhất; tuyệt vời :)
Ryan Emerle

46
cho thật? bạn vừa tạo ra một sự gián tiếp để di chuyển các tham số đó sang một lớp khác, nhưng chúng vẫn ở đó! chỉ phức tạp hơn để đối phó với họ.
chối cãi vào

23
@irreputable: Trong trường hợp suy biến khi chúng tôi chuyển tất cả các phụ thuộc vào Dịch vụ Tổng hợp, tôi đồng ý rằng đó chỉ là một mức độ gián tiếp khác không mang lại lợi ích, vì vậy lựa chọn từ ngữ của tôi hơi bị tắt. Tuy nhiên, vấn đề là chúng tôi chỉ chuyển một số phụ thuộc chi tiết nhỏ vào Dịch vụ Tổng hợp. Điều này giới hạn số lượng hoán vị phụ thuộc cả trong Dịch vụ Tổng hợp mới và cho các phụ thuộc còn lại. Điều này làm cho cả hai đơn giản hơn để đối phó với.
Mark Seemann

92
Nhận xét tốt nhất: "Một trong những lợi ích tuyệt vời của Con Contortor tiêm là nó làm cho việc vi phạm Nguyên tắc Trách nhiệm Đơn lẻ trở nên rõ ràng."
Igor Popov

2
@DonBox Trong trường hợp đó, bạn có thể viết các triển khai đối tượng null để dừng đệ quy. Không phải những gì bạn cần, nhưng vấn đề là Con Contortor tiêm không ngăn chặn chu kỳ - nó chỉ làm rõ rằng chúng ở đó.
Mark Seemann

66

Tôi không nghĩ rằng các nhà xây dựng lớp của bạn nên có một tài liệu tham khảo về thời gian chứa IOC của bạn. Điều này thể hiện sự phụ thuộc không cần thiết giữa lớp của bạn và vùng chứa (loại phụ thuộc mà IOC đang cố tránh!).


+1 Tùy thuộc vào bộ chứa IoC khiến bạn khó thay đổi bộ chứa đó sau này khi thay đổi bó mã trong tất cả các lớp khác
Tseng

1
Làm thế nào bạn sẽ thực hiện IOC mà không có các tham số giao diện trên các hàm tạo? Tôi đang đọc bài viết của bạn sai?
J Hunt

@J Hunt Tôi không hiểu bình luận của bạn. Đối với tôi, các tham số giao diện có nghĩa là các tham số là giao diện cho các phụ thuộc, nghĩa là, nếu thùng chứa phụ thuộc tiêm của bạn khởi tạo MyClass myClass = new MyClass(IDependency1 interface1, IDependency2 interface2)(tham số giao diện). Điều này không liên quan đến bài đăng của phái sinh @, mà tôi giải thích là nói rằng một thùng chứa thuốc tiêm phụ thuộc không nên tự tiêm vào các đối tượng của nó, tức làMyClass myClass = new MyClass(this)
John Doe

25

Khó khăn trong việc vượt qua trong các tham số không phải là vấn đề. Vấn đề là lớp học của bạn đang làm quá nhiều, và nên được chia nhỏ hơn.

Dependency Injection có thể đóng vai trò là một cảnh báo sớm cho các lớp học trở nên quá lớn, đặc biệt là do nỗi đau ngày càng tăng trong tất cả các phụ thuộc.


44
Sửa lỗi cho tôi nếu tôi sai, nhưng đến một lúc nào đó bạn phải 'dán mọi thứ lại với nhau', và do đó bạn phải nhận được nhiều hơn một vài phụ thuộc cho điều đó. Ví dụ: trong lớp Chế độ xem, khi xây dựng mẫu và dữ liệu cho chúng, bạn phải lấy tất cả dữ liệu từ nhiều phụ thuộc khác nhau (ví dụ: 'services') và sau đó đặt tất cả dữ liệu này vào mẫu và vào màn hình. Nếu trang web của tôi có 10 'khối' thông tin khác nhau, vì vậy tôi cần 10 lớp khác nhau để cung cấp cho tôi dữ liệu đó. Vì vậy, tôi cần 10 phụ thuộc vào lớp Xem / Mẫu của tôi?
Andrew

4

Tôi đã gặp một câu hỏi tương tự về Tiêm phụ thuộc dựa trên hàm tạo và mức độ phức tạp của nó trong việc vượt qua tất cả các phụ thuộc.

Một trong những cách tiếp cận, tôi đã sử dụng trong quá khứ là sử dụng mẫu mặt tiền ứng dụng bằng cách sử dụng một lớp dịch vụ. Điều này sẽ có một API thô. Nếu dịch vụ này phụ thuộc vào kho lưu trữ, Nó sẽ sử dụng phương thức tiêm setter của các thuộc tính riêng. Điều này đòi hỏi phải tạo ra một nhà máy trừu tượng và di chuyển logic của việc tạo các kho lưu trữ vào một nhà máy.

Mã chi tiết với lời giải thích có thể được tìm thấy ở đây

Thực tiễn tốt nhất cho IoC trong lớp dịch vụ phức tạp


3

Tôi đã đọc toàn bộ chủ đề này, hai lần và tôi nghĩ mọi người đang phản hồi bằng những gì họ biết chứ không phải bởi những gì được hỏi.

Câu hỏi ban đầu của JP có vẻ như anh ta đang xây dựng các đối tượng bằng cách gửi một bộ giải quyết, và sau đó là một nhóm các lớp, nhưng chúng tôi cho rằng các lớp / đối tượng đó là chính các dịch vụ, đã sẵn sàng để tiêm. Nếu họ không thì sao?

JP, nếu bạn đang tìm cách tận dụng DI mong muốn vinh quang pha trộn dữ liệu theo ngữ cảnh, thì không có mẫu nào trong số này (hoặc được cho là "chống mẫu") giải quyết cụ thể điều đó. Nó thực sự nắm bắt được việc sử dụng một gói sẽ hỗ trợ bạn trong một nỗ lực như vậy.

Container.GetSevice<MyClass>(someObject1, someObject2)

... định dạng này hiếm khi được hỗ trợ. Tôi tin rằng khó khăn trong việc lập trình hỗ trợ như vậy, được thêm vào hiệu năng khốn khổ sẽ liên quan đến việc triển khai, khiến nó không hấp dẫn đối với các nhà phát triển mã nguồn mở.

Nhưng nó nên được thực hiện, bởi vì tôi có thể tạo và đăng ký một nhà máy cho MyClass'es và nhà máy đó sẽ có thể nhận dữ liệu / đầu vào không bị đẩy vào làm "dịch vụ" chỉ vì mục đích thông qua dữ liệu. Nếu "chống mẫu" là về hậu quả tiêu cực, thì việc buộc các loại dịch vụ nhân tạo để truyền dữ liệu / mô hình chắc chắn là tiêu cực (ngang bằng với cảm giác của bạn về việc gói các lớp của bạn vào một thùng chứa.

Có những khung có thể giúp, mặc dù, ngay cả khi chúng trông hơi xấu xí. Ví dụ: Ninject:

Tạo một cá thể bằng Ninject với các tham số bổ sung trong hàm tạo

Đó là đối với .NET, rất phổ biến và vẫn chưa được rõ ràng như vậy, nhưng tôi chắc chắn rằng có bất cứ thứ gì trong ngôn ngữ bạn chọn sử dụng.


3

Tiêm container là một lối tắt mà cuối cùng bạn sẽ hối tiếc.

Quá tiêm không phải là vấn đề, nó thường là một triệu chứng của các lỗ hổng cấu trúc khác, đáng chú ý nhất là sự tách biệt các mối quan tâm. Đây không phải là một vấn đề nhưng có thể có nhiều nguồn và điều khiến cho việc khắc phục này trở nên khó khăn là bạn sẽ phải đối phó với tất cả chúng, đôi khi cùng một lúc (nghĩ về cách gỡ rối spaghetti).

Dưới đây là danh sách không đầy đủ những điều cần chú ý

Thiết kế tên miền kém (Tập hợp gốc của v.v.)

Phân tách mối quan tâm kém (Thành phần dịch vụ, Lệnh, truy vấn) Xem CQRS và Tìm nguồn cung ứng sự kiện.

HOẶC Mappers (hãy cẩn thận, những điều này có thể khiến bạn gặp rắc rối)

Xem Mô hình và các DTO khác (Không bao giờ sử dụng lại một mô hình và cố gắng giữ chúng ở mức tối thiểu !!!!)


2

Vấn đề :

1) Trình xây dựng với danh sách tham số ngày càng tăng.

2) Nếu lớp được kế thừa (Ví dụ RepositoryBase:) thì việc thay đổi chữ ký của hàm tạo gây ra thay đổi trong các lớp dẫn xuất.

Giải pháp 1

Truyền IoC Containercho nhà xây dựng

Tại sao

  • Không còn danh sách tham số ngày càng tăng
  • Chữ ký của người xây dựng trở nên đơn giản

Tại sao không

  • Làm cho lớp của bạn được liên kết chặt chẽ với container IoC. (Điều đó gây ra vấn đề khi 1. bạn muốn sử dụng lớp đó trong các dự án khác nơi bạn sử dụng bộ chứa IoC khác nhau. 2. bạn quyết định thay đổi bộ chứa IoC)
  • Làm cho bạn lớp mô tả ít hơn. (Bạn không thể thực sự nhìn vào trình xây dựng lớp và nói những gì nó cần cho chức năng.)
  • Lớp có thể truy cập tiềm năng tất cả các dịch vụ.

Giải pháp 2

Tạo một lớp mà nhóm tất cả các dịch vụ và chuyển nó đến hàm tạo

 public abstract class EFRepositoryBase 
 {
    public class Dependency
    {
        public DbContext DbContext { get; }
        public IAuditFactory AuditFactory { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
        }
    }

    protected readonly DbContext DbContext;        
    protected readonly IJobariaAuditFactory auditFactory;

    protected EFRepositoryBase(Dependency dependency)
    {
        DbContext = dependency.DbContext;
        auditFactory= dependency.JobariaAuditFactory;
    }
  }

Lớp có nguồn gốc

  public class ApplicationEfRepository : EFRepositoryBase      
  {
     public new class Dependency : EFRepositoryBase.Dependency
     {
         public IConcreteDependency ConcreteDependency { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory,
            IConcreteDependency concreteDependency)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
            ConcreteDependency = concreteDependency;
        }
     }

      IConcreteDependency _concreteDependency;

      public ApplicationEfRepository(
          Dependency dependency)
          : base(dependency)
      { 
        _concreteDependency = dependency.ConcreteDependency;
      }
   }

Tại sao

  • Thêm phụ thuộc mới vào lớp không ảnh hưởng đến các lớp dẫn xuất
  • Lớp học là bất khả tri của IoC Container
  • Lớp là mô tả (trong khía cạnh phụ thuộc của nó). Theo quy ước, nếu bạn muốn biết lớp nào Aphụ thuộc vào, thông tin đó được tích lũy trongA.Dependency
  • Chữ ký xây dựng trở nên đơn giản

Tại sao không

  • cần tạo thêm lớp
  • đăng ký dịch vụ trở nên phức tạp (Bạn cần đăng ký X.Dependencyriêng)
  • Khái niệm giống như đi qua IoC Container
  • ..

Giải pháp 2 chỉ là một nguyên bản, nếu có lập luận chắc chắn chống lại nó, thì nhận xét mô tả sẽ được đánh giá cao


1

Đây là cách tiếp cận tôi sử dụng

public class Hero
{

    [Inject]
    private IInventory Inventory { get; set; }

    [Inject]
    private IArmour Armour { get; set; }

    [Inject]
    protected IWeapon Weapon { get; set; }

    [Inject]
    private IAction Jump { get; set; }

    [Inject]
    private IInstanceProvider InstanceProvider { get; set; }


}

Đây là một cách tiếp cận thô sơ về cách thực hiện tiêm và chạy hàm tạo sau khi tiêm các giá trị. Đây là chương trình đầy đủ chức năng.

public class InjectAttribute : Attribute
{

}


public class TestClass
{
    [Inject]
    private SomeDependency sd { get; set; }

    public TestClass()
    {
        Console.WriteLine("ctor");
        Console.WriteLine(sd);
    }
}

public class SomeDependency
{

}


class Program
{
    static void Main(string[] args)
    {
        object tc = FormatterServices.GetUninitializedObject(typeof(TestClass));

        // Get all properties with inject tag
        List<PropertyInfo> pi = typeof(TestClass)
            .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
            .Where(info => info.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0).ToList();

        // We now happen to know there's only one dependency so we take a shortcut just for the sake of this example and just set value to it without inspecting it
        pi[0].SetValue(tc, new SomeDependency(), null);


        // Find the right constructor and Invoke it. 
        ConstructorInfo ci = typeof(TestClass).GetConstructors()[0];
        ci.Invoke(tc, null);

    }
}

Tôi hiện đang làm việc trong một dự án sở thích hoạt động như thế này https://github.com/Jokine/ToolProject/tree/Core


-8

Bạn đang sử dụng khung tiêm phụ thuộc nào? Bạn đã thử sử dụng tiêm dựa trên setter thay thế?

Lợi ích của phép tiêm dựa trên hàm tạo là nó có vẻ tự nhiên đối với các lập trình viên Java không sử dụng các khung công tác DI. Bạn cần 5 điều để khởi tạo một lớp thì bạn có 5 đối số cho hàm tạo của mình. Nhược điểm là những gì bạn đã nhận thấy, nó trở nên khó sử dụng khi bạn có nhiều phụ thuộc.

Với Spring, bạn có thể chuyển các giá trị bắt buộc bằng setters thay vào đó và bạn có thể sử dụng các chú thích @required để thực thi rằng chúng được tiêm. Nhược điểm là bạn cần di chuyển mã khởi tạo từ hàm tạo sang phương thức khác và có lệnh gọi Spring sau khi tất cả các phụ thuộc được chèn bằng cách đánh dấu nó bằng @PostConstruct. Tôi không chắc chắn về các khung công tác khác nhưng tôi cho rằng họ làm điều gì đó tương tự.

Cả hai cách làm việc, đó là một vấn đề ưu tiên.


21
Lý do tiêm chích xây dựng là để làm cho các phụ thuộc rõ ràng, không phải vì nó trông tự nhiên hơn đối với các nhà phát triển java.
L-Four

8
Nhận xét muộn, nhưng câu trả lời này làm tôi cười :)
Frederik Prijck

1
+1 cho tiêm dựa trên setter. Nếu tôi có các dịch vụ và kho lưu trữ được định nghĩa trong lớp thì rõ ràng chúng là phụ thuộc .. Tôi không cần phải viết các hàm tạo VB6 lớn và thực hiện mã gán ngu ngốc trong hàm tạo. Nó khá rõ ràng những gì phụ thuộc là trên các lĩnh vực cần thiết.
Piotr Kula

Theo năm 2018, Spring chính thức khuyến nghị không sử dụng setter tiêm trừ các phụ thuộc có giá trị mặc định hợp lý. Như trong, nếu sự phụ thuộc là bắt buộc đối với lớp, thì nên sử dụng hàm tạo. Xem thảo luận về setter vs ctor DI
John Doe
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.