Nên tiêm phụ thuộc vào ctor hoặc theo phương pháp?


16

Xem xét:

public class CtorInjectionExample
{
    public CtorInjectionExample(ISomeRepository SomeRepositoryIn, IOtherRepository OtherRepositoryIn)
    {
        this._someRepository = SomeRepositoryIn;
        this._otherRepository = OtherRepositoryIn;
    }

    public void SomeMethod()
    {
        //use this._someRepository
    }

    public void OtherMethod()
    {
        //use this._otherRepository
    }
}

chống lại:

public class MethodInjectionExample
{
    public MethodInjectionExample()
    {
    }

    public void SomeMethod(ISomeRepository SomeRepositoryIn)
    {
        //use SomeRepositoryIn
    }

    public void OtherMethod(IOtherRepository OtherRepositoryIn)
    {
        //use OtherRepositoryIn
    }
}

Mặc dù việc tiêm Ctor làm cho việc mở rộng trở nên khó khăn (bất kỳ mã nào gọi ctor sẽ cần cập nhật khi thêm phụ thuộc mới) và tiêm cấp độ phương thức dường như vẫn được gói gọn hơn từ phụ thuộc cấp độ lớp và tôi không thể tìm thấy bất kỳ đối số nào khác cho / chống lại các phương pháp này .

Có một cách tiếp cận dứt khoát để tiêm?

(NB Tôi đã tìm kiếm thông tin về điều này và cố gắng làm cho câu hỏi này trở nên khách quan.)


4
Nếu các lớp của bạn gắn kết, thì nhiều phương thức khác nhau sẽ được sử dụng cùng các trường. Điều này sẽ cung cấp cho bạn câu trả lời mà bạn tìm kiếm ...
Oded

@Oded Điểm tốt, sự gắn kết sẽ ảnh hưởng lớn đến quyết định. Nếu một lớp là các tập hợp các phương thức trợ giúp mà mỗi phương thức có các phụ thuộc khác nhau (dường như không gắn kết, nhưng tương tự về mặt logic) và một lớp khác có tính gắn kết cao, thì mỗi phương thức nên sử dụng phương pháp có lợi nhất. Tuy nhiên, điều này sẽ gây ra sự không nhất quán trong một giải pháp.
StuperUser

Có liên quan đến các ví dụ tôi đã đưa ra: en.wikipedia.org/wiki/False_dilemma
StuperUser

Câu trả lời:


3

Điều đó phụ thuộc, nếu đối tượng được tiêm được sử dụng bởi nhiều hơn một phương thức trong lớp và tiêm vào nó trên mỗi phương thức không có ý nghĩa, bạn cần giải quyết các phụ thuộc cho mỗi lệnh gọi phương thức, nhưng nếu phụ thuộc chỉ được sử dụng bởi một phương thức tiêm vào nó trên hàm tạo là không tốt, nhưng vì bạn đang phân bổ các tài nguyên không bao giờ có thể được sử dụng.


15

Phải, trước hết, hãy giải quyết "Bất kỳ mã nào gọi ctor sẽ cần cập nhật khi thêm phụ thuộc mới"; Để rõ ràng, nếu bạn đang thực hiện tiêm phụ thuộc và bạn có bất kỳ mã nào gọi new () trên một đối tượng có phụ thuộc, bạn đã làm sai .

Container DI của bạn sẽ có thể đưa ra tất cả các phụ thuộc có liên quan, do đó bạn không cần phải lo lắng về việc thay đổi chữ ký của hàm tạo, để đối số không thực sự giữ.

Đối với ý tưởng tiêm theo phương pháp so với mỗi phương pháp, có hai vấn đề chính với phương pháp tiêm theo phương pháp.

Một vấn đề là các phương thức của lớp của bạn nên chia sẻ các phụ thuộc, đó là một cách để đảm bảo các lớp của bạn được phân tách hiệu quả, nếu bạn thấy một lớp có số lượng phụ thuộc lớn (có thể hơn 4-5) thì lớp đó là một ứng cử viên chính để tái cấu trúc thành hai lớp.

Vấn đề tiếp theo là để "tiêm" các phụ thuộc, theo từng phương thức, bạn phải chuyển chúng vào lệnh gọi phương thức. Điều này có nghĩa là bạn sẽ phải giải quyết các phụ thuộc trước khi gọi phương thức, vì vậy bạn có thể sẽ kết thúc với một loạt mã như thế này:

var someDependency = ServiceLocator.Resolve<ISomeDependency>();
var something = classBeingInjected.DoStuff(someDependency);

Bây giờ, giả sử bạn sẽ gọi phương thức này ở 10 nơi xung quanh ứng dụng của mình: bạn sẽ có 10 đoạn trích này. Tiếp theo, giả sử bạn cần thêm một phụ thuộc khác vào DoStuff (): bạn sẽ phải thay đổi đoạn trích đó 10 lần (hoặc gói nó trong một phương thức, trong trường hợp bạn chỉ sao chép hành vi DI theo cách thủ công, đó là một sự lãng phí của thời gian).

Vì vậy, những gì bạn đã thực hiện ở đó là làm cho các lớp sử dụng DI của bạn nhận thức được thùng chứa DI của riêng chúng, đây là một ý tưởng tồi tệ , vì nó rất nhanh dẫn đến một thiết kế lộn xộn khó bảo trì.

So sánh điều đó với tiêm xây dựng; Trong tiêm chích xây dựng, bạn không bị ràng buộc với một thùng chứa DI cụ thể và bạn không bao giờ chịu trách nhiệm trực tiếp trong việc hoàn thành các phụ thuộc của các lớp của mình, vì vậy việc bảo trì khá đau đầu.

Nghe có vẻ như tôi đang cố gắng áp dụng IoC cho một lớp có chứa một loạt các phương thức của trình trợ giúp không liên quan, trong khi bạn nên tách lớp trình trợ giúp thành một số lớp dịch vụ dựa trên việc sử dụng, sau đó sử dụng trình trợ giúp để ủy quyền các cuộc gọi. Đây vẫn không phải là một cách tiếp cận tuyệt vời (người trợ giúp được phân loại với các phương thức làm bất cứ điều gì phức tạp hơn là chỉ giải quyết các đối số được truyền cho chúng thường chỉ là các lớp dịch vụ được viết kém), nhưng ít nhất nó sẽ giữ cho thiết kế của bạn sạch sẽ hơn một chút .

(NB Tôi đã thực hiện cách tiếp cận mà bạn đề xuất trước đây và đó là một ý tưởng rất tồi mà tôi đã không lặp lại kể từ đó. Hóa ra tôi đang cố tách ra các lớp thực sự không cần phải tách ra, và cuối cùng với một tập hợp các giao diện trong đó mỗi cuộc gọi phương thức yêu cầu một lựa chọn gần như cố định các giao diện khác. Đó là một cơn ác mộng để duy trì.)


1
"if you're doing dependency injection and you have any code calling new() on an object with dependencies, you're doing it wrong."Nếu bạn đang kiểm thử đơn vị một phương thức trên một lớp, bạn phải khởi tạo nó hoặc bạn có sử dụng DI để khởi tạo Code Under Test không?
StuperUser

@StuperUser Đơn vị thử nghiệm là một kịch bản hơi khác, nhận xét của tôi chỉ thực sự áp dụng cho mã sản xuất. Vì bạn sẽ tạo ra các phụ thuộc giả (hoặc còn sơ khai) cho các thử nghiệm của mình thông qua khung mô phỏng, mỗi hành vi được cấu hình sẵn và các giả định và xác nhận, bạn sẽ cần tiêm chúng bằng tay.
Ed James

1
Đó là mã mà tôi đang nói đến khi tôi nói "any code calling the ctor will need updating", mã sản xuất của tôi không gọi cho các công ty. Vì những lý do.
StuperUser

2
@StuperUser Đủ công bằng, nhưng bạn vẫn sẽ gọi các phương thức với các phụ thuộc cần thiết, có nghĩa là phần còn lại của câu trả lời của tôi vẫn đứng vững! Thành thật mà nói, tôi đồng ý về vấn đề tiêm chích xây dựng và buộc tất cả các phụ thuộc, từ góc độ thử nghiệm đơn vị. Tôi có xu hướng sử dụng tính năng tiêm thuộc tính, vì điều này cho phép bạn chỉ tiêm các phụ thuộc mà bạn cần để kiểm tra, mà về cơ bản tôi là một tập hợp các cuộc gọi Assert.WasNotCalled ẩn mà không phải thực sự viết bất kỳ mã nào! : D
Ed James

3

Có nhiều loại đảo ngược kiểm soát , và câu hỏi của bạn chỉ đề xuất hai (bạn cũng có thể sử dụng nhà máy để tiêm phụ thuộc).

Câu trả lời là: nó phụ thuộc vào nhu cầu. Đôi khi tốt hơn là sử dụng một loại, và một loại khác.

Cá nhân tôi thích các hàm tạo của hàm tạo, vì hàm tạo ở đó để khởi tạo hoàn toàn đối tượng. Phải gọi một setter sau này làm cho đối tượng không được xây dựng đầy đủ.


3

Bạn đã bỏ lỡ một điều mà ít nhất đối với tôi là tiêm tài sản linh hoạt nhất. Tôi sử dụng Castle / Windsor và tất cả những gì tôi cần làm là thêm một thuộc tính tự động mới vào lớp của mình và tôi nhận được đối tượng được tiêm mà không gặp vấn đề gì và không phá vỡ bất kỳ giao diện nào.


Tôi đã quen với các ứng dụng web và nếu các lớp này nằm trong lớp Miền, được gọi bởi UI, thì các phần phụ thuộc của nó đã được giải quyết theo kiểu tương tự như thuộc tính tiêm. Tôi đang tự hỏi làm thế nào để đối phó với các lớp mà tôi có thể truyền vào các đối tượng được tiêm vào.
StuperUser

Tôi cũng thích tiêm thuộc tính, đơn giản vì vậy bạn vẫn có thể sử dụng các tham số hàm tạo thông thường với vùng chứa DI, tự động phân biệt giữa các phụ thuộc được giải quyết và không được giải quyết. Tuy nhiên, vì hàm tạo và tiêm thuộc tính giống nhau về mặt chức năng cho kịch bản này, tôi đã chọn không đề cập đến nó;)
Ed James

Việc tiêm thuộc tính buộc mọi đối tượng phải có thể thay đổi và tạo các thể hiện ở trạng thái không hợp lệ. Tại sao điều đó sẽ được ưa thích?
Chris Pitman

2
@Chris Trên thực tế, trong điều kiện bình thường, hàm tạo và hàm thuộc tính hoạt động khá giống nhau, với đối tượng được tiêm đầy đủ trong quá trình phân giải. Lần duy nhất có thể được coi là "không hợp lệ" là trong quá trình thử nghiệm đơn vị, khi bạn sẽ không sử dụng bộ chứa DI để khởi tạo việc triển khai. Xem nhận xét của tôi về câu trả lời của tôi để biết lý do tại sao điều này thực sự có lợi. Tôi đánh giá cao khả năng biến đổi có thể là một vấn đề, nhưng thực tế bạn chỉ tham chiếu các lớp được giải quyết thông qua giao diện của chúng, điều này sẽ không làm lộ ra các phụ thuộc để chỉnh sửa.
Ed James
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.