Phụ thuộc tiêm thông qua các nhà xây dựng hoặc setters tài sản?


151

Tôi đang cấu trúc lại một lớp và thêm một phụ thuộc mới vào nó. Lớp hiện đang lấy các phụ thuộc hiện có của nó trong hàm tạo. Vì vậy, để thống nhất, tôi thêm tham số vào hàm tạo.
Tất nhiên, có một vài lớp con cộng với nhiều hơn cho các bài kiểm tra đơn vị, vì vậy bây giờ tôi đang chơi trò chơi để thay đổi tất cả các hàm tạo cho phù hợp, và nó mất nhiều thời gian.
Nó làm cho tôi nghĩ rằng sử dụng các thuộc tính với setters là một cách tốt hơn để có được sự phụ thuộc. Tôi không nghĩ rằng các phụ thuộc được chèn nên là một phần của giao diện để xây dựng một thể hiện của một lớp. Bạn thêm một phụ thuộc và bây giờ tất cả người dùng của bạn (các lớp con và bất kỳ ai bắt đầu trực tiếp với bạn) đột nhiên biết về nó. Điều đó cảm thấy như một sự phá vỡ của đóng gói.

Đây dường như không phải là mô hình với mã hiện có ở đây, vì vậy tôi đang tìm hiểu xem sự đồng thuận chung là gì, ưu và nhược điểm của các nhà xây dựng so với các thuộc tính. Là sử dụng tài sản setters tốt hơn?

Câu trả lời:


126

Vâng, nó phụ thuộc :-).

Nếu lớp không thể thực hiện công việc của nó mà không có sự phụ thuộc, thì hãy thêm nó vào hàm tạo. Lớp học cần sự phụ thuộc mới, vì vậy bạn muốn thay đổi của mình phá vỡ mọi thứ. Ngoài ra, tạo một lớp không được khởi tạo hoàn toàn ("xây dựng hai bước") là một mô hình chống (IMHO).

Nếu lớp có thể làm việc mà không cần phụ thuộc, thì setter vẫn ổn.


11
Tôi nghĩ rằng nhiều trường hợp nên sử dụng mẫu Null Object và yêu cầu các tham chiếu trên hàm tạo. Điều này tránh tất cả kiểm tra null và độ phức tạp chu kỳ tăng lên.
Mark Lindell

3
@Mark: Điểm tốt. Tuy nhiên, câu hỏi là về việc thêm một phụ thuộc vào một lớp hiện có. Sau đó, giữ một hàm tạo không có đối số cho phép tương thích ngược.
sleske

Điều gì sẽ xảy ra khi sự phụ thuộc là cần thiết để hoạt động, nhưng việc tiêm mặc định sự phụ thuộc đó thường sẽ đủ. Vậy thì sự phụ thuộc đó sẽ bị "quá tải" bởi thuộc tính hay quá tải của hàm tạo?
Patrick Szalapski

@Patrick: Bởi "lớp không thể thực hiện công việc của mình với sự phụ thuộc", ý tôi là không có mặc định hợp lý (ví dụ: lớp yêu cầu kết nối DB). Trong tình huống của bạn, cả hai sẽ làm việc. Tôi vẫn thường chọn cách tiếp cận hàm tạo, vì nó làm giảm độ phức tạp (ví dụ: nếu setter được gọi hai lần) thì sao?
sleske

1
Một bài viết hay về vấn đề này tại đây expljava.com/different-types-of-bean-injection-in-spring
Eldhose Abraham 27/12/17

21

Người dùng của một lớp được cho là biết về sự phụ thuộc của một lớp nhất định. Nếu tôi có một lớp, ví dụ, được kết nối với cơ sở dữ liệu và không cung cấp phương tiện để tiêm phụ thuộc lớp liên tục, người dùng sẽ không bao giờ biết rằng phải có kết nối đến cơ sở dữ liệu. Tuy nhiên, nếu tôi thay đổi hàm tạo, tôi cho người dùng biết rằng có sự phụ thuộc vào lớp kiên trì.

Ngoài ra, để ngăn bạn khỏi phải thay đổi mỗi lần sử dụng hàm tạo cũ, chỉ cần áp dụng chuỗi hàm tạo như một cầu nối tạm thời giữa hàm tạo cũ và hàm mới.

public class ClassExample
{
    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo)
        : this (dependnecyOne, dependencyTwo, new DependnecyThreeConcreteImpl())
    { }

    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo, IDependencyThree dependencyThree)
    {
        // Set the properties here.
    }
}

Một trong những điểm của tiêm phụ thuộc là tiết lộ những gì phụ thuộc mà lớp có. Nếu lớp có quá nhiều phụ thuộc, thì có lẽ đã đến lúc một số phép tái cấu trúc diễn ra: Có phải mọi phương thức của lớp đều sử dụng tất cả các phụ thuộc không? Nếu không, thì đó là điểm khởi đầu tốt để xem lớp có thể được tách ra ở đâu.


Tất nhiên, hàm tạo chuỗi chỉ hoạt động nếu có giá trị mặc định hợp lý cho tham số mới. Nhưng nếu không, bạn không thể tránh làm hỏng mọi thứ ...
sleske

Thông thường, bạn sẽ sử dụng bất cứ thứ gì bạn đang sử dụng trong phương thức trước khi tiêm phụ thuộc làm tham số mặc định. Lý tưởng nhất, điều này sẽ làm cho hàm tạo mới bổ sung một phép tái cấu trúc sạch, vì hành vi của lớp sẽ không thay đổi.
Doctor Blue

1
Tôi có quan điểm của bạn về phụ thuộc quản lý tài nguyên như kết nối cơ sở dữ liệu. Tôi nghĩ vấn đề trong trường hợp của tôi là lớp tôi đang thêm một phụ thuộc để có một vài lớp con. Trong một thế giới vùng chứa IOC nơi thuộc tính sẽ được đặt bởi vùng chứa, sử dụng trình thiết lập ít nhất sẽ làm giảm áp lực lên sự trùng lặp giao diện của hàm tạo giữa tất cả các lớp con.
Niall Connaughton

17

Tất nhiên, đưa vào hàm tạo có nghĩa là bạn có thể xác nhận tất cả cùng một lúc. Nếu bạn gán mọi thứ vào các trường chỉ đọc thì bạn có một số đảm bảo về sự phụ thuộc của đối tượng ngay từ thời gian xây dựng.

Đó là một nỗi đau thực sự khi thêm các phụ thuộc mới, nhưng ít nhất theo cách này trình biên dịch tiếp tục phàn nàn cho đến khi nó chính xác. Đó là một điều tốt, tôi nghĩ.


Thêm một cho điều này. Ngoài ra, điều này cực kỳ làm giảm nguy cơ phụ thuộc vòng tròn ...
gilgamash

11

Nếu bạn có số lượng lớn các phụ thuộc tùy chọn (đã có mùi) thì có lẽ tiêm setter là cách để đi. Con Conttor tiêm tốt hơn cho thấy phụ thuộc của bạn mặc dù.


11

Cách tiếp cận ưa thích chung là sử dụng tiêm constructor càng nhiều càng tốt.

Trình xây dựng tiêm chính xác nêu rõ các phụ thuộc cần thiết để đối tượng hoạt động đúng - không có gì khó chịu hơn việc làm mới một đối tượng và khiến nó bị sập khi gọi một phương thức trên nó vì một số phụ thuộc không được đặt. Đối tượng được trả về bởi một hàm tạo nên ở trạng thái làm việc.

Cố gắng chỉ có một nhà xây dựng, nó giữ cho thiết kế đơn giản và tránh sự mơ hồ (nếu không phải cho con người, cho container DI).

Bạn có thể sử dụng tính năng tiêm thuộc tính khi bạn có cái mà Mark Seemann gọi là mặc định cục bộ trong cuốn sách "Dependency Injection in .NET" của mình: phụ thuộc là tùy chọn vì bạn có thể cung cấp một triển khai hoạt động tốt nhưng muốn cho phép người gọi chỉ định một cách khác nếu cần thiết

(Câu trả lời cũ dưới đây)


Tôi nghĩ rằng tiêm constructor là tốt hơn nếu tiêm là bắt buộc. Nếu điều này thêm quá nhiều nhà xây dựng, hãy xem xét sử dụng các nhà máy thay vì nhà xây dựng.

Tiêm setter là tốt nếu tiêm là tùy chọn, hoặc nếu bạn muốn thay đổi máng nửa chừng. Tôi thường không thích setters, nhưng đó là vấn đề của hương vị.


Tôi cho rằng thường thay đổi tiêm giữa chừng là phong cách xấu (vì bạn đang thêm trạng thái ẩn vào đối tượng của mình). Nhưng không có quy tắc nào ngoại trừ khóa học ...
sleske

Đúng, đó là lý do tại sao tôi nói tôi không thích setter quá nhiều ... Tôi thích cách tiếp cận của nhà xây dựng vì nó không thể thay đổi.
Philippe

"Nếu điều này thêm quá nhiều nhà xây dựng, hãy xem xét sử dụng các nhà máy thay vì nhà xây dựng." Về cơ bản, bạn trì hoãn mọi ngoại lệ về thời gian chạy và thậm chí có thể gặp sự cố và cuối cùng bị mắc kẹt với việc triển khai định vị dịch vụ.
MeTitus

@Marco đó là câu trả lời trước đây của tôi và bạn đã đúng. Nếu có nhiều nhà xây dựng, tôi sẽ lập luận rằng lớp học làm quá nhiều thứ :-) Hoặc xem xét một nhà máy trừu tượng.
Philippe

7

Đó chủ yếu là vấn đề sở thích cá nhân. Cá nhân tôi có xu hướng thích tiêm setter hơn, bởi vì tôi tin rằng nó mang lại cho bạn sự linh hoạt hơn theo cách mà bạn có thể thay thế việc triển khai trong thời gian chạy. Hơn nữa, theo tôi, các hàm tạo có nhiều đối số không rõ ràng và các đối số được cung cấp trong hàm tạo nên được giới hạn ở các đối số không tùy chọn.

Miễn là giao diện lớp (API) rõ ràng về những gì nó cần để thực hiện nhiệm vụ của mình, bạn vẫn ổn.


vui lòng ghi rõ lý do tại sao bạn xuống cấp?
nkr1pt

3
Vâng, các nhà xây dựng với rất nhiều đối số là xấu. Đó là lý do tại sao bạn cấu trúc lại các lớp với nhiều tham số hàm tạo :-).
sleske

10
@ nkr1pt: Hầu hết mọi người (bao gồm cả tôi) đồng ý rằng tiêm setter là xấu nếu nó cho phép bạn tạo một lớp thất bại trong thời gian chạy nếu việc tiêm không được thực hiện. Tôi tin rằng ai đó đã phản đối tuyên bố của bạn về nó là sở thích cá nhân.
sleske

7

Cá nhân tôi thích "mẫu" Trích xuất và ghi đè hơn so với tiêm phụ thuộc vào hàm tạo, phần lớn là vì lý do được nêu trong câu hỏi của bạn. Bạn có thể đặt các thuộc tính là virtualvà sau đó ghi đè lên việc thực hiện trong một lớp có thể kiểm tra được dẫn xuất.


1
Tôi nghĩ tên chính thức của mẫu là "Phương thức mẫu".
davidjnelson

6

Tôi thích tiêm constructor vì nó giúp "thực thi" các yêu cầu phụ thuộc của một lớp. Nếu đó là trong c'tor, một người tiêu dùng để thiết lập các đối tượng để có được ứng dụng để biên dịch. Nếu bạn sử dụng phương thức tiêm setter, họ có thể không biết họ có vấn đề cho đến thời gian chạy - và tùy thuộc vào đối tượng, có thể bị trễ thời gian chạy.

Thỉnh thoảng tôi vẫn sử dụng phương thức tiêm setter khi đối tượng được tiêm có thể cần một loạt công việc, như khởi tạo.


6

Tôi tiêm tiêm xây dựng, bởi vì điều này có vẻ hợp lý nhất. Nó giống như nói rằng lớp học của tôi đòi hỏi những phụ thuộc này để thực hiện công việc của mình. Nếu nó là một phụ thuộc tùy chọn thì tính chất có vẻ hợp lý.

Tôi cũng sử dụng tính năng tiêm thuộc tính để thiết lập những thứ mà vùng chứa không có tham chiếu đến, chẳng hạn như Chế độ xem ASP.NET trên người thuyết trình được tạo bằng cách sử dụng vùng chứa.

Tôi không nghĩ rằng nó phá vỡ đóng gói. Các hoạt động bên trong nên vẫn còn nội bộ và các phụ thuộc giải quyết một mối quan tâm khác.


Cảm ơn câu trả lời của bạn. Có vẻ như các nhà xây dựng là câu trả lời phổ biến. Tuy nhiên tôi nghĩ rằng nó phá vỡ đóng gói theo một cách nào đó. Tiêm phụ thuộc trước, lớp sẽ khai báo và khởi tạo bất kỳ loại cụ thể nào cần thiết để thực hiện công việc của mình. Với DI, các lớp con (và bất kỳ trình khởi tạo thủ công nào) giờ đây sẽ biết công cụ nào mà lớp cơ sở đang sử dụng. Bạn thêm một phụ thuộc mới và bây giờ bạn phải xâu chuỗi cá thể từ tất cả các lớp con, ngay cả khi chúng không cần phải sử dụng chính phụ thuộc đó.
Niall Connaughton

Chỉ cần viết một câu trả lời dài và mất nó do một ngoại lệ trên trang web này !! :( Tóm lại, lớp cơ sở thường được sử dụng để sử dụng lại logic. Logic này có thể dễ dàng đi vào lớp con ... vì vậy bạn có thể nghĩ về lớp cơ sở và lớp con = một mối quan tâm, phụ thuộc vào nhiều đối tượng bên ngoài, đó là phụ thuộc vào nhiều đối tượng bên ngoài, đó là . làm những công việc khác nhau thực tế bạn có phụ thuộc, không có nghĩa là bạn cần để lộ bất cứ điều gì trước đây bạn đã có thể giữ bí mật.
David Kiff

3

Một lựa chọn có thể đáng xem xét là kết hợp nhiều phụ thuộc phức tạp từ các phụ thuộc đơn giản. Đó là, định nghĩa các lớp bổ sung cho các phụ thuộc ghép. Điều này làm cho mọi thứ dễ dàng hơn một chút khi xây dựng WRT constructor - ít tham số hơn cho mỗi cuộc gọi - trong khi vẫn duy trì điều phải cung cấp tất cả phụ thuộc-để-khởi tạo.

Tất nhiên sẽ hợp lý nhất nếu có một số nhóm phụ thuộc logic, do đó, hợp chất nhiều hơn một tổng hợp tùy ý và sẽ hợp lý nhất nếu có nhiều phụ thuộc cho một phụ thuộc ghép đơn - nhưng "mẫu" tham số khối đã xuất hiện từ lâu và hầu hết những thứ mà tôi thấy đều khá độc đoán.

Mặc dù vậy, cá nhân tôi là một người hâm mộ sử dụng các phương thức / thuộc tính-setters để chỉ định các phụ thuộc, các tùy chọn, vv Các tên gọi giúp mô tả những gì đang diễn ra. Mặc dù vậy, đó là một ý tưởng tốt để cung cấp ví dụ về đoạn trích này là cách thiết lập và đảm bảo rằng lớp phụ thuộc thực hiện đủ kiểm tra lỗi. Bạn có thể muốn sử dụng một mô hình trạng thái hữu hạn để thiết lập.


3

Gần đây tôi đã gặp phải một tình huống mà tôi có nhiều phụ thuộc trong một lớp, nhưng chỉ một trong số các phụ thuộc nhất thiết phải thay đổi trong mỗi lần thực hiện. Vì truy cập dữ liệu và phụ thuộc ghi nhật ký lỗi có thể chỉ được thay đổi cho mục đích thử nghiệm, tôi đã thêm các tham số tùy chọn cho các phụ thuộc đó và cung cấp cài đặt mặc định của các phụ thuộc đó trong mã xây dựng của tôi. Theo cách này, lớp duy trì hành vi mặc định của nó trừ khi bị người tiêu dùng của lớp ghi đè.

Việc sử dụng các tham số tùy chọn chỉ có thể được thực hiện trong các khung hỗ trợ chúng, chẳng hạn như .NET 4 (cho cả C # và VB.NET, mặc dù VB.NET luôn có chúng). Tất nhiên, bạn có thể thực hiện chức năng tương tự bằng cách sử dụng một thuộc tính có thể được gán lại bởi người tiêu dùng của lớp bạn, nhưng bạn không có được lợi thế về tính bất biến được cung cấp bằng cách có một đối tượng giao diện riêng được gán cho một tham số của hàm tạo.

Tất cả điều này đang được nói, nếu bạn đang giới thiệu một phụ thuộc mới phải được cung cấp bởi mọi người tiêu dùng, bạn sẽ phải cấu trúc lại hàm tạo của bạn và tất cả mã mà người tiêu dùng thuộc lớp của bạn. Các đề xuất của tôi ở trên thực sự chỉ áp dụng nếu bạn có khả năng cung cấp triển khai mặc định cho tất cả các mã hiện tại của mình nhưng vẫn cung cấp khả năng ghi đè cài đặt mặc định nếu cần.


1

Đây là một bài viết cũ, nhưng nếu nó cần thiết trong tương lai thì có lẽ đây là một mục đích sử dụng:

https://github.com/omegamit6zeichen/prinject

Tôi đã có một ý tưởng tương tự và đưa ra khuôn khổ này. Nó có thể còn lâu mới hoàn thành, nhưng đó là một ý tưởng về một khung tập trung vào việc tiêm tài sản


0

Nó phụ thuộc vào cách bạn muốn thực hiện. Tôi thích tiêm constructor bất cứ nơi nào tôi cảm thấy các giá trị đi vào thực hiện không thay đổi thường xuyên. Ví dụ: Nếu stragtegy compnay đi cùng với máy chủ oracle, tôi sẽ định cấu hình các giá trị datsource của mình cho một kết nối đạt được thông qua bean constructor. Khác, nếu ứng dụng của tôi là một sản phẩm và có khả năng nó có thể kết nối với bất kỳ db nào của khách hàng, tôi sẽ triển khai cấu hình db đó và triển khai đa thương hiệu thông qua tiêm setter. Tôi vừa lấy một ví dụ nhưng có nhiều cách tốt hơn để thực hiện các kịch bản tôi đã đề cập ở trên.


1
Ngay cả khi mã hóa nội bộ, tôi luôn viết mã theo quan điểm rằng tôi là một nhà thầu độc lập phát triển mã dự định có thể phân phối lại. Tôi cho rằng nó sẽ là nguồn mở. Bằng cách này, theo cách tôi đảm bảo rằng mã là mô-đun và có thể cắm và tuân theo các nguyên tắc RẮN.
Fred

0

Trình xây dựng của trình xây dựng không tiết lộ rõ ​​ràng các phụ thuộc, làm cho mã dễ đọc hơn và ít bị xử lý các lỗi thời gian chạy hơn nếu các đối số được kiểm tra trong hàm tạo, nhưng nó thực sự đi vào quan điểm cá nhân và bạn càng sử dụng DI càng nhiều có xu hướng lắc lư qua lại bằng cách này hay cách khác tùy thuộc vào dự án. Cá nhân tôi có vấn đề với mã có mùi giống như các nhà xây dựng với một danh sách dài các đối số và tôi cảm thấy rằng người tiêu dùng của một đối tượng nên biết các phụ thuộc để sử dụng đối tượng, vì vậy điều này tạo ra một trường hợp sử dụng thuộc tính tiêm. Tôi không thích bản chất ngầm của việc tiêm tài sản, nhưng tôi thấy nó thanh lịch hơn, dẫn đến mã trông gọn gàng hơn. Nhưng mặt khác, tiêm constructor cung cấp mức độ đóng gói cao hơn,

Chọn tiêm bởi nhà xây dựng hoặc theo tài sản một cách khôn ngoan dựa trên kịch bản cụ thể của bạn. Và đừng cảm thấy rằng bạn phải sử dụng DI chỉ vì nó có vẻ cần thiết và nó sẽ ngăn chặn mùi thiết kế và mã xấu. Đôi khi không đáng để sử dụng một mẫu nếu nỗ lực và độ phức tạp vượt trội hơn lợi ích. Giữ cho nó đơn giả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.