Khi sử dụng Nguyên tắc trách nhiệm duy nhất, điều gì tạo nên trách nhiệm của người dùng?


198

Có vẻ như khá rõ ràng rằng "Nguyên tắc trách nhiệm duy nhất" không có nghĩa là "chỉ làm một việc." Đó là những gì phương pháp dành cho.

public Interface CustomerCRUD
{
    public void Create(Customer customer);
    public Customer Read(int CustomerID);
    public void Update(Customer customer);
    public void Delete(int CustomerID);
}

Bob Martin nói rằng "các lớp học chỉ nên có một lý do để thay đổi." Nhưng điều đó thật khó khăn để bao bọc tâm trí bạn nếu bạn là một lập trình viên mới với RẮN.

Tôi đã viết một câu trả lời cho một câu hỏi khác , trong đó tôi cho rằng trách nhiệm giống như chức danh công việc và nhảy xung quanh chủ đề bằng cách sử dụng một phép ẩn dụ của nhà hàng để minh họa quan điểm của tôi. Nhưng điều đó vẫn không nói rõ một tập hợp các nguyên tắc mà ai đó có thể sử dụng để xác định trách nhiệm của lớp mình.

vậy bạn sẽ làm sao? Làm thế nào để bạn xác định trách nhiệm mà mỗi lớp nên có và làm thế nào để bạn xác định trách nhiệm trong bối cảnh của SRP?


28
Đăng lên Code Review và bị xé toạc :-D
Jörg W Mittag

8
@ JörgWMittag Hey bây giờ, không hù dọa người đi :)
Flambino

118
Những câu hỏi như thế này từ các thành viên kỳ cựu chứng minh rằng các quy tắc và nguyên tắc mà chúng tôi cố gắng tuân theo không có nghĩa là đơn giản hay đơn giản . Chúng [mâu thuẫn] tự mâu thuẫn và thần bí ... như bất kỳ bộ quy tắc tốt nào nên có. Và, tôi muốn tin những câu hỏi như thế này làm cho người khôn ngoan khiêm tốn và hy vọng cho những người cảm thấy ngu ngốc vô vọng. Cảm ơn, Robert!
Svidgen

41
Tôi tự hỏi nếu câu hỏi này đã bị hạ cấp + đánh dấu trùng lặp ngay lập tức nếu nó được đăng bởi một người mới :)
Andrejs

9
@rmunn: hay nói cách khác - đại diện lớn thu hút nhiều đại diện hơn, bởi vì không ai hủy bỏ định kiến ​​cơ bản của con người về stackexchange
Andrejs

Câu trả lời:


117

Một cách để giải quyết vấn đề này là tưởng tượng những thay đổi yêu cầu tiềm năng trong các dự án trong tương lai và tự hỏi bạn sẽ cần phải làm gì để biến chúng thành hiện thực.

Ví dụ:

Yêu cầu kinh doanh mới: Người dùng ở California được giảm giá đặc biệt.

Ví dụ về thay đổi "tốt": Tôi cần sửa đổi mã trong một lớp tính toán giảm giá.

Ví dụ về các thay đổi xấu: Tôi cần sửa đổi mã trong lớp Người dùng và thay đổi đó sẽ có tác động xếp tầng đối với các lớp khác sử dụng lớp Người dùng, bao gồm các lớp không liên quan đến giảm giá, ví dụ như đăng ký, liệt kê và quản lý.

Hoặc là:

Yêu cầu không chức năng mới: Chúng tôi sẽ bắt đầu sử dụng Oracle thay vì SQL Server

Ví dụ về thay đổi tốt: Chỉ cần sửa đổi một lớp duy nhất trong lớp truy cập dữ liệu xác định cách duy trì dữ liệu trong DTOs.

Thay đổi xấu: Tôi cần sửa đổi tất cả các lớp lớp nghiệp vụ của mình vì chúng chứa logic dành riêng cho SQL Server.

Ý tưởng là để giảm thiểu dấu chân của những thay đổi tiềm năng trong tương lai, hạn chế sửa đổi mã đối với một khu vực mã trên mỗi khu vực thay đổi.

Ở mức tối thiểu, các lớp học của bạn nên tách biệt mối quan tâm logic khỏi mối quan tâm vật lý. Một tập hợp lớn các ví dụ có thể được tìm thấy trong System.IOnamespace: đó chúng tôi có thể tìm thấy một loại suối vật lý (ví dụ FileStream, MemoryStreamhoặc NetworkStream) và độc giả khác nhau và các nhà văn ( BinaryWriter, TextWriter) mà làm việc trên một mức hợp lý. Bằng cách tách riêng họ theo cách này, chúng ta tránh nổ combinatoric: thay vì cần FileStreamTextWriter, FileStreamBinaryWriter, NetworkStreamTextWriter, NetworkStreamBinaryWriter, MemoryStreamTextWriter, và MemoryStreamBinaryWriter, bạn chỉ cần treo lên các nhà văn và các dòng suối và bạn có thể có những gì bạn muốn. Sau đó, chúng ta có thể thêm, giả sử XmlWriter, mà không cần phải thực hiện lại nó cho bộ nhớ, tệp và mạng riêng biệt.


34
Mặc dù tôi đồng ý với suy nghĩ trước, có những nguyên tắc như YAGNI và các phương pháp như TDD thar đề xuất điều ngược lại.
Robert Harvey

87
YAGNI bảo chúng ta đừng xây dựng những thứ chúng ta không cần ngày hôm nay. Nó không nói không xây dựng công cụ theo cách có thể mở rộng. Xem thêm nguyên tắc mở / đóng , trong đó nêu rõ "các thực thể phần mềm (lớp, mô-đun, hàm, v.v.) nên được mở để mở rộng, nhưng đóng để sửa đổi."
John Wu

18
@JohnW: +1 cho bình luận YAGNI của bạn. Tôi không thể tin được mình phải giải thích với mọi người rằng YAGNI không phải là cái cớ để xây dựng một hệ thống cứng nhắc, không linh hoạt, không thể phản ứng với sự thay đổi - trớ trêu thay, ngược lại với những gì SRP và hiệu trưởng Mở / Đóng đang hướng tới.
Greg Burghardt

36
@ John John: Tôi không đồng ý, YAGNI nói với chúng tôi chính xác là không xây dựng những thứ chúng ta không cần ngày hôm nay. Khả năng đọc và kiểm tra, ví dụ, là thứ mà chương trình luôn cần "ngày nay", vì vậy YAGNI không bao giờ là lý do để không thêm cấu trúc và điểm tiêm. Tuy nhiên, ngay khi "khả năng mở rộng" thêm chi phí đáng kể mà lợi ích không rõ ràng là "ngày nay", YAGNI có nghĩa là để tránh loại mở rộng này, vì điều này dẫn đến việc áp đảo.
Doc Brown

9
@JohnWu Chúng tôi đã chuyển từ SQL 2008 sang 2012. Có tổng cộng hai truy vấn cần thay đổi. Và từ SQL Auth đến đáng tin cậy? Tại sao điều đó thậm chí sẽ là một sự thay đổi mã; thay đổi ConnectionString trong tập tin cấu hình là đủ. Một lần nữa, YAGNI. YAGNI và SRP đôi khi là mối quan tâm cạnh tranh và bạn cần đánh giá xem cái nào có chi phí / lợi ích tốt hơn.
Andy

76

Thực tế mà nói, trách nhiệm bị ràng buộc bởi những điều có khả năng thay đổi. Do đó, không có cách khoa học hay công thức nào để đi đến những gì cấu thành trách nhiệm, thật không may. Đó là một cuộc gọi phán xét.

Đó là về những gì, theo kinh nghiệm của bạn , có khả năng thay đổi.

Chúng ta có xu hướng áp dụng ngôn ngữ của nguyên tắc trong một cơn thịnh nộ, theo nghĩa đen, nhiệt tình. Chúng tôi có xu hướng phân chia các lớp vì chúng có thể thay đổi hoặc dọc theo các dòng chỉ đơn giản là giúp chúng tôi phá vỡ các vấn đề. (Lý do thứ hai không phải là vốn đã xấu.) Tuy nhiên, SRP không tồn tại vì lợi ích riêng của mình; đó là dịch vụ để tạo ra phần mềm có thể bảo trì.

Vì vậy, một lần nữa, nếu các bộ phận không bị chi phối bởi những thay đổi có khả năng , thì chúng không thực sự phục vụ cho SRP 1 nếu YAGNI được áp dụng nhiều hơn. Cả hai đều phục vụ cùng một mục tiêu cuối cùng. Và cả hai đều là vấn đề của sự phán xét - hy vọng sự phán xét dày dạn .

Khi chú Bob viết về điều này, ông đề nghị chúng tôi nghĩ về "trách nhiệm" theo nghĩa "ai đang yêu cầu thay đổi". Nói cách khác, chúng tôi không muốn Bên A mất việc vì Bên B yêu cầu thay đổi.

Khi bạn viết một mô-đun phần mềm, bạn muốn đảm bảo rằng khi các thay đổi được yêu cầu, những thay đổi đó chỉ có thể bắt nguồn từ một người, hoặc đúng hơn, một nhóm người được ghép chặt chẽ duy nhất đại diện cho một chức năng kinh doanh được xác định hẹp. Bạn muốn tách biệt các mô-đun của mình khỏi sự phức tạp của toàn bộ tổ chức và thiết kế các hệ thống của bạn sao cho mỗi mô-đun chịu trách nhiệm (đáp ứng) các nhu cầu của chỉ một chức năng kinh doanh đó. ( Chú Bob - Nguyên tắc trách nhiệm duy nhất )

Các nhà phát triển giỏi và có kinh nghiệm sẽ có ý thức về những thay đổi có khả năng. Và danh sách tinh thần đó sẽ thay đổi phần nào theo ngành và tổ chức.

Những gì cấu thành trách nhiệm trong ứng dụng cụ thể của bạn, tại tổ chức cụ thể của bạn, cuối cùng là vấn đề của sự phán xét dày dạn . Đó là về những gì có khả năng thay đổi. Và, theo một nghĩa nào đó, đó là về người sở hữu logic bên trong của mô-đun.


1. Để rõ ràng, điều đó không có nghĩa là họ chia rẽ xấu . Chúng có thể là các bộ phận lớn giúp cải thiện đáng kể khả năng đọc mã. Điều đó chỉ có nghĩa là họ không bị SRP điều khiển.


11
Câu trả lời hay nhất và thực sự trích dẫn suy nghĩ của chú Bob. Đối với những gì có khả năng thay đổi, mọi người đều thực hiện một thỏa thuận lớn đối với I / O, "nếu chúng ta thay đổi cơ sở dữ liệu thì sao?" hoặc "nếu chúng ta chuyển từ XML sang JSON thì sao?" Tôi nghĩ rằng điều này thường là sai lầm. Câu hỏi thực sự phải là "nếu chúng ta cần thay đổi int này thành float, thêm một trường và thay đổi Chuỗi này thành Danh sách các Chuỗi thì sao?"
dùng949300

2
Đây là gian lận. Bản thân trách nhiệm chỉ là một cách đề xuất "cách ly thay đổi". Giải thích rằng bạn cần cách ly các thay đổi để giữ trách nhiệm "độc thân", không đề xuất cách thực hiện việc này, chỉ giải thích nguồn gốc của yêu cầu.
Basilevs

6
@Basilevs Tôi đang cố gắng khắc phục sự thiếu sót mà bạn đang thấy trong câu trả lời này - không đề cập đến câu trả lời của chú Bob! Nhưng, có lẽ tôi cần làm rõ rằng SRP không phải là về việc đảm bảo rằng "một sự thay đổi" sẽ chỉ ảnh hưởng đến 1 lớp. Đó là về việc đảm bảo rằng mỗi lớp sẽ chỉ đáp ứng "một thay đổi". ... Đó là về việc cố gắng rút mũi tên của bạn từ mỗi lớp cho một chủ sở hữu duy nhất. Không phải từ mỗi chủ sở hữu đến một lớp duy nhất.
Svidgen

2
Cảm ơn bạn đã cung cấp một phản ứng thực dụng! Ngay cả chú Bob cũng cảnh báo chống lại sự tuân thủ nhiệt tình với các nguyên tắc RẮN trong Kiến trúc Agile . Tôi không có trích dẫn tiện dụng, nhưng về cơ bản, ông nói rằng việc phân chia trách nhiệm vốn đã làm tăng mức độ trừu tượng trong mã của bạn và tất cả sự trừu tượng đều phải trả giá, vì vậy hãy đảm bảo lợi ích của việc tuân theo SRP (hoặc các nguyên tắc khác) lớn hơn chi phí thêm trừu tượng. (tiếp theo nhận xét tiếp theo)
Michael L.

4
Đây là lý do tại sao chúng ta nên đặt sản phẩm trước khách hàng sớm và thường xuyên là hợp lý, vì vậy họ sẽ buộc thay đổi trong thiết kế của chúng tôi và chúng tôi có thể thấy những khu vực có khả năng thay đổi trong sản phẩm đó. Ngoài ra, ông cảnh báo rằng chúng ta không thể bảo vệ bản thân khỏi mọi loại thay đổi. Đối với bất kỳ ứng dụng nào , một số loại thay đổi sẽ khó thực hiện. Chúng ta cần đảm bảo đó là những thay đổi ít có khả năng xảy ra nhất.
Michael L.

29

Tôi theo "các lớp chỉ nên có một lý do để thay đổi".

Đối với tôi, điều này có nghĩa là nghĩ đến các kế hoạch hạn chế mà chủ sở hữu sản phẩm của tôi có thể đưa ra ("Chúng tôi cần hỗ trợ cho thiết bị di động!", "Chúng tôi cần phải lên đám mây!", "Chúng tôi cần hỗ trợ tiếng Trung!"). Thiết kế tốt sẽ hạn chế tác động của các kế hoạch này đến các khu vực nhỏ hơn, và làm cho chúng tương đối dễ thực hiện. Thiết kế xấu có nghĩa là sẽ có rất nhiều mã và thực hiện một loạt các thay đổi rủi ro.

Kinh nghiệm là điều duy nhất tôi tìm thấy để đánh giá đúng khả năng của những kế hoạch điên rồ đó - bởi vì làm cho dễ dàng có thể làm cho hai người khác khó khăn hơn - và đánh giá sự tốt của một thiết kế. Các lập trình viên có kinh nghiệm có thể tưởng tượng những gì họ cần làm để thay đổi mã, những gì đang nằm xung quanh để cắn vào mông họ, và những thủ thuật nào làm cho mọi việc trở nên dễ dàng. Các lập trình viên có kinh nghiệm có một cảm giác tốt cho việc họ bị lừa như thế nào khi chủ sở hữu sản phẩm yêu cầu những thứ điên rồ.

Thực tế, tôi thấy rằng các bài kiểm tra đơn vị giúp đỡ ở đây. Nếu mã của bạn không linh hoạt, sẽ khó kiểm tra. Nếu bạn không thể tiêm giả hoặc dữ liệu kiểm tra khác, có lẽ bạn sẽ không thể tiêm SupportChinesemã đó .

Một số liệu thô khác là sân thang máy. Thang máy truyền thống là "nếu bạn đang ở trong thang máy với một nhà đầu tư, bạn có thể bán cho anh ta một ý tưởng không?". Các công ty khởi nghiệp cần có những mô tả đơn giản, ngắn gọn về những gì họ đang làm - trọng tâm của họ là gì. Tương tự như vậy, các lớp (và hàm) nên có một mô tả đơn giản về những gì chúng làm . Không phải "lớp này thực hiện một số fubar sao cho bạn có thể sử dụng nó trong các tình huống cụ thể này". Một cái gì đó bạn có thể nói với một nhà phát triển khác: "Lớp này tạo người dùng". Nếu bạn không thể truyền đạt điều đó đến các nhà phát triển khác, bạn sẽ gặp lỗi.


Đôi khi bạn đi để thực hiện những gì bạn nghĩ sẽ là một sự thay đổi lộn xộn, và nó trở nên đơn giản, hoặc một công cụ tái cấu trúc nhỏ làm cho nó đơn giản và thêm chức năng hữu ích cùng một lúc. Nhưng vâng, thường thì bạn có thể thấy rắc rối sắp tới.

16
Tôi là một người ủng hộ lớn cho ý tưởng "sân thang máy". Nếu thật khó để giải thích những gì một lớp làm trong một hoặc hai câu trong lãnh thổ rủi ro.
Ivan

1
Bạn chạm vào một điểm quan trọng: khả năng những kế hoạch điên rồ đó thay đổi đáng kể từ chủ sở hữu dự án này sang chủ sở hữu dự án tiếp theo. Bạn không chỉ dựa vào kinh nghiệm chung của bạn, mà còn dựa vào việc bạn biết rõ chủ sở hữu dự án như thế nào. Tôi đã làm việc cho những người muốn cắt giảm nước rút của chúng tôi xuống còn một tuần, và vẫn không thể tránh thay đổi hướng giữa nước rút.
Kevin Krumwiede

1
Ngoài những lợi ích rõ ràng, việc ghi lại mã của bạn bằng cách sử dụng "độ cao thang máy" cũng có tác dụng giúp bạn nghĩ về những gì mã của bạn đang sử dụng ngôn ngữ tự nhiên mà tôi thấy hữu ích trong việc khám phá nhiều trách nhiệm.
Alexander

1
@KevinKrumwiede Đó là những gì phương pháp "Gà chạy quanh với đầu bị cắt" và "Wild Goose Chase" dành cho!

26

Không ai biết. Hoặc ít nhất, chúng tôi không thể đồng ý về một định nghĩa. Đó là điều khiến cho XUÂN (và các nguyên tắc RẮN khác) gây tranh cãi.

Tôi sẽ tranh luận rằng có thể tìm ra những gì là hoặc không phải là trách nhiệm là một trong những kỹ năng mà nhà phát triển phần mềm phải học trong suốt sự nghiệp của mình. Càng viết nhiều mã và đánh giá, bạn sẽ càng có nhiều kinh nghiệm để xác định xem có gì đó đơn lẻ hay nhiều trách nhiệm không. Hoặc nếu trách nhiệm duy nhất bị phá vỡ trên các phần riêng biệt của mã.

Tôi cho rằng mục đích chính của SRP không phải là quy tắc cứng. Đó là để nhắc nhở chúng ta phải chú ý đến sự gắn kết trong mã và luôn luôn đặt một số nỗ lực có ý thức vào việc xác định mã nào là gắn kết và những gì không.


20
Các lập trình viên mới thường có xu hướng đối xử RẮN như thể đó là một bộ luật, điều này không có. Nó chỉ đơn thuần là một nhóm các ý tưởng tốt để giúp mọi người trở nên tốt hơn trong thiết kế lớp học. Than ôi, mọi người có xu hướng thực hiện các nguyên tắc này quá nghiêm trọng; Gần đây tôi đã thấy một bài đăng công việc trích dẫn RẮN là một trong những yêu cầu công việc.
Robert Harvey

9
+42 cho đoạn cuối. Như @RobertHarvey nói, những thứ như XUÂN, RẮN và YAGNI không nên được coi là " quy tắc tuyệt đối ", mà là nguyên tắc chung của "lời khuyên tốt". Giữa họ (và những người khác), lời khuyên đôi khi sẽ trái ngược nhau, nhưng việc cân bằng lời khuyên đó (trái với việc tuân theo một bộ quy tắc cứng nhắc) sẽ (theo thời gian, khi kinh nghiệm của bạn phát triển) sẽ hướng dẫn bạn sản xuất phần mềm tốt hơn. Chỉ nên có một "quy tắc tuyệt đối" trong phát triển phần mềm: " Không có quy tắc tuyệt đối ".
TripeHound

Điều này là rất rõ ràng về một khía cạnh của SRP. Nhưng, ngay cả khi các nguyên tắc RẮN không phải là quy tắc cứng, chúng sẽ không có giá trị khủng khiếp nếu không ai hiểu ý nghĩa của chúng - thậm chí còn ít hơn nếu tuyên bố của bạn rằng "không ai biết" là thực sự đúng! ... thật khó hiểu khi họ khó hiểu. Như với bất kỳ kỹ năng nào, có một cái gì đó phân biệt cái tốt với cái kém hơn ! Nhưng ... "không ai biết" làm cho nó trở thành một nghi thức tuyệt vời hơn. (Và tôi không tin đó là ý định của RẮN!)
svidgen

3
Bởi "Không ai biết", tôi hy vọng @Euphoric đơn giản có nghĩa là không có định nghĩa chính xác nào sẽ hoạt động cho mọi trường hợp sử dụng. Nó là một cái gì đó đòi hỏi một mức độ phán xét. Tôi nghĩ rằng một trong những cách tốt nhất để xác định trách nhiệm của bạn nằm ở đâu là lặp đi lặp lại nhanh chóng và để cho cơ sở mã hóa của bạn nói với bạn . Tìm "mùi" mà mã của bạn không dễ bảo trì. Chẳng hạn, khi một thay đổi đối với một quy tắc kinh doanh duy nhất bắt đầu có tầng ảnh hưởng thông qua các lớp dường như không liên quan, bạn có thể đã vi phạm SRP.
Michael L.

1
Tôi chân thành thứ hai @TripeHound và những người khác đã chỉ ra rằng tất cả các "quy tắc" này không tồn tại để xác định Tôn giáo phát triển đích thực, nhưng để tăng khả năng phát triển phần mềm có thể bảo trì. Hãy cảnh giác khi tuân theo "cách thực hành tốt nhất" nếu bạn không thể giải thích cách phần mềm quảng bá, cải thiện chất lượng hoặc tăng hiệu quả phát triển ..
Michael L.

5

Tôi nghĩ thuật ngữ "trách nhiệm" hữu ích như một phép ẩn dụ vì nó cho phép chúng tôi sử dụng phần mềm để điều tra xem phần mềm được tổ chức tốt như thế nào. Cụ thể, tôi tập trung vào hai nguyên tắc:

  • Trách nhiệm là tương xứng với thẩm quyền.
  • Không có hai thực thể phải chịu trách nhiệm cho cùng một điều.

Hai nguyên tắc này cho phép chúng tôi loại bỏ trách nhiệm một cách có ý nghĩa bởi vì chúng chơi với nhau. Nếu bạn đang trao quyền cho một đoạn mã để làm điều gì đó cho bạn, thì nó cần phải có trách nhiệm với những gì nó làm. Điều này gây ra trách nhiệm rằng một lớp có thể phải phát triển, mở rộng "một lý do để thay đổi" thành phạm vi rộng hơn và rộng hơn. Tuy nhiên, khi bạn làm cho mọi thứ rộng hơn, bạn tự nhiên bắt đầu gặp phải tình huống trong đó nhiều thực thể chịu trách nhiệm cho cùng một điều. Điều này có nhiều vấn đề trong trách nhiệm thực tế, vì vậy chắc chắn nó cũng là một vấn đề trong mã hóa. Kết quả là, nguyên tắc này làm cho phạm vi thu hẹp, khi bạn chia nhỏ trách nhiệm thành các bưu kiện không trùng lặp.

Ngoài hai điều này, một nguyên tắc thứ ba có vẻ hợp lý:

  • Trách nhiệm có thể được ủy quyền

Hãy xem xét một chương trình mới được đúc ... một bảng trống. Lúc đầu, bạn chỉ có một thực thể, đó là toàn bộ chương trình. Nó chịu trách nhiệm cho ... mọi thứ. Đương nhiên, tại một số điểm, bạn sẽ bắt đầu ủy thác trách nhiệm cho các chức năng hoặc các lớp. Tại thời điểm này, hai quy tắc đầu tiên có hiệu lực buộc bạn phải cân bằng trách nhiệm đó. Chương trình cấp cao nhất vẫn chịu trách nhiệm về đầu ra chung, giống như người quản lý chịu trách nhiệm về năng suất của nhóm của họ, nhưng mỗi thực thể phụ được giao trách nhiệm và có thẩm quyền thực hiện trách nhiệm đó.

Là một phần thưởng bổ sung, điều này làm cho RẮN tương thích đặc biệt với bất kỳ sự phát triển phần mềm công ty nào mà người ta có thể cần phải làm. Mọi công ty trên hành tinh đều có một số khái niệm về cách ủy thác trách nhiệm và tất cả họ đều không đồng ý. Nếu bạn ủy thác trách nhiệm trong phần mềm của mình theo cách gợi nhớ đến phái đoàn của chính công ty bạn, thì các nhà phát triển trong tương lai sẽ dễ dàng hơn trong việc tăng tốc với cách bạn làm việc tại công ty này.


Tôi không chắc chắn 100% điều này giải thích đầy đủ về nó. Nhưng, tôi nghĩ giải thích "trách nhiệm" liên quan đến "quyền lực" là một cách sâu sắc để diễn đạt nó! (+1)
svidgen

Pirsig nói: "Bạn có xu hướng xây dựng các vấn đề của mình vào máy", điều này khiến tôi tạm dừng.

@nocomprende Bạn cũng có xu hướng xây dựng thế mạnh của mình vào máy. Tôi lập luận rằng khi điểm mạnh và điểm yếu của bạn là những thứ giống nhau, thì đó là khi nó trở nên thú vị.
Cort Ammon

5

Trong hội nghị này tại Yale, chú Bob đưa ra ví dụ hài hước này :

Nhập mô tả hình ảnh ở đây

Ông nói rằng Employeecó ba lý do để thay đổi, ba nguồn yêu cầu thay đổi và đưa ra lời giải thích hài hước và tặc lưỡi này , nhưng dù sao cũng có tính minh họa:

  • Nếu CalcPay()phương pháp đó có lỗi và khiến công ty tốn hàng triệu đô la, CFO sẽ sa thải bạn .

  • Nếu ReportHours()phương pháp này có lỗi và khiến công ty tốn hàng triệu đô la, COO sẽ sa thải bạn .

  • Nếu WriteEmmployee(phương thức) có lỗi gây ra việc xóa rất nhiều dữ liệu và khiến công ty tốn hàng triệu đô la, CTO sẽ sa thải bạn .

Vì vậy, có ba trình thực thi cấp C khác nhau có khả năng sa thải bạn vì các lỗi tốn kém trong cùng một lớp có nghĩa là lớp có quá nhiều trách nhiệm.

Ông đưa ra giải pháp này để giải quyết vi phạm SRP, nhưng vẫn phải giải quyết vi phạm DIP không được hiển thị trong video.

Nhập mô tả hình ảnh ở đây


Ví dụ này trông giống như một lớp có trách nhiệm sai .
Robert Harvey

4
@RobertHarvey Khi một lớp có quá nhiều khả năng đáp ứng, điều đó có nghĩa là các khả năng phản hồi bổ sung là các phản hồi sai .
Tulains Córdova

5
Tôi nghe những gì bạn nói, nhưng tôi không thấy nó hấp dẫn. Có một sự khác biệt giữa một lớp có quá nhiều trách nhiệm và một lớp làm một việc mà nó không có hoạt động kinh doanh nào cả. Nghe có vẻ giống nhau, nhưng không phải; đếm đậu phộng không giống như gọi chúng là quả óc chó. Đó là nguyên tắc của chú Bob và ví dụ của chú Bob, nhưng nếu nó đủ mô tả, chúng tôi sẽ không cần câu hỏi này chút nào.
Robert Harvey

@RobertHarvey, sự khác biệt là gì? Những tình huống đó dường như là đẳng cấu đối với tôi.
Paul Draper

3

Tôi nghĩ rằng một cách tốt hơn để phân chia mọi thứ hơn là "lý do để thay đổi" là bắt đầu bằng cách suy nghĩ về việc liệu có yêu cầu mã đó cần thực hiện hai (hoặc nhiều) hành động hay không cần phải giữ một tham chiếu đối tượng riêng biệt cho mỗi hành động và liệu có hữu ích khi có một đối tượng công khai có thể thực hiện một hành động nhưng không phải là hành động khác hay không.

Nếu câu trả lời cho cả hai câu hỏi là có, điều đó sẽ gợi ý các hành động nên được thực hiện bởi các lớp riêng biệt. Nếu câu trả lời cho cả hai câu hỏi là không, điều đó sẽ gợi ý rằng từ quan điểm công khai nên có một lớp; nếu mã cho điều đó sẽ khó sử dụng, nó có thể được chia nhỏ bên trong thành các lớp riêng. Nếu câu trả lời cho câu hỏi đầu tiên là không, nhưng câu trả lời thứ hai là có, thì nên có một lớp riêng cho mỗi hành động cộng với một lớp tổng hợp bao gồm các tham chiếu đến các thể hiện của các hành động khác.

Nếu một người có các lớp riêng biệt cho bàn phím, máy nhắn tin, đọc số, máy in hóa đơn và ngăn kéo tiền mặt và không có lớp tổng hợp nào cho một máy tính tiền hoàn chỉnh, thì mã được cho là xử lý một giao dịch có thể vô tình bị gọi trong một cách lấy đầu vào từ bàn phím của một máy, tạo ra tiếng ồn từ tiếng bíp của máy thứ hai, hiển thị số trên màn hình của máy thứ ba, in biên lai trên máy in của máy thứ tư và bật ngăn kéo tiền mặt của máy thứ năm. Mỗi hàm phụ có thể được xử lý một cách hữu ích bởi một lớp riêng biệt, nhưng cũng cần có một lớp tổng hợp tham gia chúng. Lớp tổng hợp nên ủy thác càng nhiều logic cho các lớp cấu thành càng tốt,

Người ta có thể nói rằng "trách nhiệm" của mỗi lớp là kết hợp một số logic thực tế hoặc cách khác để cung cấp một điểm đính kèm chung cho nhiều lớp khác làm như vậy, nhưng điều quan trọng là tập trung đầu tiên và quan trọng nhất vào cách mã khách hàng nên xem một lớp. Nếu nó có ý nghĩa cho mã máy khách để xem một cái gì đó là một đối tượng, thì mã máy khách sẽ xem nó như là một đối tượng.


Đây là lời khuyên âm thanh. Có thể đáng để chỉ ra rằng bạn phân chia trách nhiệm theo nhiều tiêu chí hơn là chỉ SRP.
Jørgen Fogh

1
Tương tự xe hơi: Tôi không cần biết bao nhiêu xăng trong bình của người khác, hoặc muốn bật cần gạt nước của người khác. (nhưng đó là định nghĩa của Internet) (Suỵt! bạn sẽ phá hỏng câu chuyện)

1
@nocomprende - "Tôi không cần biết bao nhiêu xăng trong bình của người khác" - trừ khi bạn là một thiếu niên cố gắng quyết định "mượn" chiếc xe nào của gia đình cho chuyến đi tiếp theo của bạn ...;)
alephzero

3

SRP là khó để có được đúng. Đây chủ yếu là vấn đề gán 'công việc' cho mã của bạn và đảm bảo mỗi phần có trách nhiệm rõ ràng. Giống như trong cuộc sống thực, trong một số trường hợp, việc phân chia công việc giữa mọi người có thể khá tự nhiên, nhưng trong những trường hợp khác, điều đó có thể thực sự khó khăn, đặc biệt là nếu bạn không biết họ (hoặc công việc).

Tôi luôn khuyên bạn chỉ nên viết mã đơn giản hoạt động trước , sau đó cấu trúc lại một chút: Bạn sẽ có xu hướng xem cách cụm mã tự nhiên sau một thời gian. Tôi nghĩ rằng đó là một sai lầm khi buộc các trách nhiệm trước khi bạn biết mã (hoặc người) và công việc phải hoàn thành.

Một điều bạn sẽ nhận thấy là khi mô-đun bắt đầu làm quá nhiều và khó gỡ lỗi / bảo trì. Đây là thời điểm để tái cấu trúc; công việc cốt lõi nên là gì và những nhiệm vụ nào có thể được giao cho một mô-đun khác? Ví dụ, nó nên xử lý kiểm tra bảo mật và công việc khác, hay bạn nên kiểm tra bảo mật ở nơi khác trước, hay điều này sẽ làm cho mã phức tạp hơn?

Sử dụng quá nhiều chỉ dẫn và nó lại trở thành một mớ hỗn độn ... vì đối với các nguyên tắc khác, nguyên tắc này sẽ mâu thuẫn với những người khác, như KISS, YAGNI, v.v. Mọi thứ đều là vấn đề cân bằng.


Không phải SRP chỉ là sự gắn kết của Constantine lớn sao?
Nick Keighley

Bạn sẽ tự nhiên tìm thấy các mẫu đó nếu bạn viết mã đủ lâu, nhưng bạn có thể tăng tốc độ học tập bằng cách đặt tên cho chúng và nó giúp ích cho việc giao tiếp ...
Barshe Roussy

@NickKeighley Tôi nghĩ đó là sự gắn kết, không quá nhiều bài viết lớn, nhưng nhìn từ một góc độ khác.
sdenham

3

"Nguyên tắc trách nhiệm duy nhất" có lẽ là một cái tên khó hiểu. "Chỉ có một lý do để thay đổi" là một mô tả tốt hơn về nguyên tắc, nhưng vẫn dễ hiểu lầm. Chúng tôi không nói về nguyên nhân khiến các đối tượng thay đổi trạng thái khi chạy. Chúng tôi đang xem xét điều gì có thể khiến các nhà phát triển phải thay đổi mã trong tương lai.

Trừ khi chúng tôi đang sửa một lỗi, thay đổi sẽ là do yêu cầu kinh doanh mới hoặc thay đổi. Bạn sẽ phải suy nghĩ bên ngoài mã và tưởng tượng những yếu tố bên ngoài nào có thể khiến các yêu cầu thay đổi độc lập . Nói:

  • Thuế suất thay đổi do một quyết định chính trị.
  • Tiếp thị quyết định thay đổi tên của tất cả các sản phẩm
  • UI phải được thiết kế lại để có thể truy cập
  • Cơ sở dữ liệu bị tắc nghẽn, vì vậy bạn cần thực hiện một số tối ưu hóa
  • Bạn phải chứa một ứng dụng di động
  • và v.v.

Lý tưởng nhất là bạn muốn các yếu tố độc lập ảnh hưởng đến các lớp khác nhau. Ví dụ, vì thuế suất thay đổi độc lập với tên sản phẩm, các thay đổi sẽ không ảnh hưởng đến cùng một loại. Mặt khác, bạn có nguy cơ thay đổi thuế giới thiệu một lỗi trong đặt tên sản phẩm, đó là loại khớp nối chặt chẽ mà bạn muốn tránh với một hệ thống mô-đun.

Vì vậy, đừng tập trung vào những gì có thể thay đổi - mọi thứ có thể thay đổi có thể hình dung trong tương lai. Tập trung vào những gì có thể thay đổi độc lập . Thay đổi thường độc lập nếu chúng được gây ra bởi các tác nhân khác nhau.

Ví dụ của bạn với các chức danh công việc đang đi đúng hướng, nhưng bạn nên xem nó theo nghĩa đen hơn! Nếu tiếp thị có thể gây ra thay đổi cho mã và tài chính có thể gây ra những thay đổi khác, những thay đổi này sẽ không ảnh hưởng đến cùng một mã, vì đây là những chức danh công việc khác nhau theo nghĩa đen và do đó những thay đổi sẽ xảy ra độc lập.

Để trích dẫn chú Bob người đã phát minh ra thuật ngữ:

Khi bạn viết một mô-đun phần mềm, bạn muốn đảm bảo rằng khi các thay đổi được yêu cầu, những thay đổi đó chỉ có thể bắt nguồn từ một người, hoặc đúng hơn, một nhóm người được ghép chặt chẽ duy nhất đại diện cho một chức năng kinh doanh được xác định hẹp. Bạn muốn tách biệt các mô-đun của mình khỏi sự phức tạp của toàn bộ tổ chức và thiết kế các hệ thống của bạn sao cho mỗi mô-đun chịu trách nhiệm (đáp ứng) các nhu cầu của chỉ một chức năng kinh doanh đó.

Vì vậy, để tóm tắt: "trách nhiệm" là phục vụ cho một chức năng kinh doanh duy nhất. Nếu nhiều hơn một diễn viên có thể khiến bạn phải thay đổi một lớp, thì lớp đó có thể phá vỡ nguyên tắc này.


Theo cuốn sách "Kiến trúc sạch" của ông, điều này hoàn toàn chính xác. Các quy tắc kinh doanh nên đến từ một nguồn và chỉ một lần nguồn. Điều này có nghĩa là Nhân sự, Hoạt động và CNTT đều cần hợp tác xây dựng các yêu cầu trong một "Trách nhiệm duy nhất". Và đó là nguyên tắc. +1
Benny Skogberg

2

Một bài viết hay giải thích các nguyên tắc lập trình RẮN và đưa ra các ví dụ về mã cả sau và không tuân theo các nguyên tắc này là https://scotch.io/bar-talk/solid-the-first-five-principles-of-object-oriented- thiết kế .

Trong ví dụ liên quan đến SRP, ông đưa ra một ví dụ về một vài lớp hình dạng (hình tròn và hình vuông) và một lớp được thiết kế để tính tổng diện tích của nhiều hình dạng.

Trong ví dụ đầu tiên của mình, anh ta tạo lớp tính toán diện tích và nó trả lại kết quả đầu ra của mình dưới dạng HTML. Sau đó, anh quyết định muốn hiển thị nó dưới dạng JSON thay vào đó và phải thay đổi lớp tính toán diện tích của mình.

Vấn đề với ví dụ này là lớp tính toán diện tích của anh ta chịu trách nhiệm tính diện tích hình dạng VÀ hiển thị vùng đó. Sau đó, anh ta trải qua một cách tốt hơn để làm điều này bằng cách sử dụng một lớp khác được thiết kế đặc biệt để hiển thị các khu vực.

Đây là một ví dụ đơn giản (và dễ hiểu hơn khi đọc bài viết vì nó có đoạn mã) nhưng thể hiện ý tưởng cốt lõi của SRP.


0

Trước hết, những gì bạn có thực sự là hai vấn đề riêng biệt : vấn đề đặt phương thức nào vào các lớp của bạn và vấn đề phình to giao diện.

Giao diện

Bạn có giao diện này:

public Interface CustomerCRUD
{
  public void Create(Customer customer);
  public Customer Read(int CustomerID);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Có thể, bạn có nhiều lớp phù hợp với CustomerCRUDgiao diện (nếu không thì giao diện là không cần thiết) và một số chức năng do_crud(customer: CustomerCRUD)có trong một đối tượng phù hợp. Nhưng bạn đã phá vỡ SRP: bạn đã gắn bốn hoạt động riêng biệt này lại với nhau.

Hãy nói rằng sau này bạn sẽ hoạt động trên các khung nhìn cơ sở dữ liệu. Một quan điểm cơ sở dữ liệu có chỉ các Readphương pháp có sẵn cho nó. Nhưng bạn muốn viết một hàm do_query_stuff(customer: ???)mà các toán tử minh bạch trên các bảng hoặc khung nhìn toàn diện; Rốt cuộc Read, nó chỉ sử dụng phương pháp.

Vì vậy, tạo một giao diện

Giao diện công cộng CustomerReader {công khai Khách hàng đã đọc (customerID: int)}

và yếu tố CustomerCrudgiao diện của bạn là:

public interface CustomerCRUD extends CustomerReader
{
  public void Create(Customer customer);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Nhưng không có kết thúc trước mắt. Có thể có những vật thể mà chúng ta có thể tạo nhưng không cập nhật, v.v ... Lỗ thỏ này quá sâu. Cách duy nhất lành mạnh để tuân thủ nguyên tắc trách nhiệm duy nhất là làm cho tất cả các giao diện của bạn chứa chính xác một phương thức . Thực sự đi theo phương pháp này từ những gì tôi đã thấy, với phần lớn các giao diện chứa một chức năng duy nhất; Nếu bạn muốn chỉ định một giao diện có chứa hai chức năng, bạn phải lúng túng tạo một giao diện mới kết hợp cả hai. Bạn sớm nhận được một sự bùng nổ kết hợp của các giao diện.

Cách thoát khỏi mớ hỗn độn này là sử dụng phân nhóm cấu trúc (được triển khai trong ví dụ OCaml) thay vì các giao diện (là một dạng của phân nhóm danh nghĩa). Chúng tôi không xác định giao diện; thay vào đó, chúng ta chỉ cần viết một hàm

let do_customer_stuff customer = customer.read ... customer.update ...

mà gọi bất cứ phương pháp nào chúng ta thích. OCaml sẽ sử dụng suy luận kiểu để xác định rằng chúng ta có thể vượt qua trong bất kỳ đối tượng nào thực hiện các phương thức này. Trong ví dụ này, nó sẽ được xác định customercó loại <read: int -> unit, update: int -> unit, ...>.

Các lớp học

Điều này giải quyết sự lộn xộn giao diện ; nhưng chúng ta vẫn phải thực hiện các lớp có chứa nhiều phương thức. Ví dụ, chúng ta nên tạo ra hai lớp khác nhau, CustomerReaderCustomerWriter? Điều gì sẽ xảy ra nếu chúng ta muốn thay đổi cách đọc các bảng (ví dụ: bây giờ chúng ta lưu trữ các phản hồi của mình trong redis trước khi tìm nạp dữ liệu), nhưng bây giờ chúng được viết như thế nào? Nếu bạn làm theo này chuỗi các lập luận để kết luận hợp lý của nó, bạn đang dẫn chặt chẽ để lập trình chức năng :)


4
"Vô nghĩa" là một chút mạnh mẽ. Tôi có thể nhận được đằng sau "thần bí" hoặc "Zen." Nhưng, không bằng phẳng - vô nghĩa!
svidgen

Bạn có thể giải thích thêm một chút tại sao phân nhóm cấu trúc là một giải pháp?
Robert Harvey

@RobertHarvey Tái cấu trúc câu trả lời của tôi một cách đáng kể
vườn

4
Tôi sử dụng các giao diện ngay cả khi tôi chỉ có một lớp duy nhất thực hiện nó. Tại sao? Mocking trong bài kiểm tra đơn vị.
Eternal21

0

Trong tâm trí tôi, thứ gần gũi nhất với SRP xuất hiện trong đầu tôi là luồng sử dụng. Nếu bạn không có một luồng sử dụng rõ ràng cho bất kỳ lớp nào, có lẽ lớp của bạn có mùi thiết kế.

Luồng sử dụng sẽ là một cuộc gọi phương thức nhất định sẽ mang lại cho bạn kết quả mong đợi (do đó có thể kiểm tra được). Về cơ bản, bạn định nghĩa một lớp với các trường hợp sử dụng mà nó có IMHO, đó là lý do tại sao tất cả phương pháp chương trình tập trung vào các giao diện trong quá trình thực hiện.


0

Đó là để đạt được nhiều thay đổi yêu cầu, không yêu cầu thành phần của bạn thay đổi .

Nhưng chúc may mắn hiểu rằng thoạt nhìn, khi bạn lần đầu tiên nghe về RẮN.


Tôi thấy rất nhiều ý kiến ​​nói rằng SRPYAGNI có thể mâu thuẫn với nhau, nhưng YAGN tôi đã thi hành bởi TDD (GOOS, London School) đã dạy tôi nghĩ và thiết kế các thành phần của mình theo quan điểm của khách hàng. Tôi bắt đầu thiết kế các giao diện của mình bằng cách ít khách hàng nhất muốn nó làm, đó là cách nó nên làm . Và bài tập đó có thể được thực hiện mà không có bất kỳ kiến ​​thức nào về TDD.

Tôi thích kỹ thuật được mô tả bởi chú Bob (đáng buồn là tôi không thể nhớ từ đâu, đáng buồn thay), giống như:

Hãy tự hỏi, lớp học này làm gì?

Đã trả lời của bạn có chứa một trong hai hay Hoặc

Nếu vậy, trích ra một phần của câu trả lời, đó là trách nhiệm của chính nó

Kỹ thuật này là tuyệt đối, và như @svidgen đã nói, SRP là một lời kêu gọi phán xét, nhưng khi học một cái gì đó mới, tuyệt đối là tốt nhất, sẽ luôn dễ dàng hơn để luôn luôn làm một cái gì đó. Hãy chắc chắn rằng lý do bạn không tách rời là; một ước tính có giáo dục, và không phải vì bạn không biết làm thế nào. Đây là nghệ thuật, và nó cần kinh nghiệm.


Tôi nghĩ rằng rất nhiều câu trả lời dường như đưa ra một lập luận cho việc tách rời khi nói về SRP .

SRPkhông để đảm bảo một sự thay đổi không tuyên truyền xuống đồ thị phụ thuộc.

Về mặt lý thuyết, không có SRP , bạn sẽ không có bất kỳ sự phụ thuộc nào ...

Một thay đổi sẽ không gây ra thay đổi nhiều nơi trong ứng dụng, nhưng chúng tôi có các nguyên tắc khác cho điều đó. SRP tuy nhiên, cải thiện Nguyên tắc Đóng mở . Nguyên tắc này là nhiều hơn về trừu tượng, tuy nhiên, trừu tượng nhỏ hơn dễ thực hiện lại .

Vì vậy, khi dạy toàn bộ RẮN, hãy cẩn thận để dạy rằng SRP cho phép bạn thay đổi ít mã hơn khi các yêu cầu thay đổi, trong thực tế, nó cho phép bạn viết ít mã mới hơn .


3
When learning something new, absolutes are the best, it is easier to just always do something.- Theo kinh nghiệm của tôi, các lập trình viên mới quá xa cách giáo điều. Chủ nghĩa tuyệt đối dẫn đến các nhà phát triển không suy nghĩ và lập trình sùng bái hàng hóa. Nói "chỉ cần làm điều này" là tốt, miễn là bạn hiểu rằng người bạn đang nói chuyện sẽ phải sau đó không học những gì bạn đã dạy họ.
Robert Harvey

@RobertHarvey, Hoàn toàn đúng, nó tạo ra hành vi giáo điều và bạn phải học / học lại khi bạn có kinh nghiệm. Đây là quan điểm của tôi mặc dù. Nếu một lập trình viên mới cố gắng thực hiện các cuộc gọi phán xét mà không có cách nào để đưa ra quyết định của họ thì dường như đường biên đó vô dụng, vì họ không biết tại sao nó hoạt động, khi nó hoạt động. Bằng cách làm cho mọi người làm quá nó, nó dạy họ tìm kiếm các ngoại lệ thay vì đưa ra những phỏng đoán không đủ tiêu chuẩn. Tất cả những gì bạn nói là về chủ nghĩa tuyệt đối là chính xác, đó là lý do tại sao nó chỉ nên là điểm khởi đầu.
Chris Wohlert

@RobertHarvey, Một ví dụ thực tế nhanh chóng : Bạn có thể dạy con bạn luôn trung thực, nhưng khi chúng lớn lên, chúng có thể sẽ nhận ra một vài ngoại lệ nơi mọi người không muốn nghe những suy nghĩ trung thực nhất của chúng. Mong đợi một đứa trẻ 5 tuổi đưa ra một phán đoán chính xác về việc trung thực là tốt nhất là lạc quan. :)
Chris Wohlert

0

Không có câu trả lời rõ ràng cho điều đó. Mặc dù câu hỏi hẹp, nhưng lời giải thích không có.

Đối với tôi, nó là thứ gì đó giống như Occam's Razor nếu bạn muốn. Đó là một lý tưởng nơi tôi cố gắng đo mã hiện tại của mình. Thật khó để đóng đinh nó bằng những từ đơn giản và đơn giản. Một phép ẩn dụ khác sẽ là »một chủ đề« mang tính trừu tượng, nghĩa là khó nắm bắt, như »trách nhiệm duy nhất«. Một mô tả thứ ba sẽ là »xử lý một mức độ trừu tượng«.

Điều đó có nghĩa gì thực tế?

Gần đây tôi sử dụng một phong cách mã hóa bao gồm chủ yếu hai giai đoạn:

Giai đoạn I được mô tả tốt nhất là sự hỗn loạn sáng tạo. Trong giai đoạn này tôi viết mã xuống khi những suy nghĩ đang trôi chảy - tức là thô và xấu.

Giai đoạn II là hoàn toàn ngược lại. Nó giống như dọn dẹp sau một cơn bão. Điều này mất nhiều công việc và kỷ luật nhất. Và sau đó tôi nhìn vào mã từ quan điểm của một nhà thiết kế.

Bây giờ tôi đang làm việc chủ yếu bằng Python, điều này cho phép tôi nghĩ về các đối tượng và các lớp sau này. Đầu tiên Giai đoạn I - tôi viết chỉ có chức năng và lây lan cho họ gần như bằng ngẫu nhiên trong module khác nhau. Trong Giai đoạn II , sau khi tôi thực hiện mọi thứ, tôi sẽ xem xét kỹ hơn về mô-đun nào liên quan đến phần nào của giải pháp. Và trong khi lướt qua các mô-đun, chủ đề nổi lên với tôi. Một số chức năng có liên quan theo chủ đề. Đây là những ứng cử viên tốt cho các lớp học . Và sau khi tôi biến các hàm thành các lớp - gần như được thực hiện bằng cách thụt lề và thêm selfvào danh sách tham số trong python;) - Tôi sử dụng SRPnhư Occam's Razor để loại bỏ chức năng cho các mô-đun và lớp khác.

Một ví dụ hiện tại có thể là viết chức năng xuất khẩu nhỏ vào ngày khác.

Có nhu cầu về csv , excelcác bảng excel kết hợp trong một zip.

Các chức năng đơn giản được thực hiện trong ba chế độ xem (= chức năng). Mỗi hàm sử dụng một phương thức chung để xác định các bộ lọc và phương thức thứ hai để lấy dữ liệu. Sau đó, trong mỗi chức năng, việc chuẩn bị xuất khẩu đã diễn ra và được gửi dưới dạng Phản hồi từ máy chủ.

Có quá nhiều mức độ trừu tượng lẫn lộn:

I) xử lý yêu cầu / phản hồi đến / đi

II) xác định bộ lọc

III) truy xuất dữ liệu

IV) chuyển đổi dữ liệu

Bước dễ dàng là sử dụng một trừu tượng ( exporter) để đối phó với các lớp II-IV trong bước đầu tiên.

Điều duy nhất còn lại là chủ đề xử lý các yêu cầu / phản hồi . Ở cùng một mức độ trừu tượng là trích xuất các tham số yêu cầu là được. Vì vậy, tôi đã cho quan điểm này một "trách nhiệm".

Thứ hai, tôi đã phải chia tay nhà xuất khẩu, mà như chúng ta thấy bao gồm ít nhất ba lớp trừu tượng khác.

Xác định tiêu chí lọc và thực tế retrival gần trên cùng một mức độ trừu tượng (các bộ lọc là cần thiết để có được những tập hợp con bên phải của dữ liệu). Các cấp độ này đã được đưa vào một cái gì đó giống như một lớp truy cập dữ liệu .

Trong bước tiếp theo, tôi đã tách các cơ chế xuất thực tế ra: Khi cần ghi vào tệp tạm thời, tôi đã chia nó thành hai "trách nhiệm": một cho việc ghi dữ liệu thực tế vào đĩa và một phần khác xử lý định dạng thực.

Cùng với sự hình thành của các lớp và mô-đun, mọi thứ trở nên rõ ràng hơn, những gì thuộc về nơi. Và luôn luôn là câu hỏi tiềm ẩn, cho dù lớp học làm quá nhiều .

Làm thế nào để bạn xác định trách nhiệm mà mỗi lớp nên có và làm thế nào để bạn xác định trách nhiệm trong bối cảnh của SRP?

Thật khó để đưa ra một công thức để làm theo. Tất nhiên tôi có thể lặp lại mật mã »một mức độ trừu tượng« - quy tắc nếu điều đó có ích.

Chủ yếu đối với tôi đó là một loại "trực giác nghệ thuật" dẫn đến thiết kế hiện tại; Tôi mô hình mã như một nghệ sĩ có thể điêu khắc đất sét hoặc vẽ tranh.

Hãy tưởng tượng tôi là một Mã hóa Bob Ross ;)


0

Những gì tôi cố gắng làm để viết mã theo SRP:

  • Chọn một vấn đề cụ thể bạn cần giải quyết;
  • Viết mã giải quyết nó, viết mọi thứ trong một phương thức (ví dụ: main);
  • Phân tích cẩn thận mã và, dựa trên doanh nghiệp, cố gắng xác định các trách nhiệm có thể nhìn thấy trong tất cả các hoạt động đang được thực hiện (đây là phần chủ quan cũng phụ thuộc vào doanh nghiệp / dự án / khách hàng);
  • Xin lưu ý rằng tất cả các chức năng đã được thực hiện; những gì tiếp theo chỉ là tổ chức của mã (không có tính năng hoặc cơ chế bổ sung nào sẽ được triển khai từ bây giờ trong phương pháp này);
  • Dựa trên các trách nhiệm mà bạn đã xác định trong các bước trước (được xác định dựa trên doanh nghiệp và ý tưởng "một lý do để thay đổi"), trích xuất một lớp hoặc phương thức riêng cho từng người;
  • Xin lưu ý rằng phương pháp này chỉ quan tâm đến XUÂN; lý tưởng là nên có các bước bổ sung ở đây cố gắng tuân thủ các nguyên tắc khác.

Thí dụ:

Vấn đề: nhận hai số từ người dùng, tính tổng của họ và xuất kết quả cho người dùng:

//first step: solve the problem right away
static void Main(string[] args)
{
    Console.WriteLine("Number 1: ");
    int firstNumber = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("Number 2: ");
    int secondNumber = Convert.ToInt32(Console.ReadLine());

    int result = firstNumber + secondNumber;

    Console.WriteLine("Hi there! The result is: {0}", result);

    Console.ReadLine();
}

Tiếp theo, cố gắng xác định trách nhiệm dựa trên các nhiệm vụ cần được thực hiện. Từ đó, trích xuất các lớp thích hợp:

//Responsible for getting two integers from the user
class Input {
    public int FirstNumber { get; set; }
    public int SecondNumber { get; set; }
    public void Read() {
        Console.WriteLine("Number 1: ");
        FirstNumber = Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("Number 2: ");
        SecondNumber = Convert.ToInt32(Console.ReadLine());
    }
}

//Responsible for calculating the sum of two integers
class SumOperation {
    public int Result { get; set; }
    public void Calculate(int a, int b) {
        Result = a + b;
    }
}

//Responsible for the output of some value to the user
class Output {
    public void Write(int result) {
        Console.WriteLine("Hello! The result is: {0}", result);
    }
}

Sau đó, chương trình tái cấu trúc trở thành:

//Program: responsible for main execution.
//Gets two numbers from user and output their sum.
static void Main(string[] args)
{
    var input = new Input();
    input.Read();

    var operation = new SumOperation();
    operation.Calculate(input.FirstNumber, input.SecondNumber);

    var output = new Output();
    output.Write(operation.Result);

    Console.ReadLine();
}

Lưu ý: ví dụ rất đơn giản này chỉ xem xét nguyên tắc SRP. Việc sử dụng các nguyên tắc khác (ví dụ: "L" - mã phải phụ thuộc vào trừu tượng hơn là cụ thể hóa) sẽ mang lại nhiều lợi ích hơn cho mã và giúp nó dễ duy trì hơn cho các thay đổi kinh doanh.


1
Ví dụ của bạn quá đơn giản để minh họa đầy đủ SRP. Không ai sẽ làm điều này trong cuộc sống thực.
Robert Harvey

Vâng, trong các dự án thực tế, tôi viết một số mã giả thay vì viết mã chính xác như trong ví dụ của tôi. Sau mã giả, tôi cố gắng phân chia các trách nhiệm giống như tôi đã làm trong ví dụ. Dù sao, đó chỉ là cách tôi làm điều đó.
Emerson Cardoso

0

Từ cuốn sách của Robert C. Martins Kiến trúc sạch: Hướng dẫn của thợ thủ công về thiết kế và cấu trúc phần mềm , xuất bản ngày 10 tháng 9 năm 2017, Robert viết trên trang 62 như sau:

Trong lịch sử, SRP đã được mô tả theo cách này:

Một mô-đun nên có một, và chỉ một, lý do để thay đổi

Hệ thống phần mềm được thay đổi để đáp ứng người dùng và các bên liên quan; những người dùng và các bên liên quan "lý do để thay đổi". mà nguyên tắc đang nói đến. Thật vậy, chúng ta có thể viết lại nguyên tắc để nói điều này:

Một mô-đun phải chịu trách nhiệm với một và chỉ một, người dùng hoặc các bên liên quan

Thật không may, từ "người dùng" và "các bên liên quan" không thực sự là từ thích hợp để sử dụng ở đây. Có thể sẽ có nhiều hơn một người dùng hoặc các bên liên quan muốn hệ thống thay đổi theo cách lành mạnh. Thay vào đó, chúng tôi thực sự đề cập đến một nhóm - một hoặc nhiều người yêu cầu thay đổi đó. Chúng tôi sẽ đề cập đến nhóm đó như là một diễn viên .

Do đó, phiên bản cuối cùng của SRP là:

Một mô-đun phải chịu trách nhiệm với một, và chỉ một, diễn viên.

Vì vậy, đây không phải là về mã. SRP là về việc kiểm soát luồng yêu cầu và nhu cầu kinh doanh, vốn chỉ có thể đến từ một soure.


Tôi không chắc tại sao bạn lại phân biệt rằng "đó không phải là về mã." Tất nhiên đó là về mã; đây là sự phát triển phần mềm
Robert Harvey

@RobertHarvey Quan điểm của tôi là luồng yêu cầu đến từ một nguồn, diễn viên. Người dùng và các bên liên quan không tham gia vào mã, họ tuân thủ các quy tắc kinh doanh mà chúng tôi yêu cầu. Vì vậy, SRP là một quá trình để kiểm soát các yêu cầu này, mà theo tôi không phải là mã. Đó là Phát triển phần mềm (!), Nhưng không phải mã.
Benny Skogberg
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.