Khớp nối lỏng lẻo trong thiết kế hướng đối tượng


16

Tôi đang cố gắng học GRASP và tôi đã tìm thấy điều này được giải thích ( ở đây trên trang 3 ) về Low Coupling và tôi đã rất ngạc nhiên khi tôi tìm thấy điều này:

Hãy xem xét phương thức addTrackcho một Albumlớp, hai phương thức có thể là:

addTrack( Track t )

addTrack( int no, String title, double duration )

Phương pháp nào làm giảm khớp nối? Cái thứ hai thì có, vì lớp sử dụng lớp Album không cần phải biết lớp Track. Nói chung, các tham số cho các phương thức nên sử dụng các kiểu cơ sở (int, char ...) và các lớp từ các gói java. *.

Tôi có xu hướng diasgree với điều này; Tôi tin addTrack(Track t)là tốt hơn addTrack(int no, String title, double duration)vì nhiều lý do:

  1. Luôn luôn tốt hơn cho một phương thức càng ít tham số càng tốt (theo Bộ luật sạch của chú Bob không hoặc tốt nhất là 2 trong một số trường hợp và 3 trong trường hợp đặc biệt; hơn 3 nhu cầu tái cấu trúc - tất nhiên đây là những khuyến nghị không phải là quy tắc chung) .

  2. Nếu addTracklà một phương thức của một giao diện và các yêu cầu cần Trackcó thêm thông tin (giả sử năm hoặc thể loại) thì giao diện cần phải được thay đổi và do đó phương thức sẽ hỗ trợ một tham số khác.

  3. Đóng gói bị phá vỡ; nếu addTracklà trong một giao diện, thì nó không nên biết nội bộ của Track.

  4. Nó thực sự được kết hợp nhiều hơn theo cách thứ hai, với nhiều tham số. Giả sử notham số cần được thay đổi từ intthành longvì có nhiều hơn MAX_INTcác bản nhạc (hoặc vì bất kỳ lý do gì); sau đó cả Trackphương thức và phương thức cần phải được thay đổi trong khi nếu phương thức addTrack(Track track)chỉ là phương thức Tracksẽ được thay đổi.

Tất cả 4 đối số thực sự được kết nối với nhau, và một số trong số chúng là hậu quả từ những người khác.

Cách tiếp cận nào tốt hơn?


2
Đây có phải là một tài liệu được kết hợp bởi một giáo sư hoặc huấn luyện viên? Dựa trên URL của liên kết bạn cung cấp, có vẻ như nó dành cho một lớp, mặc dù tôi không thấy bất kỳ tín dụng nào trong tài liệu về người đã tạo ra nó. Nếu đây là một phần của lớp học, tôi khuyên bạn nên hỏi những câu hỏi này của người đã cung cấp tài liệu. Nhân tiện, tôi đồng ý với lý luận của bạn - có vẻ như rõ ràng với tôi rằng một lớp Album sẽ muốn biết về lớp Track.
Derek

Thành thật mà nói, bất cứ khi nào tôi đọc về "Thực tiễn tốt nhất", tôi đều dùng chúng với một hạt muối!
AraK

@Derek Tôi đã tìm thấy tài liệu bằng cách tìm kiếm Google cho "ví dụ mẫu nắm bắt"; Tôi không phải là người đã viết nó nhưng vì nó là từ một trường đại học nên tôi tin rằng nó đáng tin cậy. Tôi đang tìm kiếm một ví dụ dựa trên thông tin được cung cấp và bỏ qua nguồn.
m3th0dman

4
@ m3th0dman "nhưng vì nó là từ một trường đại học nên tôi tin rằng nó đáng tin cậy." Đối với tôi bởi vì nó là từ một trường đại học, tôi coi nó không đáng tin cậy. Tôi không tin tưởng ai đó đã từng làm việc trong các dự án nhiều năm nói về các hoạt động tốt nhất trong phát triển phần mềm.
AraK

1
@AraK Đáng tin cậy không có nghĩa là không thể nghi ngờ; và đó là những gì tôi đang làm ở đây, đặt câu hỏi về nó.
m3th0dman

Câu trả lời:


15

Vâng, ba điểm đầu tiên của bạn thực sự là về các nguyên tắc khác ngoài khớp nối. Bạn luôn phải cân bằng giữa các nguyên tắc thiết kế xung đột.

Điểm thứ tư của bạn về khớp nối, và tôi hoàn toàn đồng ý với bạn. Khớp nối là về luồng dữ liệu giữa các mô-đun. Loại container mà dữ liệu chảy vào phần lớn là không quan trọng. Vượt qua một khoảng thời gian là gấp đôi thay vì một lĩnh vực Trackkhông làm giảm nhu cầu vượt qua nó. Các mô-đun vẫn cần chia sẻ cùng một lượng dữ liệu và vẫn có cùng số lượng khớp nối.

Ông cũng không coi tất cả các khớp nối trong hệ thống là một tổng hợp. Trong khi giới thiệu một Tracklớp thừa nhận thêm một phụ thuộc khác giữa hai mô-đun riêng lẻ, nó có thể làm giảm đáng kể sự ghép của hệ thống , đây là biện pháp quan trọng ở đây.

Ví dụ: xem xét nút "Thêm vào danh sách phát" và một Playlistđối tượng. Giới thiệu một Trackđối tượng có thể được xem xét để tăng khớp nối nếu bạn chỉ xem xét hai đối tượng đó. Bây giờ bạn có ba lớp phụ thuộc lẫn nhau thay vì hai. Tuy nhiên, đó không phải là toàn bộ hệ thống của bạn. Bạn cũng cần nhập bản nhạc, phát bản nhạc, hiển thị bản nhạc, v.v. Thêm một lớp nữa vào bản phối đó là không đáng kể.

Bây giờ hãy xem xét việc cần thêm hỗ trợ để phát các bản nhạc qua mạng thay vì chỉ cục bộ. Bạn chỉ cần tạo một NetworkTrackđối tượng phù hợp với cùng một giao diện. Nếu không có Trackđối tượng, bạn sẽ phải tạo các hàm ở mọi nơi như:

addNetworkTrack(int no, string title, double duration, URL location)

Điều đó làm tăng gấp đôi hiệu quả khớp nối của bạn, yêu cầu ngay cả các mô-đun không quan tâm đến nội dung cụ thể của mạng mà vẫn theo dõi nó, để có thể vượt qua.

Kiểm tra hiệu ứng gợn của bạn là một thử nghiệm tốt để xác định số lượng khớp nối thực sự của bạn. Điều chúng tôi quan tâm là hạn chế những nơi mà một sự thay đổi ảnh hưởng.


1
+ Khớp nối với nguyên thủy vẫn là khớp nối cho dù nó được cắt như thế nào.
JustinC

+1 để đề cập đến thêm tùy chọn URL / hiệu ứng gợn.
dùng949300

4
+1 Thú vị đọc về điều này cũng sẽ là cuộc thảo luận về Nguyên tắc đảo ngược phụ thuộc trong DIP trong tự nhiên khi việc sử dụng các loại nguyên thủy thực sự được xem là "mùi" ám ảnh nguyên thủy với Giá trị đối tượng . Đối với tôi nghe có vẻ tốt hơn là vượt qua một đối tượng Theo dõi mà toàn bộ các loại nguyên thủy ... Và nếu bạn muốn tránh phụ thuộc vào / ghép với các lớp cụ thể, hãy sử dụng giao diện.
Marjan Venema

Câu trả lời được chấp nhận vì giải thích tốt về sự khác biệt giữa khớp nối toàn hệ thống và khớp nối mô-đun.
m3th0dman

10

Đề nghị của tôi là:

Sử dụng

addTrack( ITrack t )

nhưng hãy chắc chắn rằng đó ITracklà một giao diện và không phải là một lớp cụ thể.

Album không biết nội bộ của người ITrackthực hiện. Nó chỉ được kết hợp với hợp đồng được xác định bởi ITrack.

Tôi nghĩ rằng đây là giải pháp tạo ra số lượng khớp nối ít nhất.


1
Tôi tin rằng Track chỉ là một đối tượng truyền dữ liệu / bean đơn giản, trong đó nó chỉ có các trường và getters / setters trên chúng; một giao diện cần thiết trong trường hợp này?
m3th0dman

6
Cần thiết? Chắc là không. Gợi ý, vâng. Ý nghĩa cụ thể của một bản nhạc có thể và sẽ phát triển, nhưng những gì lớp tiêu thụ yêu cầu từ nó có thể sẽ không.
JustinC

2
@ m3th0dman Luôn phụ thuộc vào sự trừu tượng, không phụ thuộc vào bê tông hóa. Điều đó áp dụng bất kể Tracklà ngu ngốc hay thông minh. Tracklà một sự lắng đọng. ITrackgiao diện là một sự trừu tượng. Bằng cách đó, bạn sẽ được chấp nhận để có các loại Bản nhạc khác nhau trong tương lai, miễn là chúng tuân thủ ITrack.
Tulains Córdova

4
Tôi đồng ý với ý tưởng này, nhưng mất tiền tố 'Tôi'. Từ Clean Code, của Robert Martin, trang 24: "Cái trước của tôi, rất phổ biến trong các bản di sản ngày nay, là một sự xao lãng ở mức tốt nhất và quá nhiều thông tin tồi tệ nhất. Tôi không muốn người dùng của mình biết rằng tôi đang trao cho họ một giao diện. "
Benjamin Brumfield

1
@BenjaminBrumfield Bạn nói đúng. Tôi cũng không thích tiền tố, mặc dù tôi sẽ để lại câu trả lời cho rõ ràng.
Tulains Córdova

4

Tôi sẽ lập luận rằng phương thức ví dụ thứ hai rất có thể làm tăng khả năng ghép nối, vì rất có thể nó đang khởi tạo một đối tượng Track và lưu trữ nó trong đối tượng Album hiện tại. (Như được đề xuất trong nhận xét của tôi ở trên, tôi sẽ cho rằng nó vốn dĩ là một lớp Album sẽ có khái niệm về một lớp Track ở đâu đó bên trong nó.)

Phương thức ví dụ đầu tiên giả định rằng Bản nhạc đang được khởi tạo bên ngoài lớp Album, do đó, ít nhất, chúng ta có thể giả sử rằng phần khởi tạo của lớp Track không được ghép với lớp Album.

Nếu các thực tiễn tốt nhất gợi ý rằng chúng ta không bao giờ có một lớp tham chiếu một lớp thứ hai, toàn bộ lập trình hướng đối tượng sẽ bị ném ra ngoài cửa sổ.


Tôi không thấy việc có một tham chiếu ngầm đến một lớp khác làm cho nó được ghép nối nhiều hơn là có một tham chiếu rõ ràng. Dù bằng cách nào, hai lớp được ghép nối. Tôi nghĩ rằng tốt hơn là có khớp nối rõ ràng, nhưng tôi không nghĩ rằng bất kỳ "nhiều hơn" được ghép nối theo bất kỳ cách nào.
TMN

1
@TMN, khớp nối thêm là trong cách tôi ngụ ý rằng ví dụ thứ hai có thể sẽ tạo ra một đối tượng Track mới. Việc khởi tạo đối tượng đang được ghép nối với một phương thức mà nếu không chỉ cần thêm một đối tượng Theo dõi vào một loại danh sách nào đó trong đối tượng Album (phá vỡ Nguyên tắc Trách nhiệm Đơn lẻ). Nếu cách mà Track được tạo bao giờ cần phải thay đổi, phương thức addTrack () cũng cần phải được thay đổi. Đây không phải là như vậy trong trường hợp ví dụ đầu tiên.
Derek

3

Khớp nối chỉ là một trong nhiều khía cạnh để cố gắng lấy mã của bạn. Bằng cách giảm khớp nối, bạn không nhất thiết phải cải thiện chương trình của mình. Nói chung, đây là một thực tiễn tốt nhất, nhưng trong trường hợp cụ thể này, tại sao không Tracknên biết?

Bằng cách sử dụng một Tracklớp được chuyển đến Album, bạn đang làm cho mã của mình dễ đọc hơn, nhưng quan trọng hơn, như bạn đã đề cập, bạn đang biến một danh sách tĩnh các tham số thành một đối tượng động. Điều đó cuối cùng làm cho giao diện của bạn năng động hơn nhiều.

Bạn đề cập rằng đóng gói bị phá vỡ, nhưng nó không phải là. Albumphải biết nội bộ của Track, và nếu bạn không sử dụng một đối tượng, Albumsẽ phải biết từng thông tin được truyền đến nó trước khi có thể sử dụng tất cả các đối tượng như nhau. Người gọi cũng phải biết nội bộ của Tracknó, vì nó phải xây dựng một Trackđối tượng, nhưng người gọi phải biết tất cả thông tin này giống nhau nếu nó được truyền trực tiếp vào phương thức. Nói cách khác, nếu lợi thế của việc đóng gói là không biết nội dung của một đối tượng, thì nó không thể được sử dụng trong trường hợp này vì Albumphải sử dụng Trackthông tin giống nhau.

Nơi bạn không muốn sử dụng Tracklà nếu Trackchứa logic bên trong mà bạn không muốn người gọi có quyền truy cập. Nói cách khác, nếu Albumlà một lớp mà một lập trình viên sử dụng thư viện của bạn sẽ sử dụng, bạn sẽ không muốn anh ta sử dụng Tracknếu bạn sử dụng nó để nói, hãy gọi một phương thức để duy trì nó trên cơ sở dữ liệu. Vấn đề thực sự với điều này nằm ở chỗ giao diện bị vướng vào mô hình.

Để khắc phục sự cố, bạn sẽ cần tách Trackthành các thành phần giao diện và các thành phần logic của nó, tạo hai lớp riêng biệt. Đối với người gọi, Tracktrở thành một lớp ánh sáng có nghĩa là chứa thông tin và cung cấp tối ưu hóa nhỏ (dữ liệu được tính toán và / hoặc giá trị mặc định). Bên trong Album, bạn sẽ sử dụng một lớp có tên TrackDAOđể thực hiện việc nâng vật nặng liên quan đến việc lưu thông tin từ Trackcơ sở dữ liệu.

Tất nhiên, đây chỉ là một ví dụ. Tôi chắc chắn đây không phải là trường hợp của bạn, và vì vậy hãy thoải mái sử dụng không có Tracktội lỗi. Chỉ cần nhớ ghi nhớ người gọi khi bạn đang xây dựng các lớp và tạo giao diện khi được yêu cầu.


3

Cả hai đều đúng

addTrack( Track t ) 

tốt hơn (như bạn đã tranh luận) trong khi

addTrack( int no, String title, double duration ) 

được ít kết vì mã mà sử dụng addTrackkhông cần phải biết rằng có một Tracklớp. Theo dõi có thể được đổi tên chẳng hạn mà không cần cập nhật mã cuộc gọi.

Trong khi bạn đang nói về mã dễ đọc / dễ bảo trì hơn, bài viết đang nói về khớp nối . Mã ghép ít hơn không nhất thiết phải dễ thực hiện và dễ hiểu hơn.


Xem đối số 4; Tôi không thấy cái thứ hai ít ghép nối như thế nào.
m3th0dman

3

Khớp nối thấp không có nghĩa là Không khớp nối. Một cái gì đó, ở đâu đó, phải biết về các đối tượng ở nơi khác trong cơ sở mã, và bạn càng giảm sự phụ thuộc vào các đối tượng "tùy chỉnh", bạn càng đưa ra nhiều lý do để mã thay đổi. Những gì tác giả mà bạn trích dẫn đang quảng bá với chức năng thứ hai ít kết hợp hơn, nhưng cũng ít hướng đối tượng hơn, trái với toàn bộ ý tưởng của GRASP là một phương pháp thiết kế hướng đối tượng . Toàn bộ vấn đề là làm thế nào để thiết kế hệ thống như một bộ sưu tập các đối tượng và các tương tác của chúng; Tránh chúng cũng giống như dạy bạn cách lái xe bằng cách nói rằng bạn nên đi xe đạp thay thế.

Thay vào đó, con đường thích hợp là giảm sự phụ thuộc vào các vật thể cụ thể, đó là lý thuyết về "khớp nối lỏng lẻo". Càng ít loại bê tông xác định mà một phương pháp phải có kiến ​​thức, càng tốt. Chỉ bằng tuyên bố đó, tùy chọn đầu tiên thực sự ít được kết hợp, bởi vì phương thức thứ hai lấy các loại đơn giản hơn phải biết về tất cả các loại đơn giản hơn. Chắc chắn chúng được tích hợp sẵn và mã bên trong phương thức có thể phải quan tâm, nhưng chữ ký của phương thức và người gọi phương thức chắc chắn là không . Việc thay đổi một trong các tham số này liên quan đến bản nhạc âm thanh theo khái niệm sẽ đòi hỏi nhiều thay đổi hơn khi chúng tách biệt so với khi chúng được chứa trong một đối tượng Theo dõi (là điểm của các đối tượng; đóng gói).

Đi thêm một bước nữa, nếu Track được dự kiến ​​sẽ được thay thế bằng thứ gì đó làm công việc tương tự tốt hơn, có lẽ một giao diện xác định chức năng cần thiết sẽ theo thứ tự, một ITrack. Điều đó có thể cho phép các triển khai khác nhau, chẳng hạn như "AnalogTrack", "CdTrack" và "Mp3Track" cung cấp thông tin bổ sung cụ thể hơn cho các định dạng đó, trong khi vẫn cung cấp mức độ hiển thị dữ liệu cơ bản của ITrack đại diện cho "track"; một phần phụ hữu hạn của âm thanh. Theo dõi tương tự có thể là một lớp cơ sở trừu tượng, nhưng điều này đòi hỏi bạn phải luôn muốn sử dụng triển khai vốn có trong Theo dõi; thực hiện lại dưới dạng BetterTrack và bây giờ bạn phải thay đổi các tham số dự kiến.

Do đó, nguyên tắc vàng; các chương trình và các thành phần mã của chúng sẽ luôn có lý do để thay đổi. Bạn không thể viết một chương trình sẽ không bao giờ yêu cầu chỉnh sửa mã bạn đã viết để thêm một cái gì đó mới hoặc sửa đổi hành vi của nó. Mục tiêu của bạn, trong bất kỳ phương pháp nào (GRASP, RẮN, bất kỳ từ viết tắt hoặc từ thông dụng nào bạn có thể nghĩ ra) chỉ đơn giản là xác định những thứ sẽ phải thay đổi theo thời gian và thiết kế hệ thống sao cho những thay đổi đó dễ thực hiện nhất có thể (được dịch; chạm vào càng ít dòng mã và ảnh hưởng đến càng ít khu vực khác của hệ thống vượt quá phạm vi thay đổi dự định của bạn càng tốt). Trong trường hợp cụ thể, điều có khả năng thay đổi nhất là Theo dõi sẽ thu được nhiều thành viên dữ liệu hơn mà addTrack () có thể hoặc không quan tâm, không phải Bản nhạc đó sẽ được thay thế bằng BetterTrack.

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.