Lợi ích của việc tiêm phụ thuộc trong trường hợp hầu hết mọi người đều cần truy cập vào cấu trúc dữ liệu chung?


20

Có rất nhiều lý do tại sao toàn cầu là xấu xa trong OOP.

Nếu số lượng hoặc kích thước của các đối tượng cần chia sẻ quá lớn để có thể được truyền một cách hiệu quả trong các tham số chức năng, thông thường mọi người đều khuyên dùng Dependency Injection thay vì đối tượng toàn cầu.

Tuy nhiên, trong trường hợp hầu hết mọi người cần biết về một cấu trúc dữ liệu nhất định, tại sao Dependency Injection lại tốt hơn một đối tượng toàn cầu?

Ví dụ (một cách đơn giản hóa, để hiển thị điểm chung, mà không đào sâu quá trong một ứng dụng cụ thể)

Có một số phương tiện ảo có số lượng tài sản và trạng thái khổng lồ, từ loại, tên, màu sắc, đến tốc độ, vị trí, v.v. Một số người dùng có thể điều khiển từ xa và một số lượng lớn sự kiện (cả người dùng- bắt đầu và tự động) có thể thay đổi rất nhiều trạng thái hoặc thuộc tính của họ.

Giải pháp ngây thơ sẽ là tạo ra một thùng chứa toàn cầu của họ, như

vector<Vehicle> vehicles;

có thể được truy cập từ bất cứ đâu.

Giải pháp thân thiện với OOP hơn sẽ là để container là thành viên của lớp xử lý vòng lặp sự kiện chính và được khởi tạo trong hàm tạo của nó. Mỗi lớp cần nó, và là thành viên của luồng chính, sẽ được cấp quyền truy cập vào vùng chứa thông qua một con trỏ trong hàm tạo của chúng. Ví dụ: nếu một tin nhắn bên ngoài đến thông qua kết nối mạng, một lớp (một cho mỗi kết nối) xử lý phân tích cú pháp sẽ đảm nhiệm và trình phân tích cú pháp sẽ có quyền truy cập vào vùng chứa thông qua một con trỏ hoặc tham chiếu. Bây giờ nếu thông báo được phân tích cú pháp dẫn đến thay đổi thành phần của bộ chứa hoặc yêu cầu một số dữ liệu trong đó để thực hiện hành động, nó có thể được xử lý mà không cần phải ném hàng ngàn biến qua tín hiệu và khe cắm (hoặc tệ hơn, lưu trữ chúng trong trình phân tích cú pháp để sau đó được lấy bởi người đã gọi trình phân tích cú pháp). Tất nhiên, tất cả các lớp nhận được quyền truy cập vào vùng chứa thông qua phép nội xạ phụ thuộc, là một phần của cùng một luồng. Các luồng khác nhau sẽ không truy cập trực tiếp vào nó, nhưng thực hiện công việc của chúng và sau đó gửi tín hiệu đến luồng chính và các khe trong luồng chính sẽ cập nhật vùng chứa.

Tuy nhiên, nếu phần lớn các lớp sẽ có quyền truy cập vào container, điều gì làm cho nó thực sự khác biệt với toàn cầu? Nếu rất nhiều lớp cần dữ liệu trong vùng chứa, thì đó không phải là "cách tiêm phụ thuộc" chỉ là một toàn cầu trá hình?

Một câu trả lời sẽ là an toàn luồng: mặc dù tôi chú ý không lạm dụng container toàn cầu, có thể một nhà phát triển khác trong tương lai, dưới áp lực của thời hạn gần, tuy nhiên sẽ sử dụng container toàn cầu trong một luồng khác, mà không cần quan tâm đến tất cả các trường hợp va chạm. Tuy nhiên, ngay cả trong trường hợp tiêm phụ thuộc, người ta có thể đưa ra một con trỏ cho ai đó đang chạy trong một luồng khác, dẫn đến các vấn đề tương tự.


6
Đợi đã, bạn nói về những quả cầu không thể thay đổi và sử dụng một liên kết đến "tại sao trạng thái toàn cầu xấu" để biện minh cho nó? WTF?
Telastyn

1
Tôi không biện minh cho toàn cầu. Tôi nghĩ rõ ràng là tôi đồng ý với thực tế rằng toàn cầu không phải là giải pháp tốt. Tôi chỉ nói về một tình huống khi thậm chí sử dụng tiêm phụ thuộc có thể không tốt hơn nhiều so với (hoặc thậm chí gần như không thể phân biệt được) trên toàn cầu. Vì vậy, một câu trả lời có thể chỉ ra những lợi ích tiềm ẩn khác của việc vẫn sử dụng phương pháp tiêm phụ thuộc trong tình huống này hoặc rằng các phương pháp khác sẽ tốt hơn so với di
vsz

9
Nhưng có một sự khác biệt quyết định giữa chỉ đọc toàn cầu và nhà nước toàn cầu.
Telastyn


1
@vsz không thực sự - câu hỏi là về các nhà máy định vị dịch vụ như là một cách giải quyết vấn đề của nhiều đối tượng khác nhau; nhưng câu trả lời của nó cũng trả lời câu hỏi của bạn về việc cho phép các lớp truy cập vào dữ liệu toàn cầu thay vì dữ liệu toàn cầu được truyền đến các lớp.
gbjbaanb

Câu trả lời:


37

trong trường hợp hầu hết mọi người cần biết về một cấu trúc dữ liệu nhất định, tại sao Dependency Injection lại tốt hơn một đối tượng toàn cầu?

Tiêm phụ thuộc là điều tốt nhất kể từ khi bánh mì cắt lát , trong khi các đối tượng toàn cầu đã được biết đến trong nhiều thập kỷ là nguồn gốc của mọi tội lỗi , vì vậy đây là một câu hỏi khá thú vị.

Quan điểm của sự phụ thuộc không chỉ đơn giản là đảm bảo rằng mọi diễn viên cần một số tài nguyên đều có thể có nó, bởi vì rõ ràng, nếu bạn tạo ra tất cả các tài nguyên trên toàn cầu, thì mọi diễn viên sẽ có quyền truy cập vào mọi tài nguyên, vấn đề được giải quyết, phải không?

Điểm tiêm phụ thuộc là:

  1. Để cho phép các tác nhân truy cập tài nguyên trên cơ sở cần thiết
  2. Để có quyền kiểm soát đối tượng tài nguyên nào được truy cập bởi bất kỳ tác nhân nào.

Thực tế là trong cấu hình cụ thể của bạn, tất cả các tác nhân tình cờ cần truy cập vào cùng một thể hiện tài nguyên là không liên quan. Tin tôi đi, một ngày nào đó bạn sẽ có nhu cầu cấu hình lại mọi thứ để các diễn viên sẽ có quyền truy cập vào các phiên bản khác nhau của tài nguyên, và sau đó bạn sẽ nhận ra rằng bạn đã vẽ mình ở một góc. Một số câu trả lời đã chỉ ra một cấu hình như vậy: thử nghiệm .

Một ví dụ khác: giả sử bạn chia ứng dụng của mình thành máy khách-máy chủ. Tất cả các tác nhân trên máy khách sử dụng cùng một bộ tài nguyên trung tâm trên máy khách và tất cả các tác nhân trên máy chủ đều sử dụng cùng một bộ tài nguyên trung tâm trên máy chủ. Bây giờ, giả sử, một ngày nào đó, bạn quyết định tạo một phiên bản "độc lập" của ứng dụng máy chủ-máy khách của mình, trong đó cả máy khách và máy chủ được đóng gói trong một tệp thực thi duy nhất và chạy trong cùng một máy ảo. (Hoặc môi trường thời gian chạy, tùy thuộc vào ngôn ngữ bạn chọn.)

Nếu bạn sử dụng phép nội xạ phụ thuộc, bạn có thể dễ dàng đảm bảo rằng tất cả các tác nhân máy khách được cung cấp các phiên bản tài nguyên máy khách để làm việc, trong khi tất cả các tác nhân máy chủ đều nhận được các phiên bản tài nguyên máy chủ.

Nếu bạn không sử dụng phép nội xạ phụ thuộc, bạn hoàn toàn không gặp may, vì chỉ một trường hợp toàn cầu của mỗi tài nguyên có thể tồn tại trong một máy ảo.

Sau đó, bạn phải xem xét: tất cả các diễn viên có thực sự cần quyền truy cập vào tài nguyên đó không? có thật không?

Có thể bạn đã phạm sai lầm khi biến tài nguyên đó thành đối tượng thần, (vì vậy, tất nhiên mọi người đều cần quyền truy cập vào nó) hoặc có lẽ bạn đang đánh giá quá cao số lượng diễn viên trong dự án của bạn thực sự cần quyền truy cập vào tài nguyên đó .

Với toàn cầu, mỗi dòng mã nguồn trong toàn bộ ứng dụng của bạn có quyền truy cập vào mọi tài nguyên toàn cầu. Với phép nội xạ phụ thuộc, mỗi thể hiện tài nguyên chỉ hiển thị cho những tác nhân thực sự cần nó. Nếu hai cái này giống nhau, (các tác nhân cần một tài nguyên cụ thể bao gồm 100% các dòng mã nguồn trong dự án của bạn,) thì bạn hẳn đã mắc lỗi trong thiết kế của mình. Vì vậy

  • Tái cấu trúc nguồn tài nguyên thần khổng lồ to lớn thành tài nguyên phụ nhỏ hơn, vì vậy các tác nhân khác nhau cần truy cập vào các phần khác nhau của nó, nhưng hiếm khi diễn viên cần tất cả các phần của nó, hoặc

  • Tái cấu trúc các diễn viên của bạn để lần lượt chấp nhận làm tham số chỉ là tập hợp con của vấn đề mà họ cần giải quyết, vì vậy họ không cần phải tư vấn một số tài nguyên trung tâm lớn rất lớn mọi lúc.


Tôi không chắc là tôi hiểu điều này - một mặt bạn nói DI cho phép bạn sử dụng nhiều phiên bản của tài nguyên và toàn cầu thì không, điều này hoàn toàn vô nghĩa trừ khi bạn kết hợp một biến tĩnh duy nhất với các đối tượng được tạo tức thời - không có lý do rằng "toàn cầu" phải là một thể hiện đơn lẻ hoặc một lớp duy nhất.
gbjbaanb

3
@gbjbaanb Giả sử rằng tài nguyên là Zork. OP đang nói rằng không chỉ mỗi đối tượng trong hệ thống của anh ta cần có Zork để làm việc mà còn có một Zork duy nhất tồn tại và tất cả các đối tượng của anh ta sẽ chỉ cần truy cập vào một thể hiện của Zork. Tôi đang nói rằng cả hai giả định này đều không hợp lý: có lẽ không phải tất cả các đối tượng của anh ta đều cần Zork, và sẽ có lúc một số đối tượng sẽ cần một phiên bản Zork nhất định, trong khi các đối tượng khác sẽ cần một ví dụ khác nhau của Zork.
Mike Nakis

2
@gbjbaanb Tôi không cho rằng bạn đang yêu cầu tôi giải thích lý do tại sao sẽ có hai trường hợp Zork toàn cầu, đặt tên chúng là ZorkA và ZorkB, và mã hóa cứng vào các đối tượng mà ai sẽ sử dụng ZorkA và ai sẽ sử dụng ZorkB, đúng?
Mike Nakis

1
Tôi không nói mọi người, tôi nói gần như tất cả mọi người. Vấn đề của câu hỏi là liệu DI có lợi ích nào khác ngoài việc từ chối quyền truy cập từ một vài diễn viên không cần nó không. Tôi biết rằng "các đối tượng thần" là một mô hình chống đối, nhưng mù quáng tuân theo các thực hành tốt nhất cũng có thể là một. Có thể xảy ra rằng toàn bộ mục đích của một chương trình là làm nhiều việc khác nhau với một tài nguyên nhất định, trong trường hợp đó hầu như mọi người đều cần quyền truy cập vào tài nguyên đó. Một ví dụ điển hình sẽ là một chương trình hoạt động trên một hình ảnh: hầu hết mọi thứ trong đó đều liên quan đến dữ liệu hình ảnh.
vsz

1
@gbjbaanb Tôi tin rằng ý tưởng là có một lớp khách hàng có thể hoạt động độc quyền trên ZorkA hoặc ZorkB như được chỉ ra, không để lớp khách hàng quyết định chọn lớp nào trong số họ.
Eugene Ryabtsev

39

Có rất nhiều lý do tại sao các thế giới không đột biến là xấu xa trong OOP.

Đó là một tuyên bố đáng ngờ. Các liên kết mà bạn sử dụng như bằng chứng liên quan đến nhà nước - có thể thay đổi globals. Họ quyết định xấu xa. Chỉ đọc toàn cầu chỉ là hằng số. Hằng số tương đối lành mạnh. Ý tôi là, bạn sẽ không đưa giá trị của pi vào tất cả các lớp của mình chứ?

Nếu số lượng hoặc kích thước của các đối tượng cần chia sẻ quá lớn để có thể được truyền một cách hiệu quả trong các tham số chức năng, thông thường mọi người đều khuyên dùng Dependency Injection thay vì đối tượng toàn cầu.

Không, họ không.

Nếu các chức năng / lớp của bạn có quá nhiều phụ thuộc, mọi người khuyên bạn nên dừng lại và xem tại sao thiết kế của bạn quá tệ. Có một khối lượng phụ thuộc là một dấu hiệu cho thấy lớp / chức năng của bạn có thể đang làm quá nhiều thứ và / hoặc bạn không có đủ sự trừu tượng.

Có một số phương tiện ảo có số lượng tài sản và trạng thái khổng lồ, từ loại, tên, màu sắc, đến tốc độ, vị trí, v.v. Một số người dùng có thể điều khiển từ xa và một số lượng lớn sự kiện (cả người dùng- bắt đầu và tự động) có thể thay đổi rất nhiều trạng thái hoặc thuộc tính của họ.

Đây là một cơn ác mộng thiết kế. Bạn có một loạt các công cụ có thể được sử dụng / lạm dụng mà không có cách xác nhận, không có giới hạn đồng thời - đó chỉ là khớp nối tất cả.

Tuy nhiên, nếu phần lớn các lớp sẽ có quyền truy cập vào container, điều gì làm cho nó thực sự khác biệt với toàn cầu? Nếu rất nhiều lớp cần dữ liệu trong vùng chứa, thì đó không phải là "cách tiêm phụ thuộc" chỉ là một toàn cầu trá hình?

Đúng vậy, việc kết hợp với dữ liệu chung ở mọi nơi không quá khác biệt so với toàn cầu, và như vậy, vẫn tệ.

Ưu điểm là bạn đang tách người tiêu dùng khỏi chính biến toàn cầu. Điều này cung cấp cho bạn một số linh hoạt trong cách tạo ra thể hiện. Nó cũng không buộc bạn chỉ có một trường hợp. Bạn có thể làm cho những người khác nhau được chuyển đến những người tiêu dùng khác nhau của bạn. Sau đó, bạn cũng có thể tạo các triển khai khác nhau cho giao diện của mình cho mỗi người tiêu dùng nếu bạn cần.

Và do sự thay đổi chắc chắn đi kèm với các yêu cầu thay đổi của phần mềm, mức độ linh hoạt cơ bản như vậy là rất quan trọng .


xin lỗi vì sự nhầm lẫn với "không thể thay đổi", tôi muốn nói có thể thay đổi và bằng cách nào đó đã không nhận ra sai lầm của mình.
vsz

"Đây là một cơn ác mộng thiết kế" - tôi biết đó là. Nhưng nếu các yêu cầu là tất cả các sự kiện đó phải có khả năng thực hiện các thay đổi trong dữ liệu, thì các sự kiện sẽ được kết hợp với dữ liệu ngay cả khi tôi tách và ẩn chúng dưới một lớp trừu tượng. Toàn bộ chương trình là về dữ liệu này. Ví dụ, nếu một chương trình phải thực hiện nhiều thao tác khác nhau trên một hình ảnh, thì tất cả hoặc gần như tất cả các lớp của nó bằng cách nào đó sẽ được ghép với dữ liệu hình ảnh. Chỉ nói "hãy viết một chương trình làm điều gì đó khác biệt" là không thể chấp nhận được.
vsz

2
Các ứng dụng có nhiều đối tượng thường xuyên truy cập một số cơ sở dữ liệu không hẳn là chưa từng có.
Robert Harvey

@RobertHarvey - chắc chắn, nhưng giao diện họ phụ thuộc vào "có thể truy cập cơ sở dữ liệu" hay "một số kho lưu trữ dữ liệu liên tục cho foos" không?
Telastyn

1
Nhắc nhở tôi về Dilbert nơi PHB yêu cầu 500 tính năng và Dilbert nói không. Vì vậy, PHB yêu cầu 1 tính năng và Dilbert nói chắc chắn. Ngày hôm sau Dilbert nhận được 500 yêu cầu cho mỗi tính năng. Nếu một cái gì đó không quy mô, nó chỉ không quy mô cho dù bạn ăn mặc như thế nào.
corsiKa

16

Có ba lý do chính bạn nên xem xét.

  1. Dễ đọc. Nếu mọi đơn vị mã có mọi thứ nó cần để hoạt động hoặc được tiêm hoặc truyền vào dưới dạng tham số, thật dễ dàng để xem mã và ngay lập tức xem những gì nó đang làm. Điều này mang đến cho bạn một địa phương của chức năng, điều này cũng cho phép bạn phân tách các mối quan tâm tốt hơn và cũng buộc bạn phải suy nghĩ về ...
  2. Tính mô đun. Tại sao trình phân tích cú pháp phải biết về toàn bộ danh sách các phương tiện và tất cả các thuộc tính của chúng? Có lẽ là không. Có lẽ nó cần truy vấn xem id xe có tồn tại không hay liệu xe X có thuộc tính Y. Tuyệt vời, điều đó có nghĩa là bạn có thể viết một dịch vụ thực hiện điều đó và chỉ tiêm dịch vụ đó vào trình phân tích cú pháp của bạn. Đột nhiên bạn kết thúc với một thiết kế có ý nghĩa hơn nhiều, với mỗi bit mã chỉ xử lý dữ liệu có liên quan đến nó. Điều này dẫn chúng ta đến ...
  3. Khả năng kiểm tra. Đối với một bài kiểm tra đơn vị điển hình, bạn muốn thiết lập môi trường của mình cho nhiều tình huống khác nhau và việc tiêm giúp việc này rất dễ thực hiện. Một lần nữa, trở lại ví dụ về trình phân tích cú pháp, bạn có thực sự muốn luôn tự tạo thủ công toàn bộ danh sách các phương tiện chính thức cho mọi trường hợp kiểm tra mà bạn viết cho trình phân tích cú pháp của mình không? Hoặc bạn sẽ tạo ra một triển khai giả của đối tượng dịch vụ đã nói ở trên, trả về số lượng id khác nhau? Tôi biết cái nào tôi sẽ chọn.

Vì vậy, để tóm tắt: DI không phải là một mục tiêu, nó chỉ là một công cụ giúp đạt được mục tiêu. Nếu bạn sử dụng sai mục đích (giống như bạn làm trong ví dụ của mình), bạn sẽ không tiến gần hơn đến mục tiêu, không có thuộc tính phép thuật nào của DI sẽ làm cho một thiết kế xấu trở nên tốt.


+1 cho câu trả lời ngắn gọn tập trung vào các vấn đề bảo trì. Thêm câu trả lời P: SE nên như thế này.
dodgethesteam điều khiển

1
Tổng hợp của bạn là rất tốt. Rất tốt Nếu bạn nghĩ rằng [mẫu thiết kế của tháng] hoặc [từ thông dụng của tháng] sẽ giải quyết một cách kỳ diệu những vấn đề của bạn thì bạn sẽ có một khoảng thời gian tồi tệ.
corsiKa

@corsiKa Tôi không thích DI (hay chính xác hơn là Spring) trong một thời gian dài vì tôi không thể nhìn thấy điểm của nó. Nó dường như là rất nhiều rắc rối không có lợi ích hữu hình (điều này đã trở lại trong những ngày cấu hình XML). Tôi ước tôi đã tìm thấy một số tài liệu về những gì DI thực sự mua lại cho bạn sau đó. Nhưng tôi đã không làm, vì vậy tôi phải tự tìm ra nó. Phải mất một thời gian dài. :)
biziclop

3

Nó thực sự về khả năng kiểm tra - thông thường một đối tượng toàn cầu rất dễ bảo trì và dễ đọc / hiểu (tất nhiên khi bạn biết nó ở đó), nhưng đó là một vật cố định và khi bạn chia các lớp của mình thành các bài kiểm tra riêng biệt, bạn thấy rằng đối tượng toàn cầu bị mắc kẹt khi nói "những gì về tôi" và do đó các bài kiểm tra bị cô lập của bạn yêu cầu đối tượng toàn cầu phải được đưa vào chúng. Điều đó không giúp ích gì cho hầu hết các khung kiểm tra không dễ dàng hỗ trợ cho kịch bản này.

Vì vậy, có, nó vẫn là một toàn cầu. Lý do cơ bản để có nó không thay đổi, chỉ có cách thức mà nó được truy cập.

Về phần khởi tạo, đây vẫn là một vấn đề - bạn vẫn phải đặt cấu hình để các bài kiểm tra của bạn có thể chạy, do đó bạn không đạt được bất cứ điều gì ở đó. Tôi cho rằng bạn có thể chia một đối tượng phụ thuộc lớn thành nhiều đối tượng nhỏ hơn và chuyển đối tượng thích hợp cho các lớp cần chúng, nhưng có lẽ bạn còn phức tạp hơn nhiều so với bất kỳ lợi ích nào.

Bất kể tất cả những điều đó là ví dụ về 'cái đuôi vẫy đuôi con chó', cần phải kiểm tra xem cách thức cơ sở mã hóa được kiến ​​trúc (các vấn đề tương tự phát sinh với các mục như lớp tĩnh, ví dụ như thời gian hiện tại).

Cá nhân tôi thích gắn tất cả dữ liệu toàn cầu của mình vào một đối tượng toàn cầu (hoặc một số) nhưng cung cấp các trình truy cập để lấy các bit mà các lớp mà tôi cần. Sau đó, tôi có thể giả định những dữ liệu đó để trả về dữ liệu tôi muốn khi kiểm tra mà không cần thêm độ phức tạp của DI.


"thông thường một đối tượng toàn cầu rất dễ bảo trì" - không phải nếu nó có thể thay đổi. Theo kinh nghiệm của tôi, việc có quá nhiều trạng thái toàn cầu có thể thay đổi có thể là một cơn ác mộng (mặc dù có nhiều cách để làm cho nó có thể chịu đựng được).
sleske

3

Ngoài câu trả lời bạn đã có,

Có một số phương tiện ảo có số lượng tài sản và trạng thái khổng lồ, từ loại, tên, màu sắc, đến tốc độ, vị trí, v.v. Một số người dùng có thể điều khiển từ xa và một số lượng lớn sự kiện (cả người dùng- bắt đầu và tự động) có thể thay đổi rất nhiều trạng thái hoặc thuộc tính của họ.

Một trong những đặc điểm của lập trình OOP là chỉ giữ các thuộc tính bạn thực sự cần cho một đối tượng cụ thể,

NGAY BÂY GIỜ NẾU đối tượng của bạn có quá nhiều thuộc tính - bạn nên chia nhỏ đối tượng của mình thành các đối tượng phụ.

Chỉnh sửa

Đã đề cập tại sao các đối tượng toàn cầu là xấu, tôi sẽ thêm một ví dụ cho nó.

Bạn cần thực hiện kiểm tra đơn vị trên mã GUI của biểu mẫu cửa sổ, nếu bạn sử dụng toàn cầu, bạn sẽ cần tạo tất cả các nút và ví dụ như các sự kiện để tạo trường hợp kiểm tra thích hợp ...


Đúng, nhưng ví dụ, trình phân tích cú pháp sẽ được yêu cầu biết tất cả mọi thứ vì có thể nhận được bất kỳ lệnh nào có thể thay đổi bất cứ điều gì.
vsz

1
"Trước khi tôi đến câu hỏi thực tế của bạn" ... bạn sẽ trả lời câu hỏi của anh ấy chứ?
gbjbaanb

@gbjbaanb câu hỏi đã có rất nhiều văn bản, xin vui lòng cho tôi một chút thời gian để đọc nó, đúng cách
Toán học
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.