단일 책임 원칙 명확화


64

단일 책임 원칙에 따르면 클래스는 한 가지만 수행해야합니다. 어떤 경우는 꽤 분명합니다. 그러나 특정 추상화 수준에서 볼 때 "하나의 것"처럼 보이는 것이 낮은 수준에서 볼 때 여러 가지 일 수 있기 때문에 다른 것들은 어렵습니다. 또한 단일 책임 원칙이 더 낮은 수준에서 과도하게 분리되고 자세한 라비올리 코드를 준수하면 더 많은 라인이 모든 문제에 대한 작은 클래스를 만들고 실제 문제를 해결하는 것보다 주변에 정보를 제공하는 데 소비되는 더 많은 줄이 생길 수 있습니다.

"한 가지"의 의미를 어떻게 설명 하시겠습니까? 클래스가 실제로 "한 가지"이상을 수행한다는 구체적인 징후는 무엇입니까?


6
상위 "라비올리 코드"이상인 경우 +1 내 경력 초기에 나는 그것을 너무 멀리 데려 간 사람들 중 하나였습니다. 클래스뿐만 아니라 메소드 모듈화도 가능합니다. 내 코드에는 스크롤하지 않고 화면에 들어갈 수있는 작은 덩어리로 문제를 나누기 위해 간단한 간단한 방법이 많이 있습니다. 분명히, 이것은 종종 너무 멀리 가고 있었다.
Bobby Tables

답변:


50

Robert C. Martin (Uncle Bob) 이 Single Responsibility Principle (PDF에 연결 )을 재건 하는 방식이 정말 마음에 듭니다 .

수업이 바뀌어야하는 이유는 하나 이상이어야합니다.

그것은 전통적인 "한 가지만해야한다"라는 정의와 미묘하게 다르며, 나는 당신이 당신의 수업에 대해 생각하는 방식을 바꾸도록 강요하기 때문에 이것을 좋아합니다. "이것이 한 가지 일을하고 있는가?"에 대해 생각하는 대신에, 무엇을 바꿀 수 있는지, 그리고 그러한 변화가 당신의 수업에 어떤 영향을 미치는지 생각하십시오. 예를 들어, 데이터베이스가 변경되면 클래스를 변경해야합니까? 출력 장치 (예 : 화면, 모바일 장치 또는 프린터)가 변경되면 어떻습니까? 다른 많은 방향의 변화로 인해 수업을 변경해야하는 경우, 수업에 너무 많은 책임이 있다는 표시입니다.

관련 기사에서 Bob 아저씨는 다음과 같이 결론을 내립니다.

SRP는 가장 간단한 원칙 중 하나이며 가장 잘 이해하기 어려운 원칙 중 하나입니다. 결합 된 책임은 우리가 자연스럽게하는 것입니다. 이러한 책임을 찾아서 분리하는 것은 소프트웨어 설계가 실제로하는 것의 많은 부분입니다.


2
나는 그가 말하는 방식이 마음에 들었다. 추상적 "책임"보다는 변화와 관련이있을 때 확인하기가 더 쉽다.
Matthieu M.

그것은 실제로 그것을 넣는 훌륭한 방법입니다. 나는 그것을 좋아한다. 참고로, 나는 보통 SRP가 방법에 훨씬 더 강력하게 적용되는 것으로 생각하는 경향이 있습니다. 때로는 클래스가 두 가지 일을해야 할 수도 있지만 클래스가 두 도메인을 연결하는 클래스 일 수도 있지만 메소드는 유형 서명으로 간결하게 설명 할 수있는 것 이상을 수행하지 않아야합니다.
CodexArcanum

1
내 대학원에 이것을 보여주었습니다.
Martijn Verburg

1
오버 엔지니어링되지 않은 코드는 가능한 모든 변경이 아닌 예측 가능한 미래에 가능한 변경만을 계획해야한다는 생각과 결합하면 매우 의미가 있습니다. 나는 이것을“예상 가능한 미래에 일어날 수있는 클래스가 변화해야 할 이유가 하나 밖에 없다”고 약간 언급했다. 이것은 변경 될 수없는 부분에서 변경 및 분리가 불가능한 디자인 부분의 단순성을 선호합니다.
dsimcha

18

SRP가 어떤 문제를 해결하려고 노력하고 있습니까? SRP는 언제 도움이 되나요? 내가 생각해 낸 것은 다음과 같습니다.

다음과 같은 경우 클래스 외부에서 책임 / 기능을 리팩터링해야합니다.

1) 기능이 중복되었습니다 (DRY)

2) 코드 이해를 돕기 위해 코드에 다른 수준의 추상화가 필요하다는 것을 알게되었습니다 (KISS)

3) 도메인 전문가가 기능의 일부를 다른 구성 요소 (유비쿼터스 언어)와 분리 한 것으로 이해함

다음과 같은 경우 클래스 외부에서 책임을 리팩터링하지 않아야합니다.

1) 중복 된 기능이 없습니다.

2) 기능은 수업의 맥락 밖에서는 의미가 없습니다. 다시 말해, 클래스는 기능을 이해하기 쉬운 컨텍스트를 제공합니다.

3) 도메인 전문가에게는 그러한 책임에 대한 개념이 없습니다.

SRP가 광범위하게 적용되면 우리는 한 종류의 복잡성 (내부에서 너무 많은 방식으로 수업의 머리 또는 꼬리를 만들려고 시도)을 다른 종류 (모든 공동 작업자 / 수준의 이 클래스들이 실제로 무엇을하는지 파악하기 위해 추상화).

확실하지 않은 경우에는 그대로 두십시오! 명확한 사례가있는 경우 언제든지 나중에 리팩터링 할 수 있습니다.

어떻게 생각해?


이 지침은 도움이 될 수도 있고 그렇지 않을 수도 있지만 SOLID에 정의 된 SRP와는 실제로 관련이 없습니까?
sara

감사합니다. 나는 소위 SOLID 원리와 관련된 어떤 광기가 믿을만한 이유가 없다 . 아주 간단한 코드 를 아무 이유없이 백 배나 더 복잡하게 만든다 . 귀하가 제공하는 요점 은 SRP를 구현해야하는 실제 이유를 설명 합니다. 나는 당신이 위에서 부여한 원칙들이 그들 자신의 약어가되어야한다고 생각하고 "SOLID"를 버려야합니다. 그것은 선보다 더 해가됩니다. "아키텍처 우주 비행사", 실로 나를 안내 하는 실 에서 지적했듯이 .
Nicholas Petersen

4

나는 그것에 대한 객관적인 규모가 있는지는 모르지만 이것을 줄 것입니다. 나는 당신이 너무 멀리 분해를 취할 수 있다고 동의하지만 그것에 대해 강력하고 빠른 규칙을 따르지 않을 것입니다.


4

답은 정의에있다

당신 이 책임을 정의 하는 것은 궁극적으로 당신에게 경계를줍니다.

예:

송장 을 표시 하는 책임이있는 구성 요소가 있습니다. 이 경우 더 이상 추가하기 시작하면 원칙을 위반합니다.

반면에 경우에 나는 책임 말했다 경우 처리 송장을 - (예 : 인쇄 여러 개의 작은 기능을 추가> 송장 업데이트 송장을 모두 그 경계에 있습니다).

해당 모듈 처리하기 시작한다면 어떤 외부 기능 송장을 , 다음은 그 경계의 외부 것이다.


여기서 가장 큰 문제는 "처리"라는 단어입니다. 단일 책임을 정의하기에는 너무 일반적입니다. 프린트 인보이스를 담당하는 컴포넌트 와 하나의 컴포넌트 핸들이 아닌 업데이트 인보이스 를 담당하는 컴포넌트를 갖는 것이 더 좋을 것이라고 생각합니다 . - 디스플레이, 어떤} 송장 .
Machado

1
OP는 본질적으로 "책임을 어떻게 정의합니까?" 따라서 책임을 말하면 정의를 마치는 것이 단지 질문을 반복하는 것처럼 보입니다.
Despertar

2

나는 항상 두 가지 수준으로 봅니다.

  • 내 방법이 한 가지 일만하고 잘 수행하는지 확인하십시오.
  • 클래스를 한 가지를 잘 나타내는 메소드의 논리적 (OO) 그룹화로 본다

따라서 Dog라는 도메인 객체와 같은 것입니다.

Dog내 수업이지만 개는 많은 일을 할 수 있습니다! 나는 다음과 같은 방법이있을 수 있습니다 walk(), run()그리고 bite(DotNetDeveloper spawnOfBill)(죄송 저항 할 수 없었다; p)를.

경우 Dog다루기로되고 그 다음 나는 그 방법의 그룹이 같은 같은 다른 클래스에서 함께 모델링 할 수있는 방법에 대해 생각하는 것 Movement내 포함 할 수 클래스 walk()run()방법을.

단단하고 빠른 규칙은 없으며 OO 디자인은 시간이 지남에 따라 진화합니다. 명확한 컷 인터페이스 / 퍼블릭 API뿐만 아니라 한 가지 일과 한 가지 일을 잘 수행하는 간단한 메소드를 사용하려고합니다.


이빨은 정말 개체의 인스턴스를해야하고, DotNetDeveloper은 사람의 서브 클래스해야한다 (어쨌든, 일반적으로!)
앨런 피어스에게

@Alan -이 - 고정 그 :-) 당신을 위해
마티 Verburg

1

나는 수업의 라인을 따라 그것을 한 번만 표현 해야합니다 . Karianna의 예를 @ 적절한하기 위해, 나는이 Dog방법을 가지고 클래스를 walk(), run()하고 bark(). 내가하기위한 방법을 추가하지 않을거야 meaow(), squeak(), slither()또는 fly()그 개는 할 일이 없기 때문에. 그것들은 다른 동물들이하는 일이며, 다른 동물들은 그들을 대표하는 자신의 수업을 가질 것입니다.

(당신의 개를 경우 BTW, 않습니다 비행, 그때는 아마 창에서 그를 던지는 중지해야합니다).


"강아지가 날 경우 창 밖으로 던지는 것을 중단해야합니다"+1 :)
Bobby Tables

클래스 가 무엇을 대표 해야하는지에 대한 질문 외에도 인스턴스 는 무엇을 상징합니까? 일 개에 관해서 경우 SeesFood의 특성으로 DogEyes, Bark무언가로는 수행 DogVoice하고, Eata로 할 무언가로 DogMouth, 다음 로직과 같은 if (dog.SeesFood) dog.Eat(); else dog.Bark();될 것 if (eyes.SeesFood) mouth.Eat(); else voice.Bark();눈, 입, 음성이 모두 단일 entitity에 연결되어 정체성의 감각을 잃고.
supercat

@ supercat 컨텍스트가 중요하지만 공정한 포인트입니다. 언급 한 코드가 Dog클래스 내에 있으면 아마도 Dog관련이 있습니다. 그렇지 않다면 아마 myDog.Eyes.SeesFood그냥 아닌 다른 것으로 끝날 것입니다 eyes.SeesFood. 다른 가능성은 속성과 방법 을 요구하는 인터페이스 를 Dog노출시키는 것입니다. ISeeDog.EyesSeesFood
JohnL

@JohnL :보기의 실제 역학이 본질적으로 고양이 나 얼룩말과 같은 방식으로 개의 눈에 의해 다루어 진다면, 역학에 의해 역학을 다루는 것이 합리적 Eye이지만 개는 "보아야한다" 눈으로 보는 것보다 눈을 사용하는 것. 개는 눈이 아니지만 단순히 눈을 끄는 것도 아닙니다. 그것은 "적어도 볼 수있는 것"이며, 인터페이스를 통해 그러한 것으로 설명되어야합니다. 맹인 개조차도 음식을 보느냐고 물을 수 있습니다. 개가 항상 "아니오"라고 말하기 때문에 매우 유용하지는 않지만 물어 보면 아무런 해가 없습니다.
supercat

그런 다음 내 의견에 설명 된 것처럼 ISee 인터페이스를 사용합니다.
JohnL

1

클래스는 자체 추상화 수준에서 볼 때 한 가지 작업을 수행해야합니다. 의심 할 여지없이 추상적 인 수준에서 많은 일을합니다. 이것은 클래스가 프로그램을 유지 관리하기 쉽게 만드는 방법입니다. 프로그램을 자세히 조사 할 필요가 없으면 구현 세부 사항을 숨 깁니다.

나는 이것을 테스트하기 위해 클래스 이름을 사용합니다. 클래스에 설명이 짧은 이름을 줄 수 없거나 해당 이름에 "And"와 같은 단어가 있으면 단일 책임 원칙을 위반 한 것입니다.

내 경험상,이 원칙을보다 구체적인 수준으로 유지하는 것이 더 쉽습니다.


0

하나의 독특한 rôle을 갖는 것 입니다.

각 클래스는 역할 이름으로 다시 시작해야합니다. 역할은 사실 문맥과 관련된 동사의 집합입니다.

예를 들면 다음과 같습니다.

파일은 파일의 액세스를 제공합니다. FileManager는 파일 객체를 관리합니다.

파일에서 하나의 자원에 대한 자원 보유 데이터. ResourceManager는 모든 자원을 보유하고 제공합니다.

여기서 "관리"와 같은 일부 동사는 다른 동사 집합을 의미합니다. 동사는 대부분 클래스보다 함수로 생각하는 것이 좋습니다. 동사가 자신의 공통된 맥락을 가진 너무 많은 행동을 암시한다면, 그 자체가 클래스가되어야합니다.

따라서 아이디어는 고유 한 역할을 정의하여 클래스가 무엇을 수행하는지에 대한 간단한 아이디어를 가질 수 있도록하는 것입니다. 이는 여러 하위 역할 (멤버 오브젝트 또는 다른 오브젝트에 의해 수행됨)의 집합 일 수 있습니다.

나는 종종 다른 여러 클래스가있는 Manager 클래스를 빌드합니다. 팩토리, 레지스트리 등과 같은 일종의 그룹 장, 오케스트라 장과 같은 Manager 클래스를 참조하여 다른 사람들이 함께 협력하여 높은 수준의 아이디어를 얻도록 안내하십시오. 그는 하나의 역할을 가지고 있지만 내부의 다른 고유 한 역할을 수행하는 것을 의미합니다. 또한 회사 구성 방식과 같이 볼 수 있습니다. CEO는 순수한 생산성 수준에서 생산적인 회사는 아니지만, 그렇지 않은 경우에는 제대로 협력 할 수 없습니다. 그것이 그의 역할입니다.

디자인 할 때 고유 한 역할을 식별하십시오. 그리고 각 역할에 대해 다른 역할에서 잘릴 수 없는지 다시 확인하십시오. 그렇게하면 Manager가 객체를 작성하는 방식을 간단하게 변경해야하는 경우 간단히 Factory를 변경하고 안심하십시오.


-1

SRP는 클래스를 나누는 것뿐만 아니라 기능을 위임하는 것입니다.

위에서 사용 된 Dog 예제에서 DogBarker, DogWalker 등과 같은 3 개의 개별 클래스 (낮은 응집력)를 갖도록 SRP를 정당화로 사용하지 마십시오. 대신 클래스의 메소드 구현을보고 "너무 많이 알고 있는지"결정하십시오. 여전히 dog.walk ()를 가질 수 있지만, walk () 메소드는 다른 클래스에 걷기 수행 방법에 대한 세부 사항을 위임해야합니다.

실제로 우리는 Dog 클래스가 변경 될 수있는 한 가지 이유를 허용합니다 : Dogs가 변경되기 때문입니다. 물론, 이것을 다른 SOLID 원리와 결합하면 Dog (개방 / 폐쇄)를 변경하지 않고 새로운 기능을 위해 Dog를 확장 할 수 있습니다. 그리고 IMove 및 IEat와 같은 종속성을 주입합니다. 물론 이러한 별도의 인터페이스 (인터페이스 분리)를 만들어야합니다. 개가 버그를 발견하거나 개가 근본적으로 변경된 경우에만 개가 변경됩니다 (Liskov Sub, 확장하지 않은 다음 동작 제거).

SOLID의 최종 효과는 기존 코드를 수정하는 것보다 새 코드를 더 자주 작성한다는 것입니다.


-1

그것은 모두 책임의 정의와 그 정의가 코드 유지에 어떤 영향을 미치는지에 달려 있습니다. 모든 것이 한 가지로 요약되며 디자인이 코드를 유지하는 데 도움이되는 방식입니다.

그리고 누군가가 말했듯이 "물을 걷거나 주어진 사양으로 소프트웨어를 설계하는 것은 쉽다"고 말했다.

따라서 책임을 좀 더 구체적으로 정의하고 있다면 변경할 필요가 없습니다.

때로는 책임이 눈에 띄게 드러날 지 모르지만 때로는 미묘 할 수도 있으므로 신중하게 결정해야합니다.

catchThief ()라는 Dog 클래스에 다른 책임을 추가한다고 가정합니다. 이제는 다른 책임을지게 될 수도 있습니다. 내일, 개가 도둑을 잡는 방식이 경찰서에 의해 변경되어야한다면, 개 수업이 변경되어야합니다. 이 경우 다른 서브 클래스를 작성하고 이름을 ThiefCathcerDog로하는 것이 좋습니다. 그러나 다른 관점에서, 어떤 상황에서도 변화하지 않을 것이라고 확신하거나 catchThief가 구현 된 방식이 외부 매개 변수에 의존하는 경우이 책임을 갖는 것이 좋습니다. 책임이 현저하게 이상하지 않으면 사용 사례에 따라 신중하게 결정해야합니다.


-1

"변경해야하는 한 가지 이유"는 시스템을 사용하는 사람에 따라 다릅니다. 각 액터에 대해 사례를 사용하고 가능한 변경 사항 목록을 작성하십시오. 각 가능한 사용 사례 변경에 대해 한 클래스 만 해당 변경의 영향을 받도록하십시오. 완전히 새로운 사용 사례를 추가하는 경우 클래스를 확장하기 만하면됩니다.


1
변경해야하는 한 가지 이유는 사용 사례, 행위자 수 또는 변경 가능성과 관련이 없습니다. 변경 가능성이있는 목록을 작성해서는 안됩니다. 하나의 변경이 하나의 클래스에만 영향을주는 것이 맞습니다. 그러한 변화를 수용하기 위해 수업을 확장 할 수 있다는 것은 좋지만 SRP가 아니라 공개 폐쇄 원칙입니다.
candied_orange

변경 가능성이 가장 높은 목록을 작성하지 않는 이유는 무엇입니까? 투기적인 디자인?
kiwicomb123

도움이되지 않고, 완전하지 않으며, 변화를 예측하는 것보다 효과적인 방법이 있습니다. 기대해 의사 결정을 분리하면 영향이 최소화됩니다.
candied_orange

알다시피, "필요하지 않다"원칙을 위반한다는 것을 이해합니다.
kiwicomb123

실제로 yagni도 멀리까지 밀 수 있습니다. 실제로는 추론 적 사용 사례를 구현하지 못하게하기위한 것입니다. 구현 된 사용 사례를 격리하지 않도록합니다.
candied_orange
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.