Phụ thuộc tiêm: Tiêm thực địa so với tiêm xây dựng?


61

Tôi biết đây là một cuộc tranh luận nóng bỏng và các ý kiến ​​có xu hướng thay đổi theo thời gian để thực hành phương pháp tốt nhất.

Tôi đã từng sử dụng phương pháp tiêm trường độc quyền cho các lớp học của mình, cho đến khi tôi bắt đầu đọc các blog khác nhau ( ví dụ : petrikainulainenschauderhaftfowler ) về lợi ích của việc tiêm xây dựng. Kể từ đó, tôi đã chuyển phương pháp của mình sang sử dụng hàm tạo cho các phụ thuộc cần thiết và tiêm setter cho các phụ thuộc tùy chọn.

Tuy nhiên, gần đây tôi đã có một cuộc tranh luận với tác giả của JMockit - một khuôn khổ chế giễu - trong đó anh ta thấy người xây dựng & tiêm setter là một thực tiễn xấu và chỉ ra rằng cộng đồng JEE đồng ý với anh ta.

Trong thế giới ngày nay, có một cách làm tiêm ưa thích? Được tiêm Field được ưa thích?

Đã chuyển sang tiêm constructor từ tiêm hiện trường trong vài năm qua, tôi thấy nó rõ ràng hơn để sử dụng, nhưng tôi tự hỏi liệu tôi có nên kiểm tra lại quan điểm của tôi. Tác giả của JMockit (Rogério Liesenfeld) , rõ ràng rất thành thạo về DI, vì vậy tôi cảm thấy bắt buộc phải xem lại cách tiếp cận của mình khi cho rằng anh ta cảm thấy rất mạnh mẽ đối với việc tiêm constructor / setter.



2
Nếu bạn không được phép sử dụng hàm tạo hoặc tiêm setter, thì làm thế nào bạn có nghĩa vụ trao các phụ thuộc của bạn cho lớp? Có lẽ bạn nên liên kết tốt hơn với cuộc tranh luận, trừ khi cuộc trò chuyện của bạn với anh chàng Mockit là riêng tư.
Robert Harvey

2
@DavidArno: Trong một lớp không thay đổi, trạng thái được đặt một lần ... trong hàm tạo.
Robert Harvey

1
@Davkd những gì bạn mô tả là mô hình miền thiếu máu. Nó chắc chắn có những người đề xướng nhưng nó không thực sự là đối tượng định hướng nữa; nơi dữ liệu và các functon hoạt động trên dữ liệu đó sống cùng nhau.
Richard Tingle

3
@David Không có gì sai với lập trình chức năng. Nhưng về cơ bản java được thiết kế để hướng đối tượng và bạn sẽ liên tục chiến đấu với nó và kết thúc với mô hình miền thiếu máu nếu bạn không cẩn thận. Không có gì sai với mã định hướng chức năng nhưng nên sử dụng một công cụ thích hợp cho nó, mà java thì không (mặc dù lambda đã thêm các yếu tố lập trình dựa trên chức năng)
Richard Tingle

Câu trả lời:


48

Lĩnh vực tiêm là một chút quá "hành động ma quái ở khoảng cách xa" theo sở thích của tôi.

Hãy xem xét ví dụ bạn đã cung cấp trong bài đăng trên Google Groups:

public class VeracodeServiceImplTest {


    @Tested(fullyInitialized=true)
    VeracodeServiceImpl veracodeService;
    @Tested(fullyInitialized=true, availableDuringSetup=true)
    VeracodeRepositoryImpl veracodeRepository;
    @Injectable private ResultsAPIWrapper resultsApiWrapper;
    @Injectable private AdminAPIWrapper adminApiWrapper;
    @Injectable private UploadAPIWrapper uploadApiWrapper;
    @Injectable private MitigationAPIWrapper mitigationApiWrapper;

    static { VeracodeRepositoryImpl.class.getName(); }
...
}

Vì vậy, về cơ bản những gì bạn đang nói là "Tôi có lớp này với trạng thái riêng tư, mà tôi đã đính kèm @injectablecác chú thích, có nghĩa là trạng thái có thể được tự động điền bởi một số đại lý từ bên ngoài, mặc dù tất cả trạng thái của tôi đã được tuyên bố là riêng tư. "

Tôi hiểu những động lực cho việc này. Đó là một nỗ lực để tránh phần lớn buổi lễ vốn có trong việc thiết lập một lớp học đúng cách. Về cơ bản, điều người ta nói là "Tôi mệt mỏi khi viết tất cả bản tóm tắt này, vì vậy tôi sẽ chú thích tất cả trạng thái của mình và để cho container DI đảm nhiệm việc đặt nó cho tôi."

Đó là một quan điểm hoàn toàn hợp lệ. Nhưng đó cũng là một cách giải quyết cho các tính năng ngôn ngữ không nên làm việc xung quanh. Ngoài ra, tại sao dừng lại ở đó? Theo truyền thống, DI đã dựa vào mỗi lớp có Giao diện đồng hành. Tại sao không loại bỏ tất cả các giao diện với chú thích là tốt?

Hãy xem xét lựa chọn thay thế (đây sẽ là C #, vì tôi biết nó tốt hơn, nhưng có lẽ có một tương đương chính xác trong Java):

public class VeracodeService
{        
    private readonly IResultsAPIWrapper _resultsApiWrapper;
    private readonly IAdminAPIWrapper _adminApiWrapper;
    private readonly IUploadAPIWrapper _uploadApiWrapper;
    private readonly IMitigationAPIWrapper _mitigationApiWrapper;

    // Constructor
    public VeracodeService(IResultsAPIWrapper resultsApiWrapper, IAdminAPIWrapper adminApiWrapper, IUploadAPIWrapper uploadApiWrapper,         IMitigationAPIWrapper mitigationApiWrapper)
    {
         _resultsAPIWrapper = resultsAPIWrapper;
         _adminAPIWrapper = adminAPIWrapper;
         _uploadAPIWrapper = uploadAPIWrapper;
         _mitigationAPIWrapper = mitigationAPIWrapper;
    }
}

Tôi đã biết một số điều về lớp học này. Đó là một lớp học bất biến; trạng thái chỉ có thể được đặt trong hàm tạo (tham chiếu, trong trường hợp cụ thể này). Và bởi vì mọi thứ bắt nguồn từ một giao diện, tôi có thể trao đổi các triển khai trong hàm tạo, đó là nơi mà các giả của bạn xuất hiện.

Bây giờ tất cả các thùng chứa DI của tôi phải làm là phản ánh qua hàm tạo để xác định đối tượng nào nó cần để tạo mới. Nhưng sự phản ánh đó đang được thực hiện trên một thành viên công cộng, theo cách thức hạng nhất; tức là siêu dữ liệu đã là một phần của lớp, đã được khai báo trong hàm tạo, một phương thức có mục đích rõ ràng là cung cấp cho lớp các phụ thuộc mà nó cần.

Cấp cho điều này là rất nhiều nồi hơi, nhưng đây là cách ngôn ngữ được thiết kế. Các chú thích có vẻ như là một bản hack bẩn cho một thứ đáng lẽ phải được tích hợp vào ngôn ngữ.


Trừ khi tôi đọc bài viết của bạn sai, bạn không thực sự nhìn vào ví dụ đúng. Việc triển khai của tôi VeracodeServicegần giống với cách bạn đã viết (mặc dù trong Java so với C #). Đây VeracodeServiceImplTestthực sự là một lớp Kiểm tra đơn vị. Các @Injectabletrường về cơ bản là các đối tượng giả được chèn vào ngữ cảnh. Các @Testedlĩnh vực là đối tượng / lớp với DI Constructor xác định. Tôi đồng ý với quan điểm của bạn rằng thích tiêm Con Contor hơn tiêm tại hiện trường. Tuy nhiên, như tôi đã đề cập, tác giả JMockit cảm thấy điều ngược lại và tôi đang cố gắng hiểu tại sao
Eric B.

Nếu bạn nhìn vào bài đăng đầu tiên trong cuộc thảo luận đó, bạn sẽ thấy tôi gần như giống hệt nhau trong lớp Repo của mình.
Eric B.

Nếu bạn chỉ nói về các lớp kiểm tra, nó có thể không quan trọng. Bởi vì, lớp kiểm tra.
Robert Harvey

Không - tôi không nói về các lớp kiểm tra. Tôi đang nói về các lớp sản xuất. Nó chỉ xảy ra rằng ví dụ trong nhóm thảo luận là một thử nghiệm đơn vị, vì khung là một khung thử nghiệm / thử nghiệm. (Toàn bộ cuộc thảo luận xoay quanh nếu khung mô phỏng có hỗ trợ tiêm xây dựng)
Eric B.

3
+1: Có, không có phép thuật trong phiên bản C #. Đó là một lớp tiêu chuẩn, nó có các phụ thuộc, tất cả chúng đều được tiêm tại một thời điểm, trước khi lớp được sử dụng. Lớp này có thể được sử dụng với một thùng chứa DI hoặc không. Không có gì trong lớp nói nó là khung IOC hoặc "Tự động phụ thuộc tự động".
Nhị phân nhị phân 30/10/2015

27

Đối số của bo boa khởi tạo thử nghiệm ít hơn là hợp lệ, nhưng có những mối quan tâm khác phải được tính đến. Trước hết, bạn phải trả lời một câu hỏi:

Tôi có muốn lớp học của tôi chỉ có thể thực hiện được với sự phản chiếu không?

Sử dụng phép tiêm trường có nghĩa là thu hẹp khả năng tương thích của một lớp với các môi trường tiêm phụ thuộc để khởi tạo các đối tượng bằng cách sử dụng sự phản chiếu và hỗ trợ các chú thích tiêm cụ thể này. Một số nền tảng dựa trên ngôn ngữ Java thậm chí không hỗ trợ phản chiếu ( GWT ), do đó, lớp được tiêm vào trường sẽ không tương thích với chúng.

Vấn đề thứ hai là hiệu suất . Một lệnh gọi hàm tạo (trực tiếp hoặc bằng phản xạ) luôn nhanh hơn một loạt các phép gán trường phản xạ. Các khung tiêm phụ thuộc phải sử dụng phân tích phản xạ để xây dựng cây phụ thuộc và tạo các hàm tạo phản xạ. Quá trình này gây ra hiệu suất bổ sung.

Hiệu suất ảnh hưởng đến khả năng bảo trì . Nếu mỗi bộ thử nghiệm phải được chạy trong một số loại thùng chứa phụ thuộc, một lần chạy thử vài nghìn đơn vị thử nghiệm có thể kéo dài hàng chục phút. Tùy thuộc vào kích thước của một cơ sở mã, đây có thể là một vấn đề.

Tất cả điều này đặt ra nhiều câu hỏi mới:

  1. Xác suất sử dụng các phần của mã trên nền tảng khác là gì?
  2. Có bao nhiêu bài kiểm tra đơn vị sẽ được viết?
  3. Tôi cần chuẩn bị nhanh như thế nào cho mỗi bản phát hành mới?
  4. ...

Nói chung, một dự án càng lớn và càng quan trọng thì những yếu tố này càng có ý nghĩa. Ngoài ra, về chất lượng, chúng tôi thường muốn giữ cho mã cao về tính tương thích, khả năng kiểm tra và khả năng bảo trì. Từ quan điểm triết học, tiêm chích phá vỡ đóng gói , là một trong bốn nguyên tắc cơ bản của lập trình hướng đối tượng , là mô hình chính của Java.

Nhiều, nhiều lập luận chống lại tiêm hiện trường.


3
+1 Bạn đánh một dây thần kinh. Tôi không thích cần sự phản chiếu hoặc khung DI / IoC để khởi tạo một lớp. Nó giống như lái xe đến Rome để đến Paris từ Amsterdam. Tâm trí bạn tôi yêu DI. Chỉ không chắc chắn về các khung.
Marjan Venema

1
Những lý lẽ tuyệt vời. Nó diễn đạt rất nhiều suy nghĩ mà tôi có cho mình nhưng tôi rất vui khi thấy những người khác cũng cảm thấy như vậy. Thật tuyệt vời như tôi đã từng tìm thấy tiêm thực địa, tôi nghĩ rằng tôi yêu nó rất đơn giản bởi vì nó "dễ dàng hơn". Tôi thấy tiêm constructor bây giờ rõ ràng hơn nhiều.
Eric B.

19

Lĩnh vực tiêm nhận được một phiếu "Không" nhất định từ tôi.

Giống như Robert Harvey, nó hơi quá tự động đối với sở thích của tôi. Tôi thích mã rõ ràng hơn ẩn và chỉ chấp nhận sự gián tiếp vì / khi nó cung cấp lợi ích rõ ràng vì nó làm cho mã khó hiểu và lý do hơn.

Giống như Maciej Chałapuk, tôi không muốn cần sự phản chiếu hoặc khung DI / IoC để khởi tạo một lớp. Nó giống như lái xe đến Rome để đến Paris từ Amsterdam.

Tâm trí bạn tôi yêu DI và tất cả những lợi thế nó mang lại. Chỉ không chắc chắn về việc cần các khung công tác để khởi tạo một lớp. Đặc biệt không phải là hiệu ứng mà các container IoC hoặc các khung DI khác có thể có trên mã kiểm tra.

Tôi thích các bài kiểm tra của tôi rất đơn giản. Tôi không muốn phải trải qua việc thiết lập các bộ chứa IoC hoặc khung DI khác để sau đó chúng thiết lập lớp của tôi đang thử nghiệm. Nó chỉ cảm thấy khó xử.

Và nó làm cho các bài kiểm tra của tôi phụ thuộc vào trạng thái toàn cầu của khung. Điều đó có nghĩa là tôi không còn có thể chạy song song hai bài kiểm tra mà yêu cầu một thể hiện của lớp X được thiết lập với các phụ thuộc khác nhau.

Ít nhất là với hàm tạo và hàm setter, bạn có tùy chọn không sử dụng các khung và / hoặc phản xạ trong các thử nghiệm của mình.


Ví dụ: nếu bạn sử dụng Mockito mockito.org , bạn không cần khung DI / IoC ngay cả khi sử dụng phép tiêm trường và có thể chạy thử nghiệm song song và vv.
Hans-Peter Störr

1
@hstoerr Đẹp. Tuy nhiên, điều đó phải có nghĩa là Mockito đang sử dụng sự phản chiếu (hoặc bất cứ thứ gì tương đương với Java được gọi là) ...
Marjan Venema

5

Vâng, điều này có vẻ như một cuộc thảo luận chính trị. Điều đầu tiên cần được giải quyết là tiêm Field khác với tiêm Setter . Tôi đã làm việc với một số người nghĩ rằng tiêm Field và Setter là giống nhau.

Vì vậy, để rõ ràng:

Lĩnh vực tiêm:

@Autowired
private SomeService someService;

Tiêm Setter:

@Autowired
public void setSomeService(SomeService someService) {
    this.someService = someService;
}

Nhưng, đối với tôi, họ có chung mối quan tâm. Và, yêu thích của tôi, tiêm constructor:

@AutoWired
public MyService(SomeService someService) {
    this.someService = someService;
}

Tôi có các lý do sau để tin rằng tiêm chích xây dựng tốt hơn tiêm setter / trường (tôi sẽ trích dẫn một số liên kết Spring để chứng minh quan điểm của tôi):

  1. Ngăn chặn sự phụ thuộc vòng tròn : Spring có thể phát hiện sự phụ thuộc vòng tròn giữa các Đậu, như @Tomasz giải thích. Xem thêm Đội mùa xuân nói rằng.
  2. Phụ thuộc của lớp là hiển nhiên : Các phụ thuộc của lớp là hiển nhiên trong các nhà xây dựng nhưng hided với lĩnh vực tiêm / setter. Tôi cảm thấy các lớp học của tôi giống như một "hộp đen" với trường tiêm / setter. Và có (theo kinh nghiệm của tôi) có xu hướng các nhà phát triển không bận tâm về lớp có nhiều phụ thuộc, điều đó là hiển nhiên khi bạn cố gắng giả định lớp bằng cách sử dụng hàm tạo (xem mục tiếp theo). Một vấn đề làm phiền các nhà phát triển rất có thể sẽ được giải quyết. Ngoài ra, một lớp có quá nhiều phụ thuộc có thể vi phạm Nguyên tắc Trách nhiệm duy nhất (SRP).
  3. Dễ dàng và đáng tin cậy để giả : so với Field Field , việc thử nghiệm đơn vị dễ dàng hơn, bởi vì bạn không cần bất kỳ kỹ thuật giả lập bối cảnh hoặc phản xạ nào để tiếp cận Bean riêng được khai báo trong lớp và bạn sẽ không bị lừa bởi nó . Bạn chỉ cần khởi tạo lớp và vượt qua các hạt đậu. Đơn giản để hiểu và dễ dàng.
  4. Các công cụ chất lượng mã có thể giúp bạn : Các công cụ như Sonar có thể cảnh báo bạn về việc lớp quá phức tạp, bởi vì một lớp có nhiều tham số có lẽ là một lớp quá phức tạp và cần một số bộ tái cấu trúc. Công cụ này không thể xác định cùng một vấn đề khi lớp đang sử dụng phép tiêm Field / Setter.
  5. Mã tốt hơn và độc lập : Với ít @AutoWiredsự lan truyền giữa các mã của bạn, mã của bạn ít phụ thuộc vào khung hơn. Tôi biết rằng khả năng đơn giản thay đổi khung DI trong một dự án không chứng minh điều đó (ai làm điều đó, phải không?). Nhưng điều này cho thấy, đối với tôi, một mã tốt hơn (tôi đã học được điều này một cách khó khăn với EJB và tra cứu). Và với Spring, bạn thậm chí có thể loại bỏ @AutoWiredchú thích bằng cách sử dụng hàm tạo.
  6. Nhiều chú thích không phải lúc nào cũng là câu trả lời đúng : một số lý do , tôi thực sự cố gắng chỉ sử dụng chú thích khi chúng thực sự hữu ích.
  7. Bạn có thể làm cho tiêm bất biến . Sự bất biến là một tính năng rất được hoan nghênh .

Nếu bạn đang tìm kiếm thêm thông tin, tôi khuyên bạn nên sử dụng bài viết cũ (nhưng vẫn có liên quan) này từ Spring Blog cho chúng tôi biết lý do tại sao họ sử dụng tiêm setter nhiều như vậy và khuyến nghị sử dụng tiêm constructor:

tiêm setter được sử dụng thường xuyên hơn bạn mong đợi, thực tế là các khung như Spring nói chung, phù hợp hơn nhiều để được cấu hình bằng cách tiêm setter so với tiêm constructor

Và lưu ý này về lý do tại sao họ tin rằng hàm tạo phù hợp hơn với mã ứng dụng:

Tôi nghĩ rằng tiêm constructor có thể sử dụng nhiều hơn cho mã ứng dụng so với mã khung. Trong mã ứng dụng, bạn vốn có nhu cầu ít hơn đối với các giá trị tùy chọn mà bạn cần định cấu hình

Suy nghĩ cuối cùng

Nếu bạn không thực sự chắc chắn về lợi thế của hàm tạo, có thể ý tưởng trộn setter (không phải trường) với hàm tạo của hàm tạo là một tùy chọn, như được giải thích bởi nhóm Spring :

Vì bạn có thể kết hợp cả hai, DI dựa trên Trình xây dựng và Setter, nên sử dụng các đối số của hàm tạo cho các phụ thuộc bắt buộc và setters cho các phụ thuộc tùy chọn

Nhưng hãy nhớ rằng tiêm hiện trường, có lẽ, là một trong những điều cần tránh trong số ba.


-5

Là sự phụ thuộc của bạn có khả năng thay đổi trong suốt thời gian của lớp học? Nếu vậy sử dụng tiêm lĩnh vực / tài sản. Nếu không sử dụng tiêm xây dựng.


2
Điều này không thực sự giải thích bất cứ điều gì. Tại sao phải tiêm các lĩnh vực riêng tư khi bạn có thể làm điều đó một cách thích hợp thay thế?
Robert Harvey

Nó nói gì về lĩnh vực tư nhân? Tôi đang nói về giao diện chung của một lớp /
Darren Young

Không có tranh luận về phương pháp tiêm so với phương pháp tiêm xây dựng trong câu hỏi. Tác giả của JMockit thấy cả hai đều xấu. Nhìn vào tiêu đề của câu hỏi.
Robert Harvey

Không phải câu hỏi nói tiêm trường so với tiêm xây dựng?
Darren Young

1
Lĩnh vực tiêm là một động vật hoàn toàn khác nhau. Bạn đang chọc các giá trị vào các thành viên tư nhân .
Robert Harvey
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.