2009-10-13 81 views
215

Cả hai mẫu có vẻ giống như việc thực hiện nguyên tắc đảo ngược kiểm soát. Đó là, một đối tượng không nên biết cách xây dựng các phụ thuộc của nó.Sự khác nhau giữa các mẫu Dependency Injection và Service Locator là gì?

Tiêm phụ thuộc (DI) có vẻ như sử dụng một hàm tạo hoặc setter để "tiêm" phụ thuộc của nó.

Ví dụ về sử dụng Constructor Injection:

//Foo Needs an IBar 
public class Foo 
{ 
    private IBar bar; 

    public Foo(IBar bar) 
    { 
    this.bar = bar; 
    } 

    //... 
} 

Service Locator dường như sử dụng một "container", mà dây lên phụ thuộc của nó và cung cấp cho foo bar của nó.

Ví dụ về cách sử dụng một Locator Service:

//Foo Needs an IBar 
public class Foo 
{ 
    private IBar bar; 

    public Foo() 
    { 
    this.bar = Container.Get<IBar>(); 
    } 

    //... 
} 

Bởi vì phụ thuộc của chúng tôi chỉ là các đối tượng chính họ, những phụ thuộc có phụ thuộc, trong đó có nhiều hơn phụ thuộc, và vân vân và vân vân. Do đó, Inversion of Control Container (hoặc DI Container) được sinh ra. Ví dụ: Lâu đài Windsor, Ninject, Bản đồ Cấu trúc, Mùa xuân, v.v ...

Nhưng Hộp chứa IOC/DI trông giống chính xác như một Dịch vụ Định vị. Có gọi nó là DI Container không? Là một IOC/DI Container chỉ khác loại của Dịch vụ định vị? Sắc thái trong thực tế là chúng ta sử dụng DI Containers chủ yếu là khi chúng ta có nhiều Dependencies?

+11

Đảo ngược kiểm soát có nghĩa là "một đối tượng không nên biết cách xây dựng các phụ thuộc của nó"?!? Cái đó mới mẻ với tôi. Không, thực sự, đó không phải là những gì "đảo ngược kiểm soát" có nghĩa là. Xem http://martinfowler.com/bliki/InversionOfControl.html Bài viết đó thậm chí còn cung cấp các tham chiếu cho từ nguyên của thuật ngữ, có niên đại từ những năm 1980. –

+1

Trả lời tại đây: http://www.infoq.com/articles/Succeeding-Dependency-Injection –

+0

Mark Seemann lập luận Service Locator là anti-pattern (http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti- Mẫu/). Ngoài ra, tôi tìm thấy sơ đồ (tìm thấy ở đây, http://stackoverflow.com/a/9503612/1977871) hữu ích để hiểu tình trạng khó khăn DI và SL. Hi vọng điêu nay co ich. – VivekDev

Trả lời

67

Khi bạn sử dụng một bộ định vị dịch vụ, mỗi lớp sẽ có một sự phụ thuộc vào bộ định vị dịch vụ của bạn. Đây không phải là trường hợp với tiêm phụ thuộc. Bộ phun phụ thuộc thường sẽ được gọi chỉ một lần khi khởi động để tiêm phụ thuộc vào một số lớp chính. Các lớp này lớp chính phụ thuộc vào sẽ đệ quy có phụ thuộc của họ tiêm, cho đến khi bạn có một đồ thị đối tượng hoàn chỉnh.

Một so sánh tốt: http://martinfowler.com/articles/injection.html

Nếu phun phụ thuộc của bạn trông giống như một bộ định vị dịch vụ, nơi mà các lớp gọi là phun trực tiếp, nó có lẽ không phải là một vòi phun phụ thuộc, mà là một định vị dịch vụ.

+12

Nhưng làm cách nào bạn xử lý trường hợp bạn phải tạo đối tượng trong thời gian chạy? Nếu bạn tạo chúng bằng tay với "mới", bạn không thể sử dụng DI. Nếu bạn gọi khung DI để được trợ giúp, bạn sẽ phá vỡ mẫu. Vì vậy, các tùy chọn còn lại là gì? – Boris

+4

@Boris Tôi đã có cùng một vấn đề và quyết định đưa các nhà máy cụ thể vào lớp. Không đẹp nhưng đã hoàn thành công việc. Rất thích nhìn thấy một giải pháp đẹp hơn. –

+0

Liên kết trực tiếp để so sánh: http://martinfowler.com/articles/injection.html#ServiceLocatorVsDependencyInjection –

128

Sự khác biệt có vẻ hơi nhỏ, nhưng ngay cả với ServiceLocator, lớp vẫn chịu trách nhiệm tạo phụ thuộc của nó. Nó chỉ sử dụng định vị dịch vụ để làm điều đó. Với DI, lớp được cho là phụ thuộc của nó. Nó không biết, cũng không quan tâm họ đến từ đâu. Một kết quả quan trọng của việc này là ví dụ DI dễ dàng hơn nhiều trong việc kiểm thử đơn vị - bởi vì bạn có thể vượt qua nó để thử nghiệm việc triển khai các đối tượng phụ thuộc của nó. Bạn có thể kết hợp cả hai - và tiêm trình định vị dịch vụ (hoặc một nhà máy), nếu bạn muốn.

+16

Ngoài ra, bạn có thể sử dụng cả hai khi xây dựng một lớp.Hàm khởi tạo mặc định có thể sử dụng SL để lấy các phụ thuộc và chuyển chúng tới hàm tạo "thực" mà nhận các phụ thuộc đó. Bạn nhận được tốt nhất của cả hai thế giới. –

+3

Không, ServiceLocator là người chịu trách nhiệm khởi tạo việc triển khai chính xác cho một phụ thuộc nhất định (plugin). Trong trường hợp DI, DI "container" là người chịu trách nhiệm cho điều đó. –

+3

@Rogerio có nhưng lớp vẫn phải biết về Service Locator ... đó là hai sự thiếu hụt. Cũng thường xuyên hơn là tôi đã không thấy Service Locator ủy quyền cho vùng chứa DI để tra cứu đặc biệt cho các đối tượng tạm thời cần hỗ trợ dịch vụ. –

35

Dịch vụ định vị ẩn phụ thuộc - bạn không thể biết bằng cách nhìn vào một đối tượng cho dù nó truy cập cơ sở dữ liệu hay không (ví dụ) khi nó nhận được kết nối từ một bộ định vị. Với tiêm phụ thuộc (ít nhất là constructor injection) các phụ thuộc là rõ ràng.

Hơn nữa, các trình định vị dịch vụ đóng gói đóng gói vì chúng cung cấp một điểm truy cập toàn cầu cho các phụ thuộc của các đối tượng khác.Với định vị dịch vụ, as with any singleton:

nó trở nên khó khăn để xác định trước và sau điều kiện cho giao diện đối tượng khách hàng, vì các hoạt động thực hiện của nó có thể được meddled với từ bên ngoài.

Với phụ thuộc tiêm, khi phụ thuộc của đối tượng được chỉ định, chúng nằm dưới sự kiểm soát của đối tượng.

+2

Tôi thích "Singletons coi là ngu ngốc", http://steve.yegge.googlepages.com/singleton-considered-stupid –

+2

Tôi yêu ol 'Steve Yegge và tiêu đề của bài viết đó là tuyệt vời, nhưng tôi nghĩ rằng bài viết tôi trích dẫn và "Singletons" của Miško Hevery là những kẻ nói dối về bệnh lý "(http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/) tạo ra một trường hợp tốt hơn chống lại sự ma quỷ đặc biệt của người định vị dịch vụ. –

17

Một lớp sử dụng hàm tạo DI chỉ ra mã tiêu thụ có phụ thuộc để được thỏa mãn. Nếu lớp sử dụng SL nội bộ để lấy các phụ thuộc như vậy, mã tiêu thụ không nhận thức được các phụ thuộc. Điều này có thể trên bề mặt có vẻ tốt hơn, nhưng nó thực sự là hữu ích để biết về bất kỳ phụ thuộc rõ ràng. Nó là tốt hơn từ một cái nhìn kiến ​​trúc. Và khi thực hiện kiểm tra, bạn phải biết liệu một lớp có cần các phụ thuộc nhất định hay không, và cấu hình SL để cung cấp các phiên bản giả thích hợp của các phụ thuộc đó. Với DI, chỉ cần vượt qua trong hàng giả. Không phải là một sự khác biệt lớn, nhưng nó ở đó.

DI và SL có thể hoạt động cùng nhau. Sẽ hữu ích khi có vị trí trung tâm cho các phụ thuộc chung (ví dụ: cài đặt, trình ghi nhật ký, v.v.). Cho một lớp bằng cách sử dụng deps như vậy, bạn có thể tạo một constructor "thực" nhận được deps, và một constructor mặc định (không tham số) lấy ra từ SL và chuyển tiếp đến constructor "thực".

EDIT: và, tất nhiên, khi bạn sử dụng SL, bạn đang giới thiệu một số khớp nối với thành phần đó. Thật mỉa mai, vì ý tưởng về chức năng như vậy là để khuyến khích trừu tượng hóa và giảm khớp nối. Các mối quan tâm có thể được cân bằng, và nó phụ thuộc vào bao nhiêu nơi bạn sẽ cần phải sử dụng SL. Nếu thực hiện như đã đề xuất ở trên, chỉ trong constructor lớp mặc định.

+0

Thú vị! Tôi đang sử dụng cả DI và SL, nhưng không phải với hai nhà xây dựng. Ba hoặc bốn phụ thuộc nhàm chán thường xuyên nhất cần thiết (cài đặt, v.v ...)) nhận được từ SL khi đang bay. Mọi thứ khác được xây dựng-tiêm. Đó là một chút xấu xí, nhưng thực dụng. – maaartinus

5

Tôi nghĩ cả hai làm việc cùng nhau.

Chèn phụ thuộc có nghĩa là bạn đẩy một số lớp/giao diện phụ thuộc vào một lớp tiêu thụ (thường là với hàm tạo của nó). Điều này tách riêng hai lớp thông qua một giao diện và có nghĩa là lớp tiêu thụ có thể làm việc với nhiều kiểu triển khai "phụ thuộc được tiêm".

Vai trò của công cụ định vị dịch vụ là để cùng nhau triển khai. Bạn thiết lập một định vị dịch vụ thông qua một số khởi động đóng đai vào lúc bắt đầu chương trình của bạn. Bootstrapping là quá trình kết hợp một kiểu triển khai với một giao diện/trừu tượng cụ thể. Mà được tạo ra cho bạn tại thời gian chạy. (dựa trên cấu hình của bạn hoặc bootstrap). Nếu bạn đã không thực hiện tiêm phụ thuộc, nó sẽ rất khó khăn để sử dụng một định vị dịch vụ hoặc container IOC.

4

Lưu ý: Tôi không trả lời chính xác câu hỏi. Nhưng tôi cảm thấy rằng điều này có thể hữu ích cho những người học mới của mô hình Dependency Injection, người đang bối rối về nó với Service Locator (anti-)pattern người tình cờ gặp phải trang này.

Tôi biết sự khác biệt giữa Service Locator (nó có vẻ được coi là anti-pattern now) và Dependency Injection patterns và có thể hiểu các ví dụ cụ thể cho từng mẫu, tuy nhiên tôi đã nhầm lẫn với các ví dụ hiển thị một bộ định vị dịch vụ bên trong constructor (giả sử chúng tôi đang thực hiện tiêm constructor).

"Bộ định vị dịch vụ" thường được sử dụng làm tên của mẫu và cũng như tên để chỉ đối tượng (giả sử) được sử dụng trong mẫu đó để lấy đối tượng mà không sử dụng toán tử mới. Bây giờ, cùng một loại đối tượng cũng có thể được sử dụng tại composition root để thực hiện tiêm phụ thuộc, và đó là nơi mà sự nhầm lẫn xuất hiện.

Điểm đáng lưu ý là bạn có thể đang sử dụng đối tượng định vị dịch vụ bên trong một hàm tạo DI, nhưng bạn không sử dụng "Mẫu định vị dịch vụ". Nó ít gây nhầm lẫn nếu một trong những đề cập nó như là một đối tượng container IoC thay vào đó, như bạn có thể đoán rằng họ về cơ bản làm điều tương tự (làm đúng cho tôi nếu tôi sai).

Cho dù nó được gọi là một định vị dịch vụ (hoặc chỉ định vị), hoặc như một container IoC (hoặc chỉ container) làm cho không có sự khác biệt như bạn đã đoán vì chúng có thể đề cập đến cùng một abstraction (làm chính xác cho tôi nếu Tôi đã sai). Nó chỉ là gọi nó là một dịch vụ định vị cho thấy rằng một trong những đang sử dụng Service Locator anti-pattern cùng với Dependency Injection pattern.

IMHO, đặt tên là 'định vị' thay vì 'vị trí' hoặc 'định vị', đôi khi có thể khiến người định vị dịch vụ trong một bài viết đề cập đến vùng chứa Dịch vụ định vị chứ không phải Service Locator (anti-) mô hình, đặc biệt là khi có một mô hình liên quan được gọi là Dependency Injection và không phải Dependency Injector.

21

Martin Fowler nói:

Với dịch vụ định vị lớp ứng dụng yêu cầu nó một cách rõ ràng bằng một thông điệp nơi tìm địa điểm. Với tiêm không có yêu cầu rõ ràng, dịch vụ xuất hiện trong lớp ứng dụng - do đó nghịch đảo của điều khiển .

Tóm lại: Dịch vụ Định vị và phụ thuộc Tiêm chỉ là triển khai Nguyên tắc đảo ngược phụ thuộc.

Nguyên tắc quan trọng là "Phụ thuộc vào trừu tượng, chứ không phải dựa trên các concretions". Điều này sẽ làm cho thiết kế phần mềm của bạn "lỏng lẻo", "mở rộng", "linh hoạt".

Bạn có thể sử dụng loại phù hợp nhất với nhu cầu của mình. Đối với một ứng dụng lớn, có một codebase lớn, bạn nên sử dụng một Service Locator, bởi vì Dependency Injection sẽ đòi hỏi nhiều thay đổi hơn cho codebase của bạn.

Bạn có thể kiểm tra bài đăng này: Dependency Inversion: Service Locator or Dependency Injection

Ngoài ra cổ điển: Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler

Designing Reusable Classes bởi Ralph E. Johnson & Brian Foote

Tuy nhiên, một trong đó mở mắt của tôi là: ASP.NET MVC: Resolve or Inject? That’s the Issue… by Dino Esposito

+0

Tóm tắt tuyệt vời: "Dịch vụ định vị và phụ thuộc tiêm chỉ là việc thực hiện nguyên tắc đảo ngược phụ thuộc." – Hans

+0

Và ông cũng nêu rõ: Sự khác biệt chính là với một Service Locator mỗi người sử dụng dịch vụ có một phụ thuộc vào định vị. Các định vị có thể ẩn phụ thuộc vào triển khai khác, nhưng bạn cần phải xem định vị. Vì vậy, quyết định giữa định vị và kim phun phụ thuộc vào việc sự phụ thuộc đó có phải là một vấn đề hay không. – programaths

+1

ServiceLocator và DI không liên quan gì đến "Nguyên tắc Inversion phụ thuộc" (DIP). DIP là một cách để làm cho một thành phần mức cao có thể tái sử dụng được, bằng cách thay thế một phụ thuộc thời gian biên dịch trên một thành phần mức thấp với sự phụ thuộc vào một kiểu trừu tượng được định nghĩa cùng với thành phần mức cao. thành phần cấp; theo cách này, sự phụ thuộc thời gian biên dịch được đảo ngược, vì bây giờ nó phụ thuộc vào cấp độ thấp phụ thuộc vào cấp cao nhất. Ngoài ra, lưu ý rằng bài viết của Martin Fowler giải thích rằng DI và IoC là * không * cùng một điều. –

4

Một lý do để thêm vào, lấy cảm hứng từ bản cập nhật tài liệu mà chúng tôi đã viết cho dự án MEF tuần trước (tôi giúp xây dựng MEF).

Khi ứng dụng được tạo thành từ hàng nghìn thành phần có khả năng, có thể khó xác định xem có thể khởi tạo chính xác thành phần cụ thể nào không. Bằng cách "khởi tạo một cách chính xác", tôi có nghĩa là trong ví dụ này dựa trên các thành phần Foo, một thể hiện của IBar và sẽ có mặt, và rằng các thành phần cung cấp nó sẽ:

  • có phụ thuộc yêu cầu của nó,
  • không tham gia vào bất kỳ chu kỳ phụ thuộc không hợp lệ nào và
  • trong trường hợp của MEF, chỉ được cung cấp với một trường hợp.

Trong ví dụ thứ hai bạn đã cho, nơi các nhà xây dựng đi vào container IoC để lấy phụ thuộc của nó, cách duy nhất mà bạn có thể kiểm tra rằng một thể hiện của Foo sẽ có thể được khởi tạo một cách chính xác với thời gian chạy thực tế cấu hình ứng dụng của bạn là để thực sự xây dựng nó.

Điều này có tất cả các loại tác dụng phụ khó xử tại thời điểm thử nghiệm, bởi vì mã sẽ hoạt động trong thời gian chạy sẽ không nhất thiết phải hoạt động dưới sự khai thác thử nghiệm. Mocks sẽ không làm, bởi vì cấu hình thực sự là thứ chúng ta cần kiểm tra, chứ không phải một số thiết lập thời gian thử nghiệm.

Gốc của vấn đề này là sự khác biệt đã được gọi bởi @Jon: việc tiêm phụ thuộc thông qua hàm tạo là khai báo, trong khi phiên bản thứ hai sử dụng mẫu Service Locator bắt buộc.

Thùng chứa IoC, khi được sử dụng cẩn thận, có thể phân tích tĩnh cấu hình thời gian chạy của ứng dụng mà không thực sự tạo bất kỳ phiên bản thành phần nào có liên quan. Nhiều container phổ biến cung cấp một số biến thể của điều này; Microsoft.Composition, là phiên bản của MEF nhắm mục tiêu .NET 4.5 web và ứng dụng kiểu Metro, cung cấp mẫu CompositionAssert trong tài liệu wiki. Sử dụng nó, bạn có thể viết mã như:

// Whatever you use at runtime to configure the container 
var container = CreateContainer(); 

CompositionAssert.CanExportSingle<Foo>(container); 

(Xem this example).

Bằng cách xác minh Gốc thành phần ứng dụng của bạn tại thời điểm thử nghiệm, bạn có thể có khả năng phát hiện một số lỗi có thể bị trượt qua kiểm tra sau này trong quá trình.

Hy vọng đây là một bổ sung thú vị cho tập hợp câu trả lời khác toàn diện về chủ đề này!

3

Trong trường hợp quá khổ này, không có sự khác biệt và chúng có thể được sử dụng thay thế cho nhau. Tuy nhiên, các vấn đề thực tế không đơn giản. Chỉ cần giả định rằng chính lớp Bar có một phụ thuộc khác tên là D. Trong trường hợp đó, trình định vị dịch vụ của bạn sẽ không thể giải quyết sự phụ thuộc đó và bạn sẽ phải khởi tạo nó trong lớp D; bởi vì đó là trách nhiệm của các lớp học của bạn để khởi tạo các phụ thuộc của chúng. Nó thậm chí sẽ tồi tệ hơn nếu bản thân lớp D có những phụ thuộc khác và trong các tình huống thực tế, nó thường trở nên phức tạp hơn thế. Trong kịch bản như vậy DI là một giải pháp tốt hơn ServiceLocator.

+4

Hmm Tôi không đồng ý: định vị dịch vụ cũ. rõ ràng cho thấy rằng vẫn còn một sự phụ thuộc ở đó ... định vị dịch vụ. Nếu chính lớp 'bar' có một sự phụ thuộc, thì' bar' cũng sẽ có định vị dịch vụ, đó là toàn bộ điểm của việc sử dụng DI/IoC. – GFoley83

3

Cả hai đều là kỹ thuật triển khai IoC. Ngoài ra còn có rất nhiều bằng chứng khác mà thực hiện Inversion of Control:

  • mô hình nhà máy
  • dịch vụ định vị
  • dependency injection (constructor injection, thông số phun (nếu không bắt buộc), setter injection tiêm interface) . ..

Trình định vị dịch vụ và DI có vẻ tương tự hơn, cả hai đều sử dụng vùng chứa để xác định các phụ thuộc, ánh xạ trừu tượng cho việc triển khai cụ thể.

Sự khác biệt chính là cách phụ thuộc được đặt, trong mã dịch vụ vị trí dịch vụ yêu cầu phụ thuộc, trong DI chúng tôi sử dụng vùng chứa để tạo tất cả đối tượng và nó phụ thuộc vào tham số của hàm tạo (hoặc thuộc tính).

4

Trong dự án cuối cùng của tôi, tôi sử dụng cả hai. Tôi sử dụng tiêm phụ thuộc để kiểm tra đơn vị. Tôi sử dụng dịch vụ định vị để ẩn thực hiện và đang phụ thuộc vào container IoC của tôi. và vâng! Một khi bạn sử dụng một trong các container IoC (Unity, Ninject, Windsor Castle), bạn phụ thuộc vào nó. Và một khi nó đã lỗi thời hoặc vì một lý do nào đó nếu bạn muốn trao đổi nó, bạn sẽ/có thể cần thay đổi việc triển khai của bạn - ít nhất là thành phần gốc. Nhưng dịch vụ định vị tóm tắt giai đoạn đó.

Làm thế nào bạn sẽ không phụ thuộc vào container IoC của bạn? Hoặc bạn sẽ cần phải quấn nó của riêng bạn (đó là một ý tưởng tồi) hoặc bạn sử dụng Service Locator cấu hình container IoC của bạn. Vì vậy, bạn sẽ nói với dịch vụ định vị để có được giao diện mà bạn cần, và nó sẽ gọi IoC container cấu hình để lấy giao diện đó.

Trong trường hợp của tôi, tôi sử dụng ServiceLocator là thành phần khung. Và sử dụng Unity cho IoC container. Nếu trong tương lai tôi cần phải trao đổi container IoC của tôi đến Ninject tất cả những gì tôi cần làm là tôi cần phải cấu hình định vị dịch vụ của tôi để sử dụng Ninject thay vì Unity. Dễ dàng di chuyển.

Đây là một bài viết tuyệt vời giải thích kịch bản này; http://www.johandekoning.nl/index.php/2013/03/03/dont-wrap-your-ioc-container/

+0

Liên kết bài viết johandekoning bị hỏng. – JakeJ

1

Sự khác biệt (nếu có) giữa Dependency Injection và Service Locator là gì? Cả hai mẫu đều tốt trong việc triển khai nguyên tắc Nghiện về phụ thuộc. Mẫu Service Locator dễ sử dụng hơn trong một codebase hiện tại vì nó làm cho thiết kế tổng thể lỏng lẻo hơn mà không phải thay đổi giao diện công cộng. Cũng vì lý do này, mã dựa trên mẫu Service Locator ít có thể đọc được hơn mã tương đương dựa trên Dependency Injection.

Mẫu Dependency Injection làm cho nó rõ ràng vì chữ ký phụ thuộc vào lớp (hoặc phương thức) sẽ có. Vì lý do này, mã kết quả là sạch hơn và dễ đọc hơn.

Các vấn đề liên quan