MVC: Bộ điều khiển có phá vỡ Nguyên tắc Trách nhiệm Đơn lẻ không?


16

Nguyên tắc trách nhiệm duy nhất nói rằng "một lớp nên có một lý do thay đổi".

Trong mẫu MVC, công việc của Trình điều khiển là làm trung gian giữa Chế độ xem và Mô hình. Nó cung cấp giao diện cho Chế độ xem để báo cáo các hành động do người dùng thực hiện trên GUI (ví dụ: cho phép Xem để gọi controller.specificButtonPressed()) và có thể gọi các phương thức thích hợp trên Mô hình để thao tác dữ liệu của nó hoặc gọi các hoạt động của nó (ví dụ model.doSomething()) .

Điều này có nghĩa rằng:

  • Bộ điều khiển cần biết về GUI, để cung cấp Giao diện Xem phù hợp để báo cáo hành động của người dùng.
  • Nó cũng cần biết về logic trong Mô hình, để có thể gọi các phương thức thích hợp trên Mô hình.

Điều đó có nghĩa là có hai lý do để thay đổi : thay đổi GUI và thay đổi logic buisness.

Nếu GUI thay đổi, ví dụ: một nút mới được thêm vào, Bộ điều khiển có thể cần thêm một phương thức mới để cho phép View báo cáo người dùng nhấn vào nút này.

Và nếu logic nghiệp vụ trong Mô hình thay đổi, Bộ điều khiển có thể phải thay đổi để gọi các phương thức chính xác trên Mô hình.

Do đó, Bộ điều khiển có hai lý do có thể thay đổi . Nó có phá vỡ SRP không?


2
Bộ điều khiển là đường 2 chiều, đây không phải là cách tiếp cận từ trên xuống hoặc từ dưới lên điển hình của bạn. Không có khả năng để nó trừu tượng hóa một trong những phụ thuộc của nó bởi vì bộ điều khiển là chính sự trừu tượng hóa. Do tính chất của mẫu, không thể tuân thủ SRP ở đây. Tóm lại: có, nó vi phạm SRP nhưng đó là điều không thể tránh khỏi.
Jeroen Vannevel

1
Điểm của câu hỏi là gì? Nếu tất cả chúng ta trả lời "có, nó có", thì sao? Nếu câu trả lời là "không" thì sao? Vấn đề thực sự bạn đang cố gắng giải quyết với câu hỏi này là gì?
Bryan Oakley

1
"một lý do để thay đổi" không có nghĩa là "mã thay đổi". Nếu bạn tạo bảy lỗi chính tả trong tên biến của mình cho lớp, thì lớp đó có 7 trách nhiệm không? Không. Nếu bạn có nhiều hơn một biến hoặc nhiều hơn một hàm, bạn cũng có thể chỉ có một trách nhiệm duy nhất.
Bob

Câu trả lời:


14

Do đó, nếu bạn tiếp tục lý do về SRP, bạn sẽ nhận thấy rằng "phản hồi đơn lẻ" thực sự là một thuật ngữ xốp. Bộ não con người của chúng ta bằng cách nào đó có thể phân biệt giữa các trách nhiệm khác nhau và nhiều trách nhiệm có thể được trừu tượng hóa thành một trách nhiệm "chung". Ví dụ, hãy tưởng tượng trong một gia đình 4 người thông thường có một thành viên familiy chịu trách nhiệm làm bữa sáng. Bây giờ, để làm điều này, người ta phải luộc trứng và bánh mì nướng và tất nhiên là thiết lập một vài tách trà xanh tốt cho sức khỏe (vâng, trà xanh là tốt nhất). Bằng cách này, bạn có thể chia "làm bữa sáng" thành những phần nhỏ hơn được trừu tượng hóa thành "làm bữa sáng". Lưu ý rằng mỗi phần cũng là một trách nhiệm có thể được ủy thác cho người khác.

Quay lại với MVC: nếu làm trung gian giữa mô hình và xem ist không phải là một trách nhiệm mà là hai, thì lớp trừu tượng tiếp theo ở trên, kết hợp cả hai là gì? Nếu bạn không thể tìm thấy một trong những bạn không trừu tượng chính xác hoặc không có gì có nghĩa là bạn đã làm đúng. Và tôi cảm thấy đó là trường hợp với bộ điều khiển, xử lý khung nhìn và mô hình.


1
Cũng như một ghi chú thêm. Nếu bạn có bộ điều khiển.make Breakfast () và control.wakeUpF Family () thì bộ điều khiển đó sẽ phá vỡ SRP, nhưng không phải vì nó là bộ điều khiển, chỉ vì nó có nhiều trách nhiệm.
Bob

Cảm ơn bạn đã trả lời, không chắc chắn tôi đang theo dõi bạn. Bạn có đồng ý rằng bộ điều khiển có nhiều hơn một trách nhiệm? Lý do tôi nghĩ vậy là vì nó có hai lý do để thay đổi (tôi nghĩ): một sự thay đổi trong mô hình và một sự thay đổi trong quan điểm. Bạn có đồng ý với điều này?
Manila Cohn

1
Có, tôi có thể đồng ý rằng bộ điều khiển có nhiều trách nhiệm. Tuy nhiên đây là những trách nhiệm "thấp hơn" và đó không phải là vấn đề vì ở mức độ trừu tượng cao nhất, nó chỉ có một trách nhiệm (kết hợp những trách nhiệm thấp hơn mà bạn đề cập) và do đó nó không vi phạm SRP.
valenterry

1
Tìm mức độ trừu tượng phù hợp chắc chắn là quan trọng. Ví dụ "làm bữa sáng" là một ví dụ hay - để hoàn thành một trách nhiệm duy nhất, thường có một số nhiệm vụ phải hoàn thành. Miễn là bộ điều khiển chỉ sắp xếp các tác vụ này, nó tuân theo SRP. Nhưng nếu nó biết quá nhiều về việc luộc trứng, làm bánh mì nướng hoặc pha trà, thì nó sẽ vi phạm SRP.
Allan

Câu trả lời này có ý nghĩa với tôi. Cảm ơn bạn Valenterry.
J86

9

Nếu một lớp có "hai lý do có thể thay đổi", thì có, nó vi phạm SRP.

Một bộ điều khiển thường phải nhẹ và có trách nhiệm duy nhất là thao tác miền / mô hình để đáp ứng với một số sự kiện do GUI điều khiển. Chúng ta có thể xem xét từng thao tác này về cơ bản là các trường hợp sử dụng hoặc tính năng.

Nếu một nút mới được thêm vào GUI, bộ điều khiển chỉ phải thay đổi nếu nút mới đó thể hiện một số tính năng mới (nghĩa là trái ngược với cùng một nút tồn tại trên màn hình 1 nhưng chưa tồn tại trên màn hình 2, và sau đó nó đã tồn tại thêm vào màn hình 2). Cũng cần phải có một thay đổi mới tương ứng trong mô hình, để hỗ trợ chức năng / tính năng mới này. Bộ điều khiển vẫn có trách nhiệm thao tác tên miền / mô hình để đáp ứng với một số sự kiện do GUI điều khiển.

Nếu logic nghiệp vụ trong mô hình thay đổi do lỗi được sửa và nó yêu cầu bộ điều khiển thay đổi, thì đây là trường hợp đặc biệt (hoặc có lẽ mô hình đang vi phạm tiền gốc đóng mở). Nếu logic nghiệp vụ trong mô hình thay đổi để hỗ trợ một số chức năng / tính năng mới, thì điều đó không nhất thiết ảnh hưởng đến bộ điều khiển - chỉ khi bộ điều khiển cần phơi bày tính năng đó (mà hầu như luôn luôn là như vậy, nếu không thì tại sao nó lại được thêm vào mô hình miền nếu nó không được sử dụng). Vì vậy, trong trường hợp này, bộ điều khiển cũng phải được sửa đổi, để hỗ trợ thao tác mô hình miền theo cách mới này để đáp ứng với một số sự kiện do GUI điều khiển.

Nếu bộ điều khiển phải thay đổi bởi vì, giả sử, lớp kiên trì được thay đổi từ tệp phẳng sang cơ sở dữ liệu, thì bộ điều khiển chắc chắn vi phạm SRP. Nếu bộ điều khiển luôn hoạt động ở cùng một lớp trừu tượng, thì điều đó có thể giúp đạt được SRP.


4

Bộ điều khiển không vi phạm SRP. Như bạn nêu, trách nhiệm của nó là làm trung gian giữa các mô hình và khung nhìn.

Điều đó đang được nói, vấn đề với ví dụ của bạn là bạn đang buộc các phương thức điều khiển vào logic trong khung nhìn, tức là controller.specificButtonPressed. Đặt tên cho các phương thức theo cách này liên kết bộ điều khiển với GUI của bạn, bạn đã thất bại với những thứ trừu tượng đúng cách. Bộ điều khiển nên về việc thực hiện các hành động cụ thể, tức là controller.saveDatahoặc controller.retrieveEntry. Thêm một nút mới trong GUI không nhất thiết có nghĩa là thêm một phương thức mới vào bộ điều khiển.

Nhấn một nút trong chế độ xem, có nghĩa là để làm một cái gì đó nhưng bất cứ điều gì có thể dễ dàng được kích hoạt trong bất kỳ số lượng khác hoặc thậm chí không thông qua chế độ xem.

Từ bài viết Wikipedia về SRP

Martin định nghĩa một trách nhiệm là một lý do để thay đổi và kết luận rằng một lớp hoặc mô-đun nên có một, và chỉ một, lý do để thay đổi. Ví dụ, xem xét một mô-đun biên dịch và in báo cáo. Một mô-đun như vậy có thể được thay đổi vì hai lý do. Đầu tiên, nội dung của báo cáo có thể thay đổi. Thứ hai, định dạng của báo cáo có thể thay đổi. Hai điều này thay đổi vì những nguyên nhân rất khác nhau; một thực chất, và một mỹ phẩm. Nguyên tắc trách nhiệm duy nhất nói rằng hai khía cạnh của vấn đề này thực sự là hai trách nhiệm riêng biệt, và do đó nên ở trong các lớp hoặc mô-đun riêng biệt. Sẽ là một thiết kế tồi khi kết hợp hai thứ thay đổi vì những lý do khác nhau vào những thời điểm khác nhau.

Bộ điều khiển không quan tâm đến những gì trong chế độ xem chỉ khi một trong các phương thức của nó được gọi là nó cung cấp dữ liệu được chỉ định cho chế độ xem. Nó chỉ cần biết về chức năng của mô hình cho đến khi nó biết rằng nó cần gọi các phương thức mà chúng sẽ có. Nó không biết gì hơn thế.

Biết rằng một đối tượng có một phương thức có sẵn để gọi không giống như biết chức năng của nó.


1
Lý do tôi nghĩ bộ điều khiển nên bao gồm các phương thức như specificButtonsPressed()là vì tôi đọc rằng chế độ xem không biết gì về chức năng của các nút và các thành phần GUI khác. Tôi đã được dạy rằng khi nhấn nút, chế độ xem chỉ cần báo cáo cho bộ điều khiển và bộ điều khiển sẽ quyết định 'ý nghĩa của nó' (và sau đó gọi các phương thức thích hợp trên mô hình). Thực hiện cuộc gọi xem controller.saveData()có nghĩa là chế độ xem phải biết về ý nghĩa của nút này, bên cạnh thực tế là nó được nhấn.
Manila Cohn

1
Nếu tôi bỏ ý tưởng phân tách hoàn toàn giữa một lần nhấn nút và ý nghĩa của nó (điều này dẫn đến bộ điều khiển có các phương thức như thế nào specificButtonPressed()), thì thực sự bộ điều khiển sẽ không bị ràng buộc với GUI rất nhiều. Tôi có nên bỏ các specificButtonPressed()phương pháp? Liệu lợi thế tôi nghĩ rằng có những phương pháp này có ý nghĩa với bạn? Hoặc là có buttonPressed()các phương thức trong bộ điều khiển không có giá trị rắc rối?
Manila Cohn

1
Nói cách khác (xin lỗi vì những bình luận dài): Tôi nghĩ rằng lợi thế của việc có specificButtonPressed()các phương thức trong bộ điều khiển, là nó tách biệt Chế độ xem với ý nghĩa của việc nhấn nút hoàn toàn . Tuy nhiên, nhược điểm là nó liên kết bộ điều khiển với GUI theo một nghĩa nào đó. Cách tiếp cận nào tốt hơn?
Manila Cohn

@prog IMO Bộ điều khiển nên bị mù khi xem. Một nút sẽ có một số loại chức năng nhưng nó không cần biết chi tiết về nó. Nó chỉ cần biết rằng nó đang gửi một số dữ liệu đến một phương thức điều khiển. Về vấn đề tên không quan trọng. Nó có thể được gọi foo, hoặc chỉ là dễ dàng fireZeMissiles. Nó sẽ chỉ biết rằng nó nên báo cáo cho một chức năng cụ thể. Nó không biết chức năng nào sẽ chỉ gọi nó. Bộ điều khiển không quan tâm đến cách các phương thức của nó được gọi chỉ là nó sẽ đáp ứng theo cách cư xử nhất định khi có.
Schleis

1

Một trách nhiệm duy nhất của người kiểm soát là hợp đồng làm trung gian giữa chế độ xem và mô hình. Chế độ xem chỉ chịu trách nhiệm cho màn hình, mô hình chỉ chịu trách nhiệm về logic nghiệp vụ. Đó là trách nhiệm của người kiểm soát để kết nối hai trách nhiệm đó.

Đó là tất cả tốt và tốt nhưng để tránh xa học viện một chút; một bộ điều khiển trong MVC thường bao gồm nhiều phương thức hành động nhỏ hơn. Những hành động này thường tương ứng với những thứ mà một điều có thể làm. Nếu tôi đang bán sản phẩm, có lẽ tôi sẽ có ProductContoder. Bộ điều khiển đó sẽ có các hành động như GetReview, ShowSpecs, AddToCart ...

Chế độ xem có SRP hiển thị giao diện người dùng và một phần của giao diện người dùng đó bao gồm nút có tên AddToCart.

Bộ điều khiển có SRP để biết tất cả các Chế độ xem và Mô hình liên quan đến quy trình.

Bộ điều khiển AddToCart Action có SRP cụ thể để biết mọi người cần tham gia khi một mặt hàng được thêm vào giỏ hàng.

Mô hình sản phẩm có SRP của mô hình logic sản phẩm và Mô hình ShoppingCart có SRP mô hình hóa cách các mục được lưu để kiểm tra sau này. Mô hình người dùng có SRP mô hình hóa người dùng đang thêm nội dung vào giỏ hàng của họ.

Bạn có thể và nên sử dụng lại các mô hình để hoàn thành công việc của mình và các mô hình đó cần được ghép nối tại một số điểm trong mã của bạn. Bộ điều khiển kiểm soát từng cách duy nhất mà khớp nối xảy ra.


0

Bộ điều khiển thực sự chỉ có một trách nhiệm: thay đổi trạng thái ứng dụng dựa trên đầu vào của người dùng.

Bộ điều khiển có thể gửi lệnh đến mô hình để cập nhật trạng thái của mô hình (ví dụ: chỉnh sửa tài liệu). Nó cũng có thể gửi các lệnh đến chế độ xem được liên kết của nó để thay đổi cách trình bày mô hình của chế độ xem (ví dụ: bằng cách cuộn qua tài liệu).

source: wikipedia

Nếu thay vào đó, bạn đang có "bộ điều khiển" kiểu Rails (xử lý các trường hợp bản ghi hoạt động và các mẫu câm) , thì tất nhiên sẽ phá vỡ SRP.

Sau đó, một lần nữa, các ứng dụng kiểu Rails không thực sự là MVC để bắt đầu.

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.