Đảm bảo rằng mỗi lớp chỉ có một trách nhiệm, tại sao?


37

Theo tài liệu của Microsoft, bài viết về công chúa Wikipedia RẮN, hoặc hầu hết các kiến ​​trúc sư CNTT, chúng tôi phải đảm bảo rằng mỗi lớp chỉ có một trách nhiệm. Tôi muốn biết tại sao, bởi vì nếu mọi người dường như đồng ý với quy tắc này dường như không ai đồng ý về lý do của quy tắc này.

Một số trích dẫn bảo trì tốt hơn, một số người nói rằng nó cung cấp thử nghiệm dễ dàng hoặc làm cho lớp mạnh mẽ hơn hoặc bảo mật hơn. Điều gì là đúng và nó thực sự có nghĩa là gì? Tại sao nó làm cho bảo trì tốt hơn, kiểm tra dễ dàng hơn hoặc mã mạnh mẽ hơn?



1
Tôi có một câu hỏi mà bạn có thể thấy có liên quan: trách nhiệm thực sự của một lớp là gì?
Pierre Arlaud

3
Không thể tất cả các lý do cho trách nhiệm duy nhất là chính xác? Nó làm cho bảo trì dễ dàng hơn. Nó làm cho thử nghiệm dễ dàng hơn. Nó làm cho lớp mạnh mẽ hơn (nói chung).
Martin York

2
Vì lý do tương tự, bạn có các lớp TẠI TẤT CẢ.
Davor dralo

2
Tôi đã luôn luôn có một vấn đề với tuyên bố này. Rất khó xác định "trách nhiệm duy nhất" là gì. Một trách nhiệm có thể bao gồm bất kỳ nơi nào từ "xác minh rằng 1 + 1 = 2" đến "duy trì nhật ký chính xác của tất cả tiền được trả vào và ra khỏi tài khoản ngân hàng của công ty".
Dave Nay

Câu trả lời:


57

Tính mô đun. Bất kỳ ngôn ngữ tử tế nào cũng sẽ cung cấp cho bạn các phương tiện để dán các đoạn mã lại với nhau, nhưng không có cách nào chung để tạo ra một đoạn mã lớn mà không cần lập trình viên thực hiện phẫu thuật trên nguồn. Bằng cách đưa nhiều nhiệm vụ vào một cấu trúc mã, bạn sẽ tự mình cướp đi cơ hội của mình và những người khác để kết hợp các phần của nó theo những cách khác và đưa ra các phụ thuộc không cần thiết có thể gây ra thay đổi cho một phần để ảnh hưởng đến các phần khác.

SRP chỉ có thể áp dụng cho các chức năng như đối với các lớp, nhưng các ngôn ngữ OOP chính thống tương đối kém trong việc dán các chức năng với nhau.


12
+1 để đề cập đến lập trình chức năng như một cách an toàn hơn để soạn thảo trừu tượng.
đăng nhập

> nhưng các ngôn ngữ OOP chính thống tương đối kém trong việc dán các chức năng lại với nhau. <Có thể vì các ngôn ngữ OOP không liên quan đến các chức năng, nhưng các thông điệp.
Jeff Hubbard

@JeffHubbard Tôi nghĩ bạn có nghĩa là vì họ theo đuổi C / C ++. Không có gì về các đối tượng làm cho chúng loại trừ lẫn nhau với các chức năng. Địa ngục, một đối tượng / giao diện chỉ là một bản ghi / cấu trúc của các chức năng. Giả vờ các chức năng không quan trọng là không chính đáng cả về lý thuyết và thực hành. Dù sao họ cũng không thể tránh được - cuối cùng bạn phải ném xung quanh "Runnables", "Factories", "Providers" và "Action" hoặc sử dụng cái gọi là "mẫu" Chiến lược và Phương thức mẫu ", và kết thúc bằng một loạt các nồi hơi không cần thiết để hoàn thành công việc tương tự.
Doval

@Doval Không, tôi thực sự có nghĩa là OOP quan tâm đến tin nhắn. Điều đó không có nghĩa là một ngôn ngữ nhất định tốt hơn hoặc kém hơn một ngôn ngữ lập trình chức năng trong việc dán các chức năng lại với nhau - chỉ là nó không phải là mối quan tâm chính của ngôn ngữ .
Jeff Hubbard

Về cơ bản, nếu ngôn ngữ của bạn là làm cho OOP trở nên dễ dàng hơn / tốt hơn / nhanh hơn / mạnh hơn, thì việc các chức năng có kết hợp tốt với nhau hay không không phải là điều bạn tập trung vào.
Jeff Hubbard

30

Bảo trì tốt hơn, kiểm tra dễ dàng, sửa lỗi nhanh hơn chỉ là kết quả (rất dễ chịu) của việc áp dụng SRP. Lý do chính (như Robert C. Matin nói) là:

Một lớp nên có một, và chỉ một, lý do để thay đổi.

Nói cách khác, SRP tăng thay đổi địa phương .

SRP cũng quảng bá mã DRY. Miễn là chúng tôi có các lớp chỉ có một trách nhiệm, chúng tôi có thể chọn sử dụng chúng ở bất cứ đâu chúng tôi muốn. Nếu chúng ta có một lớp có hai trách nhiệm, nhưng chúng ta chỉ cần một trong số chúng và thứ hai là can thiệp, chúng ta có 2 lựa chọn:

  1. Sao chép-dán lớp vào một lớp khác và thậm chí có thể tạo ra một người đột biến đa trách nhiệm khác (tăng nợ kỹ thuật một chút).
  2. Phân chia lớp và làm cho nó phải ở vị trí đầu tiên, có thể tốn kém do sử dụng rộng rãi của lớp ban đầu.

1
+1 Để liên kết SRP với DRY.
Mahdi

21

Rất dễ dàng để tạo mã để khắc phục một vấn đề cụ thể. Sẽ phức tạp hơn để tạo mã khắc phục sự cố đó trong khi cho phép các thay đổi sau đó được thực hiện an toàn. RẮN cung cấp một tập các thực hành làm cho mã tốt hơn.

Như một trong những chính xác: Tất cả ba trong số họ. Chúng đều là lợi ích của việc sử dụng trách nhiệm duy nhất và lý do mà bạn nên sử dụng nó.

Theo ý nghĩa của chúng:

  • Bảo trì tốt hơn có nghĩa là dễ dàng thay đổi hơn và không thay đổi thường xuyên. Vì có ít mã hơn và mã đó được tập trung vào một cái gì đó cụ thể, nếu bạn cần thay đổi thứ gì đó không liên quan đến lớp, lớp không cần phải thay đổi. Hơn nữa, khi bạn cần thay đổi lớp, miễn là bạn không cần thay đổi giao diện chung, bạn chỉ cần lo lắng về lớp đó và không có gì khác.
  • Kiểm tra dễ dàng có nghĩa là kiểm tra ít hơn, thiết lập ít hơn. Không có nhiều bộ phận chuyển động trên lớp, vì vậy số lượng lỗi có thể xảy ra trên lớp là nhỏ hơn, và do đó ít trường hợp bạn cần kiểm tra. Sẽ có ít lĩnh vực / thành viên tư nhân được thiết lập.
  • Bởi vì hai ở trên, bạn nhận được một lớp thay đổi ít hơn và thất bại ít hơn, và do đó mạnh mẽ hơn.

Hãy cố gắng tạo mã trong một thời gian theo nguyên tắc và xem lại mã đó sau để thực hiện một số thay đổi. Bạn sẽ thấy lợi thế lớn mà nó cung cấp.

Bạn làm tương tự với mỗi lớp và kết thúc với nhiều lớp hơn, tất cả đều tuân thủ SRP. Nó làm cho cấu trúc phức tạp hơn từ quan điểm của các kết nối, nhưng sự đơn giản của mỗi lớp biện minh cho nó.


Tôi không nghĩ rằng những điểm được thực hiện ở đây là hợp lý. Cụ thể, làm thế nào để bạn tránh các trò chơi có tổng bằng không trong đó việc đơn giản hóa một lớp khiến các lớp khác trở nên phức tạp hơn, không có hiệu ứng ròng trên toàn bộ cơ sở mã? Tôi tin rằng câu trả lời của @ Doval giải quyết vấn đề này.

2
Bạn làm tương tự cho tất cả các lớp. Bạn kết thúc với nhiều lớp hơn, tất cả đều tuân thủ SRP. Nó làm cho cấu trúc phức tạp hơn từ quan điểm của các kết nối. Nhưng sự đơn giản trên mỗi lớp biện minh cho nó. Tôi thích câu trả lời của @ Doval.
Miyamoto Akira

Tôi nghĩ rằng bạn nên thêm nó vào câu trả lời của bạn.

4

Dưới đây là những lập luận mà theo quan điểm của tôi, ủng hộ cho rằng Nguyên tắc Trách nhiệm đơn lẻ là một thực tiễn tốt. Tôi cũng cung cấp các liên kết đến các tài liệu xa hơn, nơi bạn có thể đọc các lý do chi tiết hơn - và hùng hồn hơn của tôi:

  • Bảo trì tốt hơn : lý tưởng, bất cứ khi nào một chức năng của hệ thống phải thay đổi, sẽ có một và chỉ một lớp phải thay đổi. Một ánh xạ rõ ràng giữa các lớp và trách nhiệm có nghĩa là bất kỳ nhà phát triển nào tham gia vào dự án đều có thể xác định đây là lớp nào. (Như @ MaciejChałapuk đã lưu ý, xem Robert C. Martin, "Mã sạch" .)

  • Kiểm tra dễ dàng hơn : lý tưởng nhất là các lớp nên có giao diện chung tối thiểu nhất có thể và các kiểm tra chỉ nên xử lý giao diện chung này. Nếu bạn không thể kiểm tra một cách rõ ràng, vì nhiều phần trong lớp của bạn là riêng tư, thì đây là một dấu hiệu rõ ràng rằng lớp của bạn có quá nhiều trách nhiệm và bạn nên chia nó thành các lớp con nhỏ hơn. Xin lưu ý điều này cũng áp dụng cho các ngôn ngữ không có thành viên nhóm 'công khai' hoặc 'riêng tư'; có một giao diện công cộng nhỏ có nghĩa là nó rất rõ ràng đối với mã máy khách mà phần nào của lớp được sử dụng. (Xem Kent Beck, "Phát triển dựa trên thử nghiệm" để biết thêm chi tiết.)

  • Mã mạnh mẽ : của bạn sẽ không thất bại ít nhiều thường xuyên vì nó được viết tốt; nhưng, như tất cả các mã, mục tiêu cuối cùng của nó không phảigiao tiếp với máy , mà là với các nhà phát triển đồng nghiệp (xem Kent Beck, "Các mẫu triển khai" , Chương 1.) Một cơ sở mã rõ ràng dễ lý do hơn, do đó sẽ ít lỗi hơn được đưa ra và sẽ mất ít thời gian hơn giữa việc phát hiện ra lỗi và sửa nó.


Nếu một cái gì đó phải thay đổi vì lý do kinh doanh tin tôi với SRP, bạn sẽ phải sửa đổi nhiều hơn một classe. Nói rằng nếu một lớp thay đổi thì chỉ vì một lý do không giống nhau là nếu có thay đổi, nó sẽ chỉ ảnh hưởng đến một lớp.
Bastien Vandamme

@ B413: Tôi không có nghĩa là bất kỳ thay đổi nào sẽ ngụ ý một thay đổi duy nhất cho một lớp duy nhất. Nhiều bộ phận của hệ thống có thể cần thay đổi để tuân thủ một thay đổi kinh doanh duy nhất. Nhưng có lẽ bạn có một điểm và tôi nên viết "bất cứ khi nào chức năng của hệ thống phải thay đổi".
logc

3

Có một số lý do, nhưng lý do tôi thích là cách tiếp cận được sử dụng bởi nhiều chương trình UNIX đầu tiên: Làm tốt một việc. Thật khó để làm điều đó với một điều, và càng ngày càng khó khăn hơn khi bạn cố gắng làm nhiều việc hơn.

Một lý do khác là để hạn chế và kiểm soát tác dụng phụ. Tôi yêu sự kết hợp mở cửa máy pha cà phê của tôi. Thật không may, cà phê thường tràn khi tôi có khách. Tôi đã quên đóng cửa sau khi tôi pha cà phê vào một ngày khác và ai đó đã lấy trộm nó.

Từ quan điểm tâm lý, bạn chỉ có thể theo dõi một vài điều tại một thời điểm. Ước tính chung là bảy cộng hoặc trừ hai. Nếu một lớp làm nhiều việc, bạn cần theo dõi tất cả chúng cùng một lúc. Điều này làm giảm khả năng của bạn để theo dõi những gì bạn đang làm. Nếu một lớp thực hiện ba điều và bạn chỉ muốn một trong số chúng, bạn có thể sử dụng hết khả năng của mình để theo dõi mọi thứ trước khi bạn thực sự làm bất cứ điều gì với lớp.

Làm nhiều thứ làm tăng độ phức tạp của mã. Ngoài mã đơn giản nhất, tăng độ phức tạp làm tăng khả năng lỗi. Từ quan điểm này, bạn muốn các lớp học càng đơn giản càng tốt.

Kiểm tra một lớp học làm một việc đơn giản hơn nhiều. Bạn không phải xác minh rằng điều thứ hai mà lớp đã làm hoặc không xảy ra cho mọi bài kiểm tra. Bạn cũng không phải sửa các điều kiện bị hỏng và kiểm tra lại khi một trong những thử nghiệm này thất bại.


2

Bởi vì phần mềm là hữu cơ. Yêu cầu thay đổi liên tục để bạn phải thao tác với các thành phần ít đau đầu nhất có thể. Bằng cách không tuân theo các nguyên tắc RẮN, bạn có thể kết thúc với một cơ sở mã được đặt cụ thể.

Hãy tưởng tượng một ngôi nhà với một bức tường bê tông chịu tải. Điều gì xảy ra khi bạn lấy bức tường này ra mà không có bất kỳ sự hỗ trợ nào? Ngôi nhà có thể sẽ sụp đổ. Trong phần mềm, chúng tôi không muốn điều này, vì vậy chúng tôi cấu trúc các ứng dụng theo cách sao cho bạn có thể dễ dàng di chuyển / thay thế / sửa đổi các thành phần mà không gây ra nhiều thiệt hại.


1

Tôi làm theo suy nghĩ : 1 Class = 1 Job.

Sử dụng một phép tương tự sinh lý: Động cơ (Hệ thống thần kinh), Hô hấp (Phổi), Tiêu hóa (Dạ dày), Olfactory (Quan sát), v.v ... Mỗi người trong số họ sẽ có một tập hợp các bộ điều khiển, nhưng mỗi người chỉ có một trách nhiệm, cho dù đó là quản lý cách mỗi hệ thống con tương ứng của chúng hoạt động hoặc cho dù chúng là hệ thống con điểm cuối và chỉ thực hiện 1 nhiệm vụ, chẳng hạn như nhấc một ngón tay hoặc mọc một nang lông.

Đừng nhầm lẫn thực tế rằng nó có thể hoạt động như một người quản lý thay vì một công nhân. Cuối cùng, một số công nhân được thăng chức lên Manager, khi công việc họ đang thực hiện trở nên quá phức tạp đối với một quy trình để tự xử lý.

Phần phức tạp nhất của những gì tôi đã trải qua, là biết khi nào nên chỉ định một Lớp làm quy trình Người vận hành, Người giám sát hoặc Người quản lý. Bất kể, bạn sẽ cần phải quan sát và biểu thị chức năng của nó cho 1 trách nhiệm (Người vận hành, Người giám sát hoặc Người quản lý).

Khi Class / Object thực hiện nhiều hơn 1 trong số các vai trò loại này, bạn sẽ thấy rằng quy trình tổng thể sẽ bắt đầu có vấn đề về hiệu năng hoặc xử lý cổ chai.


Ngay cả khi việc xử lý oxy trong khí quyển thành hemoglobin nên được xử lý là một Lunglớp, tôi sẽ khẳng định rằng một trường hợp Personvẫn nên Breathe, và do đó, Personlớp nên chứa đủ logic, tối thiểu, ủy thác các trách nhiệm liên quan đến phương pháp đó. Tôi muốn đề xuất thêm rằng một khu rừng gồm các thực thể được kết nối với nhau chỉ có thể truy cập thông qua một chủ sở hữu chung thường dễ dàng lý luận hơn một khu rừng có nhiều điểm truy cập độc lập.
supercat

Đúng trong trường hợp đó, phổi là Người quản lý, mặc dù Villi sẽ là Người giám sát và quá trình Hô hấp sẽ là lớp Công nhân để chuyển các hạt Khí vào dòng máu.
GoldBishop

@GoldBishop: Có lẽ quan điểm đúng đắn là nói rằng một Personlớp có nhiệm vụ là phái đoàn của tất cả các chức năng liên quan đến một thực thể "con người thực sự" phức tạp quá mức, bao gồm cả sự liên kết của các bộ phận khác nhau . Có một thực thể làm 500 việc là xấu, nhưng nếu đó là cách hệ thống thế giới thực được mô hình hóa hoạt động, có một lớp ủy nhiệm 500 chức năng trong khi vẫn giữ một danh tính có thể tốt hơn là phải làm mọi thứ với các phần rời rạc.
supercat

@supercat Hoặc bạn có thể chỉ cần Tổng hợp toàn bộ nội dung và có 1 Lớp với Người giám sát cần thiết trong các quy trình phụ. Theo cách đó, về mặt lý thuyết, bạn có thể tách từng quy trình khỏi lớp cơ sở Personnhưng vẫn báo cáo chuỗi thành công / thất bại nhưng không nhất thiết ảnh hưởng đến quy trình khác. 500 Hàm (mặc dù được đặt làm ví dụ, sẽ quá mức và không thể hỗ trợ) tôi cố gắng giữ loại chức năng đó có nguồn gốc và mô-đun.
GoldBishop

@GoldBishop: Tôi ước có một cách nào đó để khai báo một loại "phù du", sao cho mã bên ngoài có thể gọi các thành viên theo nó hoặc truyền nó như một tham số cho các phương thức của chính loại đó, nhưng không có gì khác. Từ góc độ tổ chức mã, các lớp phân chia có ý nghĩa và thậm chí có mã bên ngoài gọi các phương thức xuống một cấp (ví dụ: Fred.vision.lookAt(George)có ý nghĩa, nhưng cho phép mã someEyes = Fred.vision; someTubes = Fred.digestioncó vẻ khó hiểu vì nó che khuất mối quan hệ giữa someEyessomeTubes.
supercat

1

Đặc biệt với nguyên tắc quan trọng như Trách nhiệm đơn lẻ, cá nhân tôi sẽ mong đợi rằng có nhiều lý do tại sao mọi người áp dụng nguyên tắc này.

Một số lý do có thể là:

  • Bảo trì - SRP đảm bảo rằng việc thay đổi trách nhiệm trong một lớp sẽ không ảnh hưởng đến các trách nhiệm khác, giúp việc bảo trì đơn giản hơn. Đó là bởi vì nếu mỗi lớp chỉ có một trách nhiệm, thì những thay đổi được thực hiện đối với một trách nhiệm sẽ tách biệt với các trách nhiệm khác.
  • Kiểm tra - Nếu một lớp có một trách nhiệm, việc tìm ra cách kiểm tra trách nhiệm này sẽ dễ dàng hơn nhiều. Nếu một lớp có nhiều trách nhiệm, bạn phải chắc chắn rằng bạn đang kiểm tra đúng và một bài kiểm tra không bị ảnh hưởng bởi các trách nhiệm khác mà lớp đó có.

Cũng lưu ý rằng SRP đi kèm với một gói các nguyên tắc RẮN. Việc tuân thủ SRP và bỏ qua phần còn lại cũng tệ như không làm SRP ngay từ đầu. Vì vậy, bạn không nên tự đánh giá SRP, nhưng trong bối cảnh của tất cả các nguyên tắc RẮN.


... test is not affected by other responsibilities the class has, bạn có thể vui lòng giải thích về cái này không?
Mahdi

1

Cách tốt nhất để hiểu tầm quan trọng của các nguyên tắc này là có nhu cầu.

Khi tôi là một lập trình viên mới làm quen, tôi đã không nghĩ nhiều đến thiết kế, thực tế, tôi thậm chí còn không biết các mẫu thiết kế tồn tại. Khi các chương trình của tôi phát triển, thay đổi một điều có nghĩa là thay đổi nhiều thứ khác. Thật khó để theo dõi các lỗi, mã rất lớn và lặp đi lặp lại. Không có nhiều thứ bậc của đối tượng, mọi thứ ở khắp mọi nơi. Thêm một cái gì đó mới, hoặc loại bỏ một cái gì đó cũ sẽ gây ra lỗi trong các phần khác của chương trình. Đi hình.

Trong các dự án nhỏ, điều đó có thể không quan trọng, nhưng trong các dự án lớn, mọi thứ có thể rất ác mộng. Sau này khi tôi bắt gặp các khái niệm về thiết kế patters, tôi tự nhủ: "oh yah, làm điều này sẽ khiến mọi thứ trở nên dễ dàng hơn rất nhiều".

Bạn thực sự không thể hiểu tầm quan trọng của các mẫu thiết kế cho đến khi có nhu cầu. Tôi tôn trọng các mẫu bởi vì từ kinh nghiệm tôi có thể nói rằng chúng làm cho việc bảo trì mã dễ dàng và mã mạnh mẽ.

Tuy nhiên, giống như bạn, tôi vẫn không chắc chắn về "thử nghiệm dễ dàng", vì tôi chưa có nhu cầu tham gia thử nghiệm đơn vị.


Các mẫu được kết nối bằng cách nào đó với SRP, nhưng SRP thì không bắt buộc, khi áp dụng các mẫu thiết kế. Có thể sử dụng các mẫu và hoàn toàn bỏ qua SRP. Chúng tôi sử dụng các mẫu để giải quyết các yêu cầu phi chức năng, nhưng sử dụng các mẫu không bắt buộc trong bất kỳ chương trình nào. Nguyên tắc rất cần thiết trong việc làm cho sự phát triển bớt đau đớn và (IMO) nên là một thói quen.
Maciej Chałapuk

Tôi hoàn toàn đồng ý với bạn. SRP, trong một chừng mực nào đó, thi hành thiết kế và phân cấp đối tượng sạch. Đó là một tâm lý tốt cho một nhà phát triển để tham gia, vì nó khiến nhà phát triển bắt đầu suy nghĩ về các đối tượng, và làm thế nào họ nên được. Cá nhân, nó cũng có tác động rất tốt đến sự phát triển của tôi.
harsimranb

1

Câu trả lời là, như những người khác đã chỉ ra, tất cả chúng đều đúng, và tất cả chúng ăn vào nhau, dễ kiểm tra hơn giúp bảo trì dễ dàng hơn làm cho mã mạnh hơn giúp bảo trì dễ dàng hơn và v.v.

Tất cả những điều này rút xuống một hiệu trưởng chính - mã phải nhỏ và làm ít nhất có thể để hoàn thành công việc. Điều này áp dụng cho một ứng dụng, một tệp hoặc một lớp giống như một hàm. Càng nhiều thứ mà một đoạn mã làm, càng khó hiểu, để duy trì, mở rộng hoặc kiểm tra.

Tôi nghĩ rằng nó có thể được tóm tắt trong một từ: phạm vi. Hãy chú ý đến phạm vi của các vật phẩm, càng ít thứ trong phạm vi tại bất kỳ điểm cụ thể nào trong một ứng dụng thì càng tốt.

Phạm vi mở rộng = phức tạp hơn = nhiều cách để mọi thứ đi sai.


1

Nếu một lớp học quá lớn, nó trở nên khó để duy trì, kiểm tra và hiểu, các câu trả lời khác đã bao hàm ý chí này.

Có thể một lớp có nhiều hơn một trách nhiệm mà không gặp vấn đề gì, nhưng bạn sẽ sớm gặp vấn đề với các lớp quá phức tạp.

Tuy nhiên, có một quy tắc đơn giản là chỉ có một trách nhiệm, chỉ giúp bạn dễ dàng biết khi nào bạn cần một lớp mới .

Tuy nhiên, việc xác định trách nhiệm của Cameron là khó, điều đó không có nghĩa là thực hiện mọi thứ mà thông số kỹ thuật ứng dụng nói, kỹ năng thực sự là biết cách chia nhỏ vấn đề thành các đơn vị nhỏ của trách nhiệm.

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.