MVC : 컨트롤러가 단일 책임 원칙을 위반합니까?


16

단일 책임 원칙에 따르면 "클래스에는 변화가 한 가지 이유가 있어야합니다".

MVC 패턴에서 컨트롤러의 작업은 뷰와 모델 사이를 중재하는 것입니다. 그것은 (예를 들어 통화에보기를 허용 GUI에서 사용자가 만든 보고서 작업을보기위한 인터페이스를 제공합니다 controller.specificButtonPressed()(예를 들어)를, 그리고 그것의 데이터를 조작하기 위해 또는 그것의 작업을 호출하는 모델의 해당 메소드를 호출 할 수 있습니다 model.doSomething()) .

이것은 다음을 의미합니다.

  • View에 사용자 조치를보고하기에 적합한 인터페이스를 제공하려면 컨트롤러가 GUI에 대해 알아야합니다.
  • 또한 모델에서 적절한 메소드를 호출하려면 모델의 논리에 대해 알아야합니다.

즉 , GUI 변경과 비즈니스 로직 변경 이라는 두 가지 이유가 있습니다 .

GUI가 변경되면 (예 : 새 버튼 추가) Controller가 View에 사용자가이 버튼을 눌렀 음을보고 할 수 있도록 새로운 방법을 추가해야 할 수도 있습니다.

그리고 모델의 비즈니스 로직이 변경되면 모델에서 올바른 메소드를 호출하기 위해 컨트롤러를 변경해야 할 수도 있습니다.

따라서 컨트롤러에는 변경해야 할 두 가지 이유가 있습니다 . SRP가 깨 집니까?


2
컨트롤러는 양방향 거리이며 일반적인 하향식 또는 상향식 접근 방식이 아닙니다. 컨트롤러가 추상화 자체이기 때문에 종속 항목 중 하나를 추상화 할 가능성이 없습니다. 패턴의 특성으로 인해 SRP를 준수 할 수 없습니다. 요컨대, 그것은 SRP를 위반하지만 피할 수는 없습니다.
Jeroen Vannevel

1
질문의 요점은 무엇입니까? 우리 모두가 "그렇다"라고 대답하면 어떻게됩니까? 대답이 "아니오"이면 어떻게됩니까? 이 질문으로 해결하려는 실제 문제는 무엇입니까?
Bryan Oakley

1
"변경 이유"는 "변경되는 코드"를 의미하지 않습니다. 클래스의 변수 이름에 오타를 7 번 입력하면 해당 클래스에 7 개의 책임이 있습니까? 아니요. 둘 이상의 변수 또는 둘 이상의 함수가있는 경우에도 단일 책임 만있을 수 있습니다.

답변:


14

결과적으로 SRP에 대한 추론을 계속하면 "단일 책임"이 실제로 해로운 용어임을 알게 될 것입니다. 우리의 인간 두뇌는 어떻게 다른 책임을 구별 할 수 있으며 여러 책임을 하나의 "일반적인"책임으로 추상화 할 수 있습니다. 예를 들어, 일반적인 4 인 가족에 아침 식사를 만드는 가족 구성원이 있다고 가정 해보십시오. 이제 계란을 끓여 빵을 굽고 건강에 좋은 녹차를 준비해야합니다 (예, 녹차가 가장 좋습니다). 이렇게하면 "조식 만들기"를 더 작은 조각으로 나눌 수 있으며 "조식 만들기"로 요약됩니다. 각 작품은 예를 들어 다른 사람에게 위임 될 수있는 책임이기도합니다.

MVC로 돌아 가기 : 모델과 뷰 사이의 조정이 하나의 책임이 아니라 두 가지 인 경우, 위의 다음 추상화 계층은 무엇이고,이 두 계층을 결합하는 것은 무엇입니까? 당신이 그것을 찾을 수 없다면 당신은 그것을 올바르게 추상화하지 않았거나 아무것도 없다는 것은 당신이 그것을 잘 얻었음을 의미합니다. 그리고 나는 컨트롤러가 뷰와 모델을 처리하는 경우라고 생각합니다.


1
추가 된 메모처럼. controller.makeBreakfast () 및 controller.wakeUpFamily ()가있는 경우 해당 컨트롤러는 SRP를 손상 시키지만 컨트롤러 때문이 아니라 하나 이상의 책임이 있기 때문입니다.

답장을 보내 주셔서 감사합니다. 컨트롤러에 하나 이상의 책임이 있음에 동의하십니까? 내가 생각하는 이유는 모델의 변경과 뷰의 변경이라는 두 가지 이유가 있기 때문입니다. 이것에 동의하십니까?
Aviv Cohn

1
예, 컨트롤러에 둘 이상의 책임이 있음에 동의 할 수 있습니다. 그러나 이것들은 "낮은"책임이며, 가장 높은 추상화 레벨에서는 오직 하나의 책임 (당신이 언급 한 낮은 것을 결합 함) 만 가지므로 SRP를 위반하지 않기 때문에 문제가되지 않습니다.
valenterry

1
올바른 추상화 수준을 찾는 것이 중요합니다. "조식 만들기"예제는 좋은 예입니다. 단일 책임을 완수하기 위해 종종 수행해야하는 많은 작업이 있습니다. 컨트롤러가 이러한 작업을 조정하는 한 SRP를 따릅니다. 그러나 계란 끓기, 토스트 만들기 또는 차 양조에 대해 너무 많이 알고 있다면 SRP를 위반하게됩니다.
Allan

이 대답은 나에게 의미가 있습니다. Valenterry 감사합니다.
J86

9

클래스에 "변경 가능한 두 가지 이유"가 있으면 예, SRP를 위반합니다.

컨트롤러는 일반적으로 가벼워 야하며 GUI 기반 이벤트에 응답하여 도메인 / 모델을 조작하는 단일 책임이 있습니다. 이러한 각 조작은 기본적으로 사용 사례 또는 기능인 것으로 간주 할 수 있습니다.

GUI에 새 버튼이 추가 된 경우 컨트롤러는 새 버튼이 새로운 기능을 나타내는 경우에만 변경해야합니다 (즉, 화면 1에는 존재했지만 화면 2에는 아직 존재하지 않은 동일한 버튼과 반대되는 경우). 화면 2)에 추가되었습니다. 이 새로운 기능 / 기능을 지원하려면 모델에 해당하는 새로운 변경 사항이 있어야합니다. 컨트롤러는 여전히 GUI 기반 이벤트에 응답하여 도메인 / 모델을 조작 할 책임이 있습니다.

버그 수정으로 인해 모델의 비즈니스 로직이 변경되고 컨트롤러를 변경해야하는 경우 이는 특별한 경우입니다 (또는 모델이 공개 폐쇄 주체를 위반 한 것임). 모델의 비즈니스 로직이 일부 새로운 기능 / 기능을 지원하도록 변경되는 경우 컨트롤러가 해당 기능을 노출해야하는 경우에만 컨트롤러에 영향을 미칠 필요는 없습니다 (거의 항상 그렇습니다) 도메인 모델이 사용되지 않는 경우). 따라서이 경우 GUI 기반 이벤트에 대한 응답으로이 새로운 방식으로 도메인 모델 조작을 지원하도록 컨트롤러도 수정해야합니다.

지속성 계층이 플랫 파일에서 데이터베이스로 변경되어 컨트롤러를 변경해야하는 경우 컨트롤러가 SRP를 위반 한 것입니다. 컨트롤러가 항상 동일한 추상화 계층에서 작동하면 SRP를 달성하는 데 도움이 될 수 있습니다.


4

컨트롤러는 SRP를 위반하지 않습니다. 당신이 말한대로, 모델과 뷰 사이를 중재하는 책임이 있습니다.

즉, 예제의 문제는 컨트롤러 메소드를보기의 논리에 묶는 것 controller.specificButtonPressed입니다. 이 방법으로 메소드의 이름을 지정하면 컨트롤러가 GUI에 연결되므로 제대로 추상화하지 못했습니다. 컨트롤러는 특정 작업 (예 : controller.saveData또는)을 수행해야합니다 controller.retrieveEntry. GUI에 새로운 버튼을 추가한다고해서 반드시 컨트롤러에 새로운 방법을 추가하는 것은 아닙니다.

뷰에서 버튼을 누르는 것은 다른 방법으로 쉽게 트리거되거나 뷰를 통해 트리거되지 않을 수있는 모든 것을 수행하는 것을 의미합니다.

SRP에 관한 Wikipedia 기사에서

Martin은 책임을 변경 이유로 정의하고 클래스 또는 모듈에는 변경해야 할 단 하나의 이유 만 있어야한다고 결론을 내립니다. 예를 들어, 보고서를 컴파일하고 인쇄하는 모듈을 고려하십시오. 이러한 모듈은 두 가지 이유로 변경 될 수 있습니다. 첫째, 보고서의 내용이 변경 될 수 있습니다. 둘째, 보고서 형식이 변경 될 수 있습니다. 이 두 가지는 매우 다른 원인으로 바뀐다. 하나의 실질적이고 하나의 화장품. 단일 책임 원칙에 따르면 문제의이 두 가지 측면은 실제로 두 가지 별도 책임이므로 별도의 클래스 나 모듈에 있어야합니다. 다른 시간에 다른 이유로 변경되는 두 가지를 결합하는 것은 좋지 않은 디자인입니다.

컨트롤러는 메소드 중 하나가 호출 될 때 지정된 데이터를보기에 제공하는 것만보기에있는 것에 관심이 없습니다. 모델이 가질 메소드를 호출해야한다는 것을 알기 만하면 모델의 기능 만 알면됩니다. 그것보다 더 아무것도 모른다.

객체가 호출 할 수있는 메소드를 가지고 있다는 것은 그 기능을 아는 것과 다릅니다.


1
컨트롤러에 메소드를 포함시켜야한다고 생각한 이유 specificButtonsPressed()는 뷰가 버튼과 다른 GUI 요소의 기능에 대해 아무것도 알아야한다는 것을 읽었 기 때문입니다. 버튼을 누르면보기가 컨트롤러에보고해야하며 컨트롤러는 '무엇을 의미하는지'결정한 다음 모델에서 적절한 메소드를 호출해야한다고 배웠습니다. 뷰를 호출 controller.saveData()한다는 것은 뷰가 눌려 졌다는 것 외에이 버튼의 눌림의 의미에 대해 뷰가 알아야한다는 것을 의미합니다.
Aviv Cohn

1
버튼 누름과 그 의미 (컨트롤러와 같은 메소드가 있음 specificButtonPressed()) 를 완전히 분리한다는 아이디어를 버린다면 실제로 컨트롤러는 GUI에 너무 묶이지 않을 것입니다. specificButtonPressed()방법을 버려야 합니까? 이 방법을 사용하는 것이 유리하다고 생각합니까? 아니면 buttonPressed()컨트롤러에 문제가없는 방법이 있습니까?
Aviv Cohn

1
다른 말로하면 (긴 의견에 대해 유감스럽게 생각 합니다 .) specificButtonPressed()컨트롤러에 메소드 를 갖는 이점은 뷰가 버튼 누름의 의미와 완전히 분리된다는 것 입니다. 그러나 단점은 컨트롤러가 GUI에 어떤 의미로 연결되어 있다는 것입니다. 어떤 접근법이 더 낫습니까?
Aviv Cohn

@prog IMO 컨트롤러가 시야에 보이지 않아야합니다. 버튼에는 일종의 기능이 있지만 자세한 내용을 알 필요는 없습니다. 컨트롤러 메소드에 일부 데이터를 보내고 있다는 것을 알아야합니다. 그런 점에서 이름은 중요하지 않습니다. foo쉽게 호출 할 수 있습니다 fireZeMissiles. 특정 기능에보고해야한다는 것만 알 수 있습니다. 함수가 무엇을 호출하는지는 알지 못합니다. 컨트롤러는 메소드가 호출되는 방식에 관심이 없으며 특정 방식으로 응답 할 수 있습니다.
Schleis

1

컨트롤러의 단일 책임은 뷰와 모델 사이를 중재하는 계약입니다. 뷰는 디스플레이 만 담당하고 모델은 비즈니스 로직 만 담당해야합니다. 이 두 가지 책임을 연결하는 것은 컨트롤러 책임입니다.

그것은 모두 훌륭하지만 학계에서 조금 벗어나는 것입니다. MVC의 컨트롤러는 일반적으로 많은 작은 작업 방법으로 구성됩니다. 이러한 행동은 일반적으로 일이 할 수있는 일에 해당합니다. 제품을 판매하는 경우 아마도 ProductController가있을 것입니다. 해당 컨트롤러에는 GetReviews, ShowSpecs, AddToCart 등의 작업이 있습니다.

뷰에는 UI를 표시하는 SRP가 있으며 해당 UI의 일부에는 AddToCart라는 버튼이 있습니다.

컨트롤러에는 프로세스와 관련된 모든 뷰 및 모델을 아는 SRP가 있습니다.

컨트롤러 AddToCart Action에는 품목이 장바구니에 추가 될 때 관여해야하는 모든 사람을 아는 특정 SRP가 있습니다.

제품 모델에는 제품 로직 모델링의 SRP가 있고 ShoppingCart 모델에는 나중에 체크 아웃하기 위해 항목을 저장하는 방법을 모델링하는 SRP가 있습니다. 사용자 모델에는 장바구니에 물건을 추가하는 사용자를 모델링하는 SRP가 있습니다.

비즈니스를 수행하기 위해 모델을 재사용 할 수 있으며 재사용해야하며 해당 모델을 코드의 특정 시점에 연결해야합니다. 컨트롤러는 커플 링이 발생하는 각각의 고유 한 방식을 제어합니다.


0

컨트롤러는 실제로 사용자의 입력에 따라 애플리케이션 상태를 변경하는 단 하나의 책임이 있습니다.

컨트롤러 (예를 들어, 문서를 편집) 모델의 상태를 업데이트 할 모델에 명령을 보낼 수 있습니다. 또한 모델의보기 표시를 변경하기 위해 명령을 관련보기로 보낼 수도 있습니다 (예 : 문서를 스크롤하여).

source: wikipedia

대신 Rails 스타일의 "컨트롤러" (활성 레코드 인스턴스 및 벙어리 템플릿을 저글링) 를 사용하는 경우 물론 SRP를 위반하는 것입니다.

다시 Rails 스타일 애플리케이션은 실제로 MVC가 아닙니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.