Phải tiêm Dependency phải trả chi phí của Encapsulation?


128

Nếu tôi hiểu chính xác, cơ chế điển hình cho Dependency Injection là tiêm thông qua hàm tạo của lớp hoặc thông qua một thuộc tính công cộng (thành viên) của lớp.

Điều này phơi bày sự phụ thuộc được tiêm và vi phạm nguyên tắc đóng gói OOP.

Tôi có đúng trong việc xác định sự đánh đổi này? Làm thế nào để bạn đối phó với vấn đề này?

Xin vui lòng xem câu trả lời của tôi cho câu hỏi của riêng tôi dưới đây.


7
đây là một câu hỏi rất thông minh imho
dfa

5
Trả lời câu hỏi này trước tiên đòi hỏi một lập luận về ý nghĩa của việc đóng gói . ;)
Jeff Sternal

2
Đóng gói được duy trì bởi các giao diện. Họ phơi bày các đặc điểm thiết yếu của đối tượng và ẩn các chi tiết như phụ thuộc. Điều này cho phép chúng tôi 'mở ra' các lớp ở một mức độ nào đó để cung cấp một cấu hình linh hoạt hơn.
Lawrence Wagerfield

Câu trả lời:


62

Có một cách khác để xem xét vấn đề này mà bạn có thể thấy thú vị.

Khi chúng tôi sử dụng IoC / tiêm phụ thuộc, chúng tôi không sử dụng các khái niệm OOP. Phải thừa nhận rằng chúng tôi đang sử dụng ngôn ngữ OO làm 'máy chủ', nhưng các ý tưởng đằng sau IoC đến từ kỹ thuật phần mềm định hướng thành phần, chứ không phải OO.

Phần mềm thành phần là tất cả về việc quản lý các phụ thuộc - một ví dụ được sử dụng phổ biến là cơ chế hội của .NET. Mỗi hội đồng công bố danh sách các hội đồng mà nó tham chiếu và điều này làm cho việc kết hợp (và xác thực) các phần cần thiết cho một ứng dụng đang chạy dễ dàng hơn nhiều.

Bằng cách áp dụng các kỹ thuật tương tự trong các chương trình OO của chúng tôi thông qua IoC, chúng tôi hướng đến việc làm cho các chương trình dễ dàng hơn để cấu hình và bảo trì. Xuất bản phụ thuộc (như tham số hàm tạo hoặc bất cứ điều gì) là một phần quan trọng của điều này. Đóng gói không thực sự được áp dụng, như trong thế giới định hướng thành phần / dịch vụ, không có "loại triển khai" để biết chi tiết rò rỉ từ đó.

Thật không may, ngôn ngữ của chúng tôi hiện không tách biệt các khái niệm hướng đối tượng, hạt mịn khỏi các khái niệm hướng thành phần hạt thô, vì vậy đây là điểm khác biệt mà bạn phải giữ trong đầu :)


18
Đóng gói không chỉ là một phần của thuật ngữ ưa thích. Đó là một điều thực sự với những lợi ích thực sự và không thành vấn đề nếu bạn coi chương trình của mình là "hướng thành phần" hay "hướng đối tượng". Đóng gói được cho là để bảo vệ trạng thái của đối tượng / thành phần / dịch vụ của bạn / bất cứ điều gì khỏi bị thay đổi theo những cách không mong muốn và IoC sẽ lấy đi một số bảo vệ này, vì vậy chắc chắn có một sự đánh đổi.
Ron Inbar

1
Các đối số được cung cấp thông qua một hàm tạo vẫn nằm trong phạm vi của các cách dự kiến mà đối tượng có thể bị "thay đổi": chúng được phơi bày rõ ràng và các bất biến xung quanh chúng được thi hành. Ẩn thông tin là thuật ngữ tốt hơn cho loại quyền riêng tư mà bạn đang đề cập, @RonInbar, và nó không nhất thiết phải luôn có lợi (nó làm cho mì ống khó gỡ rối hơn ;-)).
Nicholas Blumhardt

2
Toàn bộ quan điểm của OOP là mớ mì ống được tách ra thành các lớp riêng biệt và bạn chỉ phải lộn xộn với nó nếu đó là hành vi của lớp cụ thể mà bạn muốn sửa đổi (đây là cách OOP giảm thiểu độ phức tạp). Một lớp (hoặc mô-đun) đóng gói các phần bên trong của nó trong khi hiển thị giao diện công cộng thuận tiện (đây là cách OOP tạo điều kiện tái sử dụng). Một lớp phơi bày sự phụ thuộc của nó thông qua các giao diện tạo ra sự phức tạp cho các máy khách của nó và kết quả là ít tái sử dụng hơn. Nó cũng vốn đã mong manh hơn.
Neutrino

1
Cho dù tôi nhìn nó như thế nào, đối với tôi, DI dường như làm suy yếu nghiêm trọng một số lợi ích quý giá nhất của OOP và tôi chưa gặp phải tình huống mà tôi thực sự thấy nó được sử dụng theo cách giải quyết thực sự vấn đề hiện có.
Neutrino

1
Đây là một cách khác để giải quyết vấn đề: hãy tưởng tượng nếu các hội đồng .NET chọn "đóng gói" và không khai báo các hội đồng khác mà họ dựa vào. Nó sẽ là một tình huống điên rồ, đọc tài liệu và chỉ hy vọng rằng một cái gì đó hoạt động sau khi tải nó. Khai báo các phụ thuộc ở cấp độ đó cho phép công cụ tự động đối phó với thành phần quy mô lớn của ứng dụng. Bạn phải nheo mắt để xem sự tương tự, nhưng các lực tương tự được áp dụng ở cấp thành phần. Có sự đánh đổi và YMMV như mọi khi :-)
Nicholas Blumhardt

29

Đó là một câu hỏi hay - nhưng tại một số điểm, đóng gói ở dạng tinh khiết nhất của nó cần phải bị vi phạm nếu đối tượng đã bao giờ hoàn thành sự phụ thuộc của nó. Một số nhà cung cấp phụ thuộc phải biết cả hai đối tượng trong câu hỏi yêu cầu Foovà nhà cung cấp phải có cách cung cấp Foocho đối tượng.

Về mặt kinh điển, trường hợp sau này được xử lý như bạn nói, thông qua các đối số của hàm tạo hoặc các phương thức setter. Tuy nhiên, điều này không nhất thiết đúng - tôi biết rằng các phiên bản mới nhất của khung công tác Spring DI trong Java, ví dụ, cho phép bạn chú thích các trường riêng (ví dụ với @Autowired) và sự phụ thuộc sẽ được đặt thông qua sự phản chiếu mà bạn không cần phải tiết lộ sự phụ thuộc thông qua bất kỳ lớp nào phương thức công khai / hàm tạo. Đây có thể là loại giải pháp bạn đang tìm kiếm.

Điều đó nói rằng, tôi cũng không nghĩ rằng tiêm chích xây dựng cũng là một vấn đề. Tôi luôn cảm thấy rằng các đối tượng nên hoàn toàn hợp lệ sau khi xây dựng, sao cho mọi thứ chúng cần để thực hiện vai trò của chúng (nghĩa là ở trạng thái hợp lệ) dù sao cũng nên được cung cấp thông qua nhà xây dựng. Nếu bạn có một đối tượng yêu cầu cộng tác viên làm việc, đối với tôi, nhà xây dựng quảng cáo công khai yêu cầu này và đảm bảo nó được đáp ứng khi một thể hiện mới của lớp được tạo ra.

Lý tưởng nhất là khi làm việc với các đối tượng, dù sao bạn cũng tương tác với chúng thông qua một giao diện và bạn càng làm điều này (và có các phụ thuộc được nối qua DI), bạn càng thực sự phải đối phó với các nhà xây dựng. Trong tình huống lý tưởng, mã của bạn không xử lý hoặc thậm chí không bao giờ tạo ra các thể hiện cụ thể của các lớp; do đó, nó chỉ được cung cấp IFoothông qua DI, mà không cần lo lắng về những gì người xây dựng FooImplchỉ ra rằng nó cần phải thực hiện công việc của mình, và trên thực tế mà không hề biết đến FooImplsự tồn tại của nó. Từ quan điểm này, đóng gói là hoàn hảo.

Tất nhiên đây là ý kiến, nhưng theo tôi, DI không nhất thiết vi phạm đóng gói và thực tế có thể giúp nó bằng cách tập trung tất cả các kiến ​​thức cần thiết về nội bộ vào một nơi. Bản thân nó không chỉ là một điều tốt, mà thậm chí nơi này còn nằm ngoài cơ sở mã của riêng bạn, vì vậy không có mã nào bạn viết cần biết về sự phụ thuộc của các lớp.


2
Điểm tốt. Tôi khuyên bạn không nên sử dụng @Autowired trên các lĩnh vực tư nhân; điều đó làm cho lớp khó kiểm tra; Làm thế nào để bạn sau đó tiêm giả hoặc sơ khai?
lumpynose

4
Tôi không đồng ý. DI không vi phạm đóng gói, và điều này có thể tránh được. Ví dụ, bằng cách sử dụng ServiceLocator, rõ ràng không cần biết gì về lớp máy khách; nó chỉ cần biết về việc triển khai phụ thuộc Foo. Nhưng điều tốt nhất, trong hầu hết các trường hợp, chỉ đơn giản là sử dụng toán tử "mới".
Rogério

5
@Rogerio - Có thể cho rằng bất kỳ khung DI nào hoạt động chính xác như ServiceLocator mà bạn mô tả; khách hàng không biết gì cụ thể về việc triển khai Foo và công cụ DI không biết bất cứ điều gì cụ thể về khách hàng. Và việc sử dụng "mới" còn tệ hơn nhiều khi vi phạm đóng gói, vì bạn cần biết không chỉ lớp thực hiện chính xác, mà cả các lớp trường hợp chính xác của tất cả các phụ thuộc mà nó cần.
Andrzej Doyle

4
Sử dụng "mới" để khởi tạo một lớp người trợ giúp, thường không công khai, sẽ thúc đẩy việc đóng gói. Giải pháp thay thế DI sẽ là làm cho lớp trình trợ giúp công khai và thêm một hàm tạo công khai hoặc setter trong lớp máy khách; cả hai thay đổi sẽ phá vỡ sự đóng gói được cung cấp bởi lớp người trợ giúp ban đầu.
Rogério

1
"Đó là một câu hỏi hay - nhưng tại một số điểm, đóng gói ở dạng tinh khiết nhất của nó cần phải bị vi phạm nếu đối tượng đã bao giờ hoàn thành sự phụ thuộc của nó" Tiền đề sáng lập của câu trả lời của bạn chỉ đơn giản là không đúng sự thật. Vì @ Rogério tuyên bố mới về một sự phụ thuộc trong nội bộ và bất kỳ phương pháp nào khác mà một đối tượng bên trong bão hòa các phụ thuộc của chính nó không vi phạm đóng gói.
Neutrino

17

Điều này phơi bày sự phụ thuộc được tiêm và vi phạm nguyên tắc đóng gói OOP.

Vâng, thẳng thắn mà nói, mọi thứ đều vi phạm đóng gói. :) Đó là một loại nguyên tắc đấu thầu phải được đối xử tốt.

Vì vậy, những gì vi phạm đóng gói?

Kế thừa nào .

"Bởi vì tính kế thừa làm lộ ra một lớp con với các chi tiết về việc thực hiện của cha mẹ nó, nên người ta thường nói rằng" sự kế thừa phá vỡ sự đóng gói "". (Gang of Four 1995: 19)

Lập trình hướng theo khía cạnh nào . Ví dụ: bạn đăng ký gọi lại onMethodCall () và điều đó mang đến cho bạn cơ hội tuyệt vời để đưa mã vào đánh giá phương thức bình thường, thêm các hiệu ứng phụ lạ, v.v.

Khai báo bạn bè trong C ++ nào .

Lớp học mở rộng trong Ruby nào . Chỉ cần xác định lại một phương thức chuỗi ở đâu đó sau khi một lớp chuỗi được xác định đầy đủ.

Vâng, rất nhiều thứ không .

Đóng gói là một nguyên tắc tốt và quan trọng. Nhưng không phải chỉ có một.

switch (principle)
{
      case encapsulation:
           if (there_is_a_reason)
      break!
}

3
"Đó là những nguyên tắc của tôi, và nếu bạn không thích chúng ... tốt, tôi có những thứ khác." (Groucho Marx)
Ron Inbar

2
Tôi nghĩ rằng đây là loại điểm. Đó là tiêm phụ thuộc vs đóng gói. Vì vậy, chỉ sử dụng tiêm phụ thuộc nơi nó mang lại lợi ích đáng kể. Đó là DI ở khắp mọi nơi mang lại cho DI một cái tên xấu
Richard Tingle

Không chắc câu trả lời này đang cố nói gì ... Việc vi phạm đóng gói khi thực hiện DI có ổn không, rằng "luôn luôn ổn" vì dù sao nó cũng sẽ bị vi phạm, hoặc đơn giản là DI có thể là lý do để vi phạm đóng gói? Một lưu ý khác, những ngày này không cần phải dựa vào các nhà xây dựng hoặc tài sản công cộng để tiêm phụ thuộc; thay vào đó, chúng ta có thể đưa vào các trường chú thích riêng , đơn giản hơn (ít mã hơn) và bảo toàn việc đóng gói. Vì vậy, chúng ta có thể tận dụng cả hai nguyên tắc cùng một lúc.
Rogério

Kế thừa về nguyên tắc không vi phạm đóng gói, mặc dù nó có thể làm nếu lớp cha mẹ được viết xấu. Các điểm khác mà bạn nêu lên là một mô hình lập trình khá rìa và một số tính năng ngôn ngữ ít liên quan đến kiến ​​trúc hoặc thiết kế.
Neutrino

13

Có, DI vi phạm đóng gói (còn được gọi là "ẩn thông tin").

Nhưng vấn đề thực sự xảy ra khi các nhà phát triển sử dụng nó như một cái cớ để vi phạm các nguyên tắc KISS (Keep It Short and Simple) và YAGNI (You Ain't Gonna Need It).

Cá nhân, tôi thích các giải pháp đơn giản và hiệu quả. Tôi chủ yếu sử dụng toán tử "mới" để khởi tạo các phụ thuộc trạng thái bất cứ khi nào và bất cứ nơi nào chúng cần. Nó là đơn giản, đóng gói tốt, dễ hiểu và dễ kiểm tra. Vậy tại sao không?


Suy nghĩ về phía trước không phải là khủng khiếp, nhưng tôi đồng ý Keep It Simple, St ngu, đặc biệt là nếu bạn không cần nó! Tôi đã thấy các nhà phát triển lãng phí chu kỳ vì họ đang thiết kế một thứ gì đó để trở thành bằng chứng trong tương lai và dựa trên linh cảm về điều đó, thậm chí không có yêu cầu kinh doanh nào được biết / nghi ngờ.
Jacob McKay

5

Một thùng chứa / hệ thống tiêm làm suy giảm tốt sẽ cho phép tiêm constructor. Các đối tượng phụ thuộc sẽ được gói gọn và không cần phải phơi bày công khai. Hơn nữa, bằng cách sử dụng hệ thống DP, không có mã nào của bạn thậm chí "biết" các chi tiết về cách xây dựng đối tượng, thậm chí có thể bao gồm cả đối tượng đang được xây dựng. Trong trường hợp này có nhiều đóng gói hơn vì gần như tất cả các mã của bạn không chỉ được bảo vệ khỏi kiến ​​thức về các đối tượng được đóng gói, mà thậm chí còn không tham gia vào việc xây dựng các đối tượng.

Bây giờ, tôi giả sử bạn đang so sánh với trường hợp đối tượng được tạo sẽ tạo ra các đối tượng được đóng gói riêng, rất có thể trong hàm tạo của nó. Sự hiểu biết của tôi về DP là chúng tôi muốn đưa trách nhiệm này ra khỏi đối tượng và giao nó cho người khác. Cuối cùng, "người khác", là container DP trong trường hợp này, có kiến ​​thức sâu sắc "vi phạm" đóng gói; lợi ích là nó kéo kiến ​​thức đó ra khỏi đối tượng, chính nó. Ai đó phải có nó. Phần còn lại của ứng dụng của bạn thì không.

Tôi sẽ nghĩ về nó theo cách này: Container / hệ thống tiêm phụ thuộc vi phạm đóng gói, nhưng mã của bạn thì không. Trong thực tế, mã của bạn đã được "đóng gói" hơn bao giờ hết.


3
Nếu bạn có một tình huống trong đó đối tượng khách hàng có thể khởi tạo trực tiếp các phụ thuộc của nó, thì tại sao không làm như vậy? Đó chắc chắn là điều đơn giản nhất để làm, và không nhất thiết phải giảm khả năng kiểm tra. Bên cạnh sự đơn giản và đóng gói tốt hơn, điều này cũng giúp dễ dàng có các đối tượng trạng thái thay vì các đơn vị không quốc tịch.
Rogério

1
Thêm vào những gì @ Rogério nói, nó cũng có khả năng hiệu quả hơn đáng kể. Không phải mọi lớp từng được tạo ra trong lịch sử thế giới đều cần có mỗi một phụ thuộc của nó được khởi tạo trong toàn bộ vòng đời của đối tượng sở hữu. Một đối tượng sử dụng DI sẽ mất quyền kiểm soát cơ bản nhất đối với các phụ thuộc của chính nó, cụ thể là thời gian tồn tại của chúng.
Neutrino

5

Như Jeff Sternal đã chỉ ra trong một bình luận cho câu hỏi, câu trả lời hoàn toàn phụ thuộc vào cách bạn xác định đóng gói .

Dường như có hai trại chính về ý nghĩa của việc đóng gói:

  1. Tất cả mọi thứ liên quan đến đối tượng là một phương thức trên một đối tượng. Vì vậy, một Fileđối tượng có thể có phương pháp để Save, Print, Display, ModifyTextvv
  2. Một đối tượng là thế giới nhỏ bé của riêng nó, và không phụ thuộc vào hành vi bên ngoài.

Hai định nghĩa này mâu thuẫn trực tiếp với nhau. Nếu một Fileđối tượng có thể tự in, nó sẽ phụ thuộc rất nhiều vào hành vi của máy in. Mặt khác, nếu nó chỉ biết về một thứ gì đó có thể in cho nó (một IFilePrinterhoặc một số giao diện như vậy), thì Fileđối tượng không phải biết gì về in ấn, và vì vậy làm việc với nó sẽ mang lại ít phụ thuộc hơn vào đối tượng.

Vì vậy, tiêm phụ thuộc sẽ phá vỡ đóng gói nếu bạn sử dụng định nghĩa đầu tiên. Nhưng, thành thật mà nói tôi không biết nếu tôi thích định nghĩa đầu tiên - rõ ràng nó không có quy mô (nếu có, MS Word sẽ là một lớp lớn).

Mặt khác, tiêm phụ thuộc gần như là bắt buộc nếu bạn đang sử dụng định nghĩa thứ hai về đóng gói.


Tôi chắc chắn đồng ý với bạn về định nghĩa đầu tiên. Nó cũng vi phạm SoC, được cho là một trong những tội lỗi chính yếu của lập trình và có lẽ là một trong những lý do khiến nó không mở rộng được.
Marcus Stade

4

Nó không vi phạm đóng gói. Bạn đang cung cấp một cộng tác viên, nhưng lớp sẽ quyết định cách sử dụng nó. Miễn là bạn làm theo Tell đừng hỏi mọi thứ đều ổn. Tôi thấy tiêm constructer thích hợp hơn, nhưng setters có thể tốt cũng như miễn là chúng thông minh. Đó là chúng chứa logic để duy trì các bất biến mà lớp đại diện.


1
Bởi vì nó ... không? Nếu bạn có một logger và bạn có một lớp cần logger, việc chuyển logger đến lớp đó không vi phạm đóng gói. Và đó là tất cả tiêm phụ thuộc là.
jrockway

3
Tôi nghĩ rằng bạn hiểu nhầm đóng gói. Ví dụ, lấy một lớp ngày ngây thơ. Trong nội bộ nó có thể có các biến thể ngày, tháng và năm. Nếu chúng được hiển thị dưới dạng các setters đơn giản không có logic, điều này sẽ phá vỡ sự đóng gói, vì tôi có thể làm một cái gì đó như đặt tháng thành 2 và ngày thành 31. Mặt khác, nếu các setters thông minh và kiểm tra các bất biến, thì mọi thứ đều ổn . Cũng lưu ý rằng trong phiên bản sau, tôi có thể thay đổi dung lượng lưu trữ thành ngày kể từ ngày 1/1/1970 và không có gì sử dụng giao diện cần phải biết về điều này với điều kiện tôi viết lại một cách thích hợp các phương pháp ngày / tháng / năm.
Jason Watkins

2
DI chắc chắn không vi phạm đóng gói / ẩn thông tin. Nếu bạn biến một phụ thuộc nội bộ riêng tư thành một thứ được hiển thị trong giao diện chung của lớp, thì theo định nghĩa, bạn đã phá vỡ sự đóng gói của phụ thuộc đó.
Rogério

2
Tôi có một ví dụ cụ thể nơi tôi nghĩ rằng đóng gói bị xâm phạm bởi DI. Tôi có một FooProvider nhận được "dữ liệu foo" từ DB và FooManager lưu trữ nó và tính toán các công cụ trên đầu nhà cung cấp. Tôi đã có những người tiêu dùng mã của mình nhầm vào FooProvider để lấy dữ liệu, nơi tôi muốn đóng gói nó đi để họ chỉ biết về FooManager. Điều này về cơ bản là kích hoạt cho câu hỏi ban đầu của tôi.
urig

1
@Rogerio: Tôi sẽ lập luận rằng hàm tạo không phải là một phần của giao diện công cộng, vì nó chỉ được sử dụng trong thư mục gốc. Do đó, sự phụ thuộc chỉ được "nhìn thấy" bởi gốc thành phần. Trách nhiệm duy nhất của gốc thành phần là nối các phụ thuộc này lại với nhau. Vì vậy, sử dụng tiêm constructor không phá vỡ bất kỳ đóng gói.
Jay Sullivan

4

Điều này tương tự như câu trả lời được nêu lên nhưng tôi muốn nghĩ to - có lẽ những người khác cũng nhìn mọi thứ theo cách này.

  • OO cổ điển sử dụng các nhà xây dựng để xác định hợp đồng "khởi tạo" công khai cho người tiêu dùng của lớp (ẩn TẤT CẢ chi tiết triển khai; còn gọi là đóng gói). Hợp đồng này có thể đảm bảo rằng sau khi khởi tạo, bạn có một đối tượng sẵn sàng sử dụng (nghĩa là không có các bước khởi tạo bổ sung nào được ghi nhớ (er, quên) bởi người dùng).

  • (constructor) DI không thể phủ nhận phá vỡ đóng gói bằng cách chảy máu chi tiết cấy ghép thông qua giao diện xây dựng công cộng này. Miễn là chúng tôi vẫn xem xét nhà xây dựng công cộng chịu trách nhiệm xác định hợp đồng khởi tạo cho người dùng, chúng tôi đã tạo ra một vi phạm khủng khiếp về đóng gói.

Ví dụ lý thuyết:

Class Foo có 4 phương thức và cần một số nguyên để khởi tạo, do đó, hàm tạo của nó trông giống Foo (kích thước int) và ngay lập tức rõ ràng với người dùng của lớp Foo rằng họ phải cung cấp một kích thước ngay lập tức để Foo hoạt động.

Nói rằng việc triển khai đặc biệt này của Foo cũng có thể cần một IWidget để thực hiện công việc của mình. Trình xây dựng nội dung của phụ thuộc này sẽ cho chúng ta tạo một hàm tạo như Foo (kích thước int, tiện ích IWidget)

Điều làm tôi bực mình là bây giờ chúng ta có một hàm tạo kết hợp dữ liệu khởi tạo với các phụ thuộc - một đầu vào được người dùng của lớp ( kích thước ) quan tâm, cái còn lại là một phụ thuộc bên trong chỉ phục vụ gây nhầm lẫn cho người dùng và là một triển khai chi tiết ( phụ tùng ).

Tham số kích thước KHÔNG phải là phụ thuộc - đơn giản là giá trị khởi tạo theo thể hiện. IoC là tuyệt vời cho các phụ thuộc bên ngoài (như widget) nhưng không cho khởi tạo trạng thái nội bộ.

Thậm chí tệ hơn, điều gì xảy ra nếu Widget chỉ cần thiết cho 2 trong 4 phương thức trên lớp này; Tôi có thể phải chịu chi phí tức thời cho Widget mặc dù có thể không được sử dụng!

Làm thế nào để thỏa hiệp / hòa giải điều này?

Một cách tiếp cận là chuyển đổi độc quyền sang các giao diện để xác định hợp đồng hoạt động; và bãi bỏ việc sử dụng các nhà xây dựng bởi người dùng. Để thống nhất, tất cả các đối tượng sẽ chỉ được truy cập thông qua các giao diện và chỉ được khởi tạo thông qua một số dạng trình phân giải (như bộ chứa IOC / DI). Chỉ có container được khởi tạo mọi thứ.

Điều đó quan tâm đến sự phụ thuộc của Widget, nhưng làm thế nào để chúng ta khởi tạo "kích thước" mà không cần dùng đến một phương thức khởi tạo riêng biệt trên giao diện Foo? Sử dụng giải pháp này, chúng tôi đã mất khả năng đảm bảo rằng một phiên bản của Foo được khởi tạo hoàn toàn vào thời điểm bạn nhận được cá thể. Bummer, bởi vì tôi thực sự thích ý tưởng và sự đơn giản của tiêm constructor.

Làm cách nào để tôi đạt được khởi tạo được đảm bảo trong thế giới DI này, khi khởi tạo THÊM hơn CHỈ phụ thuộc bên ngoài?


Cập nhật: Tôi chỉ nhận thấy Unity 2.0 hỗ trợ cung cấp các giá trị cho các tham số của hàm tạo (như trình khởi tạo trạng thái), trong khi vẫn sử dụng cơ chế bình thường cho IoC của các phụ thuộc trong khi giải quyết (). Có lẽ các container khác cũng hỗ trợ điều này? Điều đó giải quyết được khó khăn kỹ thuật khi trộn trạng thái init và DI trong một hàm tạo, nhưng nó vẫn vi phạm đóng gói!
shawnT

Tôi nghe ya. Tôi đã hỏi câu hỏi này bởi vì tôi cũng cảm thấy rằng hai điều tốt (DI & đóng gói) đến với cái giá phải trả. BTW, trong ví dụ của bạn, nơi chỉ có 2 trong số 4 phương thức cần IWidget, điều đó sẽ chỉ ra rằng 2 phương thức còn lại thuộc về một thành phần IMHO khác.
urig

3

Đóng gói thuần túy là một lý tưởng không bao giờ có thể đạt được. Nếu tất cả các phụ thuộc bị ẩn thì bạn sẽ không cần DI nữa. Hãy nghĩ về nó theo cách này, nếu bạn thực sự có các giá trị riêng tư có thể được nội hóa trong đối tượng, ví dụ như giá trị nguyên của tốc độ của một đối tượng xe hơi, thì bạn không có sự phụ thuộc bên ngoài và không cần phải đảo ngược hoặc tiêm phụ thuộc đó. Những loại giá trị trạng thái nội bộ được vận hành hoàn toàn bởi các chức năng riêng tư là những gì bạn muốn gói gọn luôn.

Nhưng nếu bạn đang chế tạo một chiếc xe muốn có một loại đối tượng động cơ nhất định thì bạn có một sự phụ thuộc bên ngoài. Bạn có thể khởi tạo động cơ đó - ví dụ như GMOverHeadCamEngine () mới - bên trong bên trong công cụ xây dựng của đối tượng ô tô, bảo toàn đóng gói nhưng tạo ra một khớp nối quỷ quyệt hơn với một lớp bê tông GMOverHeadCamEngine, hoặc bạn có thể tiêm nó, cho phép đối tượng Xe của bạn hoạt động ví dụ về mặt nông nghiệp (và mạnh mẽ hơn nhiều) trên một giao diện IEngine mà không phụ thuộc cụ thể. Cho dù bạn sử dụng thùng chứa IOC hay DI đơn giản để đạt được điều này không phải là vấn đề - vấn đề là bạn đã có một chiếc Xe có thể sử dụng nhiều loại động cơ mà không cần ghép nối với bất kỳ trong số chúng, do đó làm cho cơ sở mã hóa của bạn linh hoạt hơn và ít bị tác dụng phụ.

DI không phải là vi phạm đóng gói, đó là một cách để giảm thiểu khớp nối khi đóng gói nhất thiết bị phá vỡ như một vấn đề tất nhiên trong hầu như mọi dự án OOP. Việc tiêm một phụ thuộc vào một giao diện bên ngoài sẽ giảm thiểu các tác dụng phụ của khớp nối và cho phép các lớp của bạn duy trì sự không tin tưởng về việc thực hiện.


3

Nó phụ thuộc vào việc phụ thuộc có thực sự là một chi tiết triển khai hay thứ gì đó mà khách hàng muốn / cần biết về cách này hay cách khác. Một điều có liên quan là mức độ trừu tượng mà lớp đang nhắm tới. Dưới đây là một số ví dụ:

Nếu bạn có một phương pháp sử dụng bộ nhớ đệm dưới mui xe để tăng tốc các cuộc gọi, thì đối tượng bộ đệm phải là Singleton hoặc một cái gì đó và không nên được tiêm. Thực tế là bộ đệm đang được sử dụng hoàn toàn là một chi tiết triển khai mà các máy khách của lớp bạn không cần phải quan tâm.

Nếu lớp của bạn cần xuất luồng dữ liệu, có thể nên truyền luồng đầu ra để lớp có thể dễ dàng xuất kết quả ra một mảng, tệp hoặc bất cứ nơi nào khác mà người khác muốn gửi dữ liệu.

Đối với một khu vực màu xám, giả sử bạn có một lớp mô phỏng monte carlo. Nó cần một nguồn ngẫu nhiên. Một mặt, thực tế rằng nó cần đây là một chi tiết triển khai trong đó khách hàng thực sự không quan tâm chính xác sự ngẫu nhiên đến từ đâu. Mặt khác, vì các trình tạo số ngẫu nhiên trong thế giới thực tạo ra sự đánh đổi giữa mức độ ngẫu nhiên, tốc độ, v.v. mà khách hàng có thể muốn kiểm soát và khách hàng có thể muốn kiểm soát gieo hạt để có hành vi lặp lại, tiêm có thể có ý nghĩa. Trong trường hợp này, tôi khuyên bạn nên cung cấp một cách tạo lớp mà không chỉ định trình tạo số ngẫu nhiên và sử dụng Singleton luồng cục bộ làm mặc định. Nếu / khi có nhu cầu kiểm soát tốt hơn, hãy cung cấp một hàm tạo khác cho phép một nguồn ngẫu nhiên được đưa vào.


2

Tôi tin vào sự đơn giản. Áp dụng IOC / Dependword Tiêm trong các lớp Miền không có bất kỳ cải thiện nào ngoại trừ việc làm cho mã trở nên khó khăn hơn nhiều bằng cách có một tệp xml bên ngoài mô tả mối quan hệ. Nhiều công nghệ như EJB 1.0 / 2.0 & struts 1.1 đang đảo ngược bằng cách giảm nội dung đưa vào XML và thử đặt chúng vào mã dưới dạng chú thích, v.v. Vì vậy, áp dụng IOC cho tất cả các lớp bạn phát triển sẽ khiến mã không có ý nghĩa.

IOC có lợi ích khi đối tượng phụ thuộc chưa sẵn sàng để tạo tại thời điểm biên dịch. Điều này có thể xảy ra trong hầu hết các thành phần kiến ​​trúc mức trừu tượng cơ sở hạ tầng, cố gắng thiết lập một khung cơ sở chung có thể cần phải làm việc cho các kịch bản khác nhau. Ở những nơi sử dụng IOC có ý nghĩa hơn. Tuy nhiên, điều này không làm cho mã đơn giản hơn / có thể bảo trì.

Như tất cả các công nghệ khác, điều này cũng có PRO & CON. Lo lắng của tôi là, chúng tôi thực hiện các công nghệ mới nhất ở tất cả các nơi không phân biệt sử dụng bối cảnh tốt nhất của họ.


2

Đóng gói chỉ bị phá vỡ nếu một lớp có cả trách nhiệm tạo đối tượng (đòi hỏi kiến ​​thức về chi tiết thực hiện) và sau đó sử dụng lớp (không yêu cầu kiến ​​thức về các chi tiết này). Tôi sẽ giải thích lý do tại sao, nhưng trước tiên là một giải phẫu xe nhanh chóng:

Khi tôi đang lái chiếc Kombi đời 1971 cũ của mình, tôi có thể nhấn chân ga và nó đi nhanh hơn một chút. Tôi không cần biết tại sao, nhưng những người chế tạo Kombi tại nhà máy biết chính xác tại sao.

Nhưng trở lại với mã hóa. Đóng gói là "ẩn một chi tiết thực hiện khỏi một cái gì đó bằng cách sử dụng thực hiện đó." Đóng gói là một điều tốt vì các chi tiết triển khai có thể thay đổi mà người dùng của lớp không biết.

Khi sử dụng phép nội xạ phụ thuộc, phép xây dựng được sử dụng để xây dựng các đối tượng loại dịch vụ (trái ngược với các đối tượng thực thể / giá trị có trạng thái mô hình). Bất kỳ biến thành viên nào trong đối tượng loại dịch vụ thể hiện chi tiết triển khai không bị rò rỉ. ví dụ số cổng socket, thông tin cơ sở dữ liệu, một lớp khác để gọi để thực hiện mã hóa, bộ đệm, v.v.

Hàm tạo có liên quan khi lớp ban đầu được tạo. Điều này xảy ra trong giai đoạn xây dựng trong khi container DI (hoặc nhà máy) của bạn kết nối tất cả các đối tượng dịch vụ. Container DI chỉ biết về chi tiết thực hiện. Nó biết tất cả về các chi tiết thực hiện như những người ở nhà máy Kombi biết về bugi.

Trong thời gian chạy, đối tượng dịch vụ đã được tạo được gọi là apon để thực hiện một số công việc thực sự. Tại thời điểm này, người gọi của đối tượng không biết gì về các chi tiết thực hiện.

Đó là tôi lái chiếc Kombi của tôi đến bãi biển.

Bây giờ, trở lại đóng gói. Nếu chi tiết triển khai thay đổi, thì lớp sử dụng triển khai đó vào thời gian chạy không cần thay đổi. Đóng gói không bị phá vỡ.

Tôi cũng có thể lái chiếc xe mới của mình đến bãi biển. Đóng gói không bị phá vỡ.

Nếu chi tiết triển khai thay đổi, container DI (hoặc nhà máy) không cần thay đổi. Bạn đã không bao giờ cố gắng che giấu chi tiết thực hiện từ nhà máy ở nơi đầu tiên.


Làm thế nào bạn sẽ đơn vị kiểm tra nhà máy của bạn? Và điều đó có nghĩa là khách hàng cần biết về nhà máy để có được một chiếc xe hoạt động, điều đó có nghĩa là bạn cần một nhà máy cho từng đối tượng khác trong hệ thống của bạn.
Rodrigo Ruiz

2

Đã đấu tranh với vấn đề xa hơn một chút, bây giờ tôi nghĩ rằng Dependency Injection không (tại thời điểm này) vi phạm đóng gói ở một mức độ nào đó. Đừng hiểu sai về tôi - Tôi nghĩ rằng việc sử dụng phương pháp tiêm phụ thuộc rất đáng để đánh đổi trong hầu hết các trường hợp.

Trường hợp tại sao DI vi phạm đóng gói trở nên rõ ràng khi thành phần bạn đang làm việc sẽ được chuyển đến một bên "bên ngoài" (nghĩ về việc viết thư viện cho khách hàng).

Khi thành phần của tôi yêu cầu các thành phần phụ được đưa vào thông qua hàm tạo (hoặc thuộc tính công cộng), không có gì đảm bảo cho

"Ngăn người dùng thiết lập dữ liệu nội bộ của thành phần ở trạng thái không hợp lệ hoặc không nhất quán".

Đồng thời không thể nói rằng

"Người dùng của thành phần (các phần mềm khác) chỉ cần biết thành phần đó làm gì và không thể tự phụ thuộc vào các chi tiết về cách thức hoạt động của nó" .

Cả hai trích dẫn là từ wikipedia .

Để đưa ra một ví dụ cụ thể: Tôi cần cung cấp một DLL phía máy khách để đơn giản hóa và ẩn giao tiếp với dịch vụ WCF (về cơ bản là mặt tiền từ xa). Bởi vì nó phụ thuộc vào 3 lớp proxy WCF khác nhau, nếu tôi sử dụng phương pháp DI, tôi buộc phải phơi bày chúng thông qua hàm tạo. Với điều đó, tôi để lộ nội bộ của lớp giao tiếp mà tôi đang cố gắng che giấu.

Nói chung tôi là tất cả cho DI. Trong ví dụ cụ thể (cực đoan) này, nó đánh tôi là nguy hiểm.


2

DI vi phạm Đóng gói cho các đối tượng Không chia sẻ - giai đoạn. Các đối tượng được chia sẻ có tuổi thọ bên ngoài đối tượng được tạo và do đó phải ĐỒNG Ý vào đối tượng được tạo. Các đối tượng là riêng tư đối với đối tượng được tạo nên được TẠO vào đối tượng được tạo - khi đối tượng được tạo bị hủy, nó sẽ đưa đối tượng cấu thành với nó. Hãy lấy cơ thể con người làm ví dụ. Những gì sáng tác và những gì tổng hợp. Nếu chúng ta sử dụng DI, người xây dựng cơ thể người sẽ có 100 'đối tượng. Nhiều cơ quan, ví dụ, (có khả năng) có thể thay thế. Nhưng, chúng vẫn được cấu tạo vào cơ thể. Các tế bào máu được tạo ra trong cơ thể (và bị phá hủy) hàng ngày, mà không cần các tác động bên ngoài (trừ protein). Do đó, các tế bào máu được tạo ra bên trong cơ thể - BloodCell mới ().

Những người ủng hộ DI cho rằng một đối tượng KHÔNG BAO GIỜ nên sử dụng toán tử mới. Cách tiếp cận "thuần túy" đó không chỉ vi phạm đóng gói mà còn cả Nguyên tắc thay thế Liskov cho bất cứ ai đang tạo ra đối tượng.


1

Tôi đã đấu tranh với khái niệm này là tốt. Lúc đầu, 'yêu cầu' sử dụng bộ chứa DI (như Spring) để khởi tạo một đối tượng có cảm giác như nhảy qua vòng. Nhưng trong thực tế, nó thực sự không phải là một trò lừa đảo - đó chỉ là một cách 'xuất bản' khác để tạo ra các đối tượng tôi cần. Chắc chắn, đóng gói là 'hỏng' vì ai đó 'bên ngoài lớp' biết những gì nó cần, nhưng thực sự không phải là phần còn lại của hệ thống biết rằng - đó là container DI. Không có gì kỳ diệu xảy ra khác nhau vì DI 'biết' một đối tượng cần một đối tượng khác.

Trên thực tế, nó thậm chí còn tốt hơn - bằng cách tập trung vào các nhà máy và kho lưu trữ, tôi thậm chí không cần biết DI có liên quan gì cả! Điều đó với tôi đặt nắp trở lại đóng gói. Phù!


1
Miễn là DI phụ trách toàn bộ chuỗi khởi tạo, thì tôi đồng ý rằng loại đóng gói xảy ra. Sắp xếp, bởi vì các phụ thuộc vẫn còn công khai và có thể bị lạm dụng. Nhưng khi một nơi nào đó "lên" trong chuỗi, ai đó cần khởi tạo một đối tượng mà không cần họ sử dụng DI (có thể họ là "bên thứ ba") thì nó trở nên lộn xộn. Họ tiếp xúc với sự phụ thuộc của bạn và có thể bị cám dỗ lạm dụng chúng. Hoặc họ có thể không muốn biết về họ.
urig

1

Tái bút Bằng cách cung cấp Tiêm phụ thuộc, bạn không nhất thiết phải phá vỡ Đóng gói . Thí dụ:

obj.inject_dependency(  factory.get_instance_of_unknown_class(x)  );

Mã khách hàng không biết chi tiết thực hiện.


Làm thế nào trong ví dụ của bạn là bất cứ điều gì được tiêm? (Ngoại trừ việc đặt tên cho chức năng setter của bạn)
foo

1

Có thể đây là một cách suy nghĩ ngây thơ về nó, nhưng sự khác biệt giữa một hàm tạo lấy tham số nguyên và hàm tạo lấy một dịch vụ làm tham số là gì? Điều này có nghĩa là việc xác định một số nguyên bên ngoài đối tượng mới và đưa nó vào đối tượng phá vỡ đóng gói? Nếu dịch vụ chỉ được sử dụng trong đối tượng mới, tôi sẽ không thấy điều đó sẽ phá vỡ đóng gói.

Ngoài ra, bằng cách sử dụng một số loại tính năng tự động (ví dụ Autofac cho C #), nó làm cho mã cực kỳ sạch sẽ. Bằng cách xây dựng các phương thức mở rộng cho trình xây dựng Autofac, tôi đã có thể cắt ra rất nhiều mã cấu hình DI mà tôi sẽ phải duy trì theo thời gian khi danh sách các phụ thuộc tăng lên.


Đó không phải là về giá trị so với dịch vụ. Đó là về đảo ngược điều khiển - cho dù hàm tạo của lớp thiết lập lớp hay liệu dịch vụ đó có tiếp quản việc thiết lập lớp hay không. Mà nó cần biết chi tiết triển khai của lớp đó, vì vậy bạn có một nơi khác để duy trì và đồng bộ hóa.
foo

1

Tôi nghĩ rằng rõ ràng là ít nhất DI làm suy yếu đáng kể việc đóng gói. Ngoài ra, đây là một số nhược điểm khác của DI cần xem xét.

  1. Nó làm cho mã khó sử dụng hơn. Một mô-đun mà khách hàng có thể sử dụng mà không cần phải cung cấp phụ thuộc một cách rõ ràng, rõ ràng là dễ sử dụng hơn so với mô-đun mà khách hàng phải khám phá bằng cách nào đó phụ thuộc của thành phần đó và sau đó bằng cách nào đó làm cho chúng có sẵn. Ví dụ, một thành phần ban đầu được tạo để sử dụng trong ứng dụng ASP có thể sẽ có các phụ thuộc được cung cấp bởi bộ chứa DI cung cấp các thể hiện đối tượng với thời gian sống liên quan đến các yêu cầu http của máy khách. Điều này có thể không đơn giản để sao chép trong một máy khách khác không đi kèm với bộ chứa DI được tích hợp tương tự như ứng dụng ASP gốc.

  2. Nó có thể làm cho mã dễ vỡ hơn. Các phụ thuộc được cung cấp bởi đặc tả giao diện có thể được thực hiện theo những cách không mong muốn dẫn đến một loại lỗi thời gian chạy không thể thực hiện được với một phụ thuộc cụ thể được giải quyết tĩnh.

  3. Nó có thể làm cho mã kém linh hoạt theo nghĩa là bạn có thể có ít lựa chọn hơn về cách bạn muốn nó hoạt động. Không phải mọi lớp đều cần có tất cả các phụ thuộc của nó trong toàn bộ thời gian tồn tại của cá thể sở hữu, nhưng với nhiều triển khai DI, bạn không có lựa chọn nào khác.

Với suy nghĩ đó, tôi nghĩ rằng câu hỏi quan trọng nhất sau đó trở thành, " một phụ thuộc cụ thể có cần phải được chỉ định bên ngoài không? ". Trong thực tế, tôi hiếm khi thấy cần phải cung cấp một phụ thuộc bên ngoài chỉ để hỗ trợ thử nghiệm.

Khi một sự phụ thuộc thực sự cần được cung cấp bên ngoài, điều đó thường gợi ý rằng mối quan hệ giữa các đối tượng là sự hợp tác chứ không phải là sự phụ thuộc bên trong, trong trường hợp đó, mục tiêu thích hợp là đóng gói của mỗi lớp, thay vì đóng gói một lớp bên trong lớp kia .

Theo kinh nghiệm của tôi, vấn đề chính liên quan đến việc sử dụng DI là bạn bắt đầu với khung ứng dụng có tích hợp DI hay bạn thêm hỗ trợ DI vào cơ sở mã của mình, vì một số lý do mọi người cho rằng vì bạn có hỗ trợ DI phải là chính xác cách để khởi tạo mọi thứ . Họ thậm chí không bao giờ bận tâm đặt câu hỏi "sự phụ thuộc này có cần được chỉ định bên ngoài không?". Và tệ hơn nữa, họ cũng bắt đầu cố gắng ép buộc mọi người khác sử dụng hỗ trợ DI cho mọi thứ .

Kết quả của việc này là cơ sở mã cơ sở của bạn bắt đầu chuyển sang trạng thái trong đó việc tạo bất kỳ trường hợp nào trong cơ sở mã của bạn yêu cầu các cấu hình bộ chứa DI khó hiểu và gỡ lỗi mọi thứ khó gấp đôi vì bạn có khối lượng công việc thêm để cố gắng xác định và nơi mà bất cứ điều gì đã được khởi tạo.

Vì vậy, câu trả lời của tôi cho câu hỏi này là. Sử dụng DI nơi bạn có thể xác định một vấn đề thực tế mà nó giải quyết cho bạn, mà bạn không thể giải quyết đơn giản hơn bất kỳ cách nào khác.


0

Tôi đồng ý rằng đưa đến một thái cực, DI có thể vi phạm đóng gói. Thông thường DI phơi bày các phụ thuộc không bao giờ thực sự được đóng gói. Dưới đây là một ví dụ đơn giản được mượn từ Miško Hevery Singletons là những kẻ nói dối bệnh lý :

Bạn bắt đầu với bài kiểm tra CreditCard và viết bài kiểm tra đơn vị đơn giản.

@Test
public void creditCard_Charge()
{
    CreditCard c = new CreditCard("1234 5678 9012 3456", 5, 2008);
    c.charge(100);
}

Tháng tới bạn nhận được hóa đơn 100 đô la. Tại sao bạn bị tính phí? Các thử nghiệm đơn vị ảnh hưởng đến một cơ sở dữ liệu sản xuất. Trong nội bộ, các cuộc gọi CreditCard Database.getInstance(). Tái cấu trúc Thẻ tín dụng để có một DatabaseInterfacenhà xây dựng của nó phơi bày thực tế là có sự phụ thuộc. Nhưng tôi sẽ lập luận rằng sự phụ thuộc không bao giờ được gói gọn để bắt đầu vì lớp CreditCard gây ra các tác dụng phụ có thể nhìn thấy bên ngoài. Nếu bạn muốn kiểm tra CreditCard mà không cần tái cấu trúc, bạn chắc chắn có thể quan sát sự phụ thuộc.

@Before
public void setUp()
{
    Database.setInstance(new MockDatabase());
}

@After
public void tearDown()
{
    Database.resetInstance();
}

Tôi không nghĩ rằng đáng để lo lắng liệu việc phơi bày Cơ sở dữ liệu như một sự phụ thuộc có làm giảm đóng gói hay không, bởi vì đó là một thiết kế tốt. Không phải tất cả các quyết định DI sẽ rất thẳng về phía trước. Tuy nhiên, không có câu trả lời nào khác cho thấy một ví dụ phản biện.


1
Các bài kiểm tra đơn vị thường được viết bởi tác giả lớp, vì vậy bạn có thể đánh vần các phụ thuộc trong trường hợp kiểm tra, theo quan điểm kỹ thuật. Khi lớp thẻ tín dụng sau này thay đổi để sử dụng API Web như PayPal, người dùng sẽ cần thay đổi mọi thứ nếu nó bị BẮT. Kiểm tra đơn vị thường được thực hiện với kiến ​​thức sâu sắc về đối tượng được kiểm tra (không phải là toàn bộ vấn đề sao?) Vì vậy tôi nghĩ các bài kiểm tra là một ngoại lệ hơn là một ví dụ điển hình.
kizzx2

1
Điểm của DI là để tránh những thay đổi bạn mô tả. Nếu bạn đã chuyển từ API Web sang PayPal, bạn sẽ không thay đổi hầu hết các thử nghiệm vì họ sẽ sử dụng MockPaymentService và CreditCard sẽ được xây dựng với PaymentService. Bạn chỉ có một số thử nghiệm xem xét sự tương tác thực sự giữa CreditCard và Dịch vụ thanh toán thực sự, vì vậy những thay đổi trong tương lai rất cô lập. Các lợi ích thậm chí còn lớn hơn đối với các biểu đồ phụ thuộc sâu hơn (chẳng hạn như một lớp phụ thuộc vào Thẻ tín dụng).
Craig P. Motlin

@Craig p. Motlin Làm thế nào CreditCardđối tượng có thể thay đổi từ WebAPI sang PayPal mà không cần bất cứ điều gì bên ngoài lớp phải thay đổi?
Ian Boyd

@Ian Tôi đã đề cập đến CreditCard nên được cấu trúc lại để lấy DatabaseInterface trong hàm tạo của nó để bảo vệ nó khỏi những thay đổi trong quá trình triển khai DatabaseInterfaces. Có lẽ điều đó cần phải chung chung hơn nữa và hãy xem StorageInterface mà WebAPI có thể là một triển khai khác. PayPal ở cấp độ sai mặc dù, vì đó là một sự thay thế cho CreditCard. Cả PayPal và CreditCard đều có thể triển khai PaymentInterface để bảo vệ các phần khác của ứng dụng bên ngoài ví dụ này.
Craig P. Motlin

@ kizzx2 Nói cách khác, thật vô nghĩa khi nói thẻ tín dụng nên sử dụng PayPal. Họ là những lựa chọn thay thế trong cuộc sống thực.
Craig P. Motlin

0

Tôi nghĩ đó là vấn đề phạm vi. Khi bạn xác định đóng gói (không biết làm thế nào), bạn phải xác định chức năng được đóng gói là gì.

  1. Lớp như là : những gì bạn đang gói gọn là khả năng đáp ứng duy nhất của lớp. Những gì nó biết làm thế nào. Ví dụ, sắp xếp. Nếu bạn tiêm một số công cụ so sánh để đặt hàng, giả sử, khách hàng, đó không phải là một phần của điều được gói gọn: quicksort.

  2. Chức năng được định cấu hình : nếu bạn muốn cung cấp chức năng sẵn sàng sử dụng thì bạn không cung cấp lớp QuickSort, mà là một phiên bản của lớp QuickSort được cấu hình với Bộ so sánh. Trong trường hợp đó, mã chịu trách nhiệm tạo và cấu hình phải được ẩn khỏi mã người dùng. Và đó là sự đóng gói.

Khi bạn đang lập trình các lớp, nghĩa là, thực hiện các trách nhiệm đơn lẻ thành các lớp, bạn đang sử dụng tùy chọn 1.

Khi bạn đang lập trình các ứng dụng, đó là, làm một cái gì đó đảm nhận một số công việc cụ thể hữu ích thì bạn sẽ lặp đi lặp lại bằng cách sử dụng tùy chọn 2.

Đây là việc thực hiện của thể hiện được cấu hình:

<bean id="clientSorter" class="QuickSort">
   <property name="comparator">
      <bean class="ClientComparator"/>
   </property>
</bean>

Đây là cách một số mã máy khách khác sử dụng nó:

<bean id="clientService" class"...">
   <property name="sorter" ref="clientSorter"/>
</bean>

Nó được gói gọn bởi vì nếu bạn thay đổi triển khai (bạn thay đổi clientSorterđịnh nghĩa bean) thì nó không phá vỡ việc sử dụng máy khách. Có thể, khi bạn sử dụng các tệp xml với tất cả được viết cùng nhau, bạn sẽ thấy tất cả các chi tiết. Nhưng tin tôi đi, mã máy khách ( ClientService) không biết gì về trình sắp xếp của nó.


0

Có lẽ đáng nói Encapsulationlà phụ thuộc vào quan điểm.

public class A { 
    private B b;

    public A() {
        this.b = new B();
    }
}


public class A { 
    private B b;

    public A(B b) {
        this.b = b;
    }
}

Từ quan điểm của một người làm việc trên Alớp, trong ví dụ thứ hai Abiết rất ít về bản chất củathis.b

Trong khi đó không có DI

new A()

đấu với

new A(new B())

Người nhìn vào mã này biết nhiều hơn về bản chất của Aví dụ thứ hai.

Với DI, ít nhất tất cả những kiến ​​thức bị rò rỉ đều ở một nơi.

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.