단일 책임 원칙을 어 기지 않고 클래스에 여러 개의 메소드를 가질 수있는 방법


64

단일 책임 원칙은 Wikipedia에서 다음 과 같이 정의 됩니다.

단일 책임 원칙은 모든 모듈, 클래스 또는 기능이 소프트웨어가 제공하는 기능의 단일 부분에 대해 책임을 가져야하며 책임은 클래스에 의해 완전히 캡슐화되어야한다는 컴퓨터 프로그래밍 원칙입니다.

수업이 하나의 책임만을 가져야한다면, 어떻게 하나 이상의 방법을 가질 수 있습니까? 각 방법이 서로 다른 책임을 가지지 않을 것이므로 클래스가 하나 이상의 책임을지게 될 것입니다.

단일 책임 원칙을 보여주는 모든 예는 하나의 메서드 만있는 예제 클래스를 사용합니다. 하나의 책임이있는 것으로 간주 될 수있는 여러 메소드가있는 클래스에 대한 예제를 보거나 설명하는 것이 도움이 될 수 있습니다.


11
왜 공감해야합니까? SE.SE에게는 이상적인 질문 인 것 같습니다. 그 사람은 그 주제를 연구하고 질문을 매우 명확하게하기 위해 노력했습니다. 대신 공감할 가치가 있습니다.
Arseni Mourzenko

19
downvote는 아마도이 질문에 이미 여러 번 질문과 대답을했기 때문일 수 있습니다 (예 : softwareengineering.stackexchange.com/questions/345018/… 참조) . 제 생각에는 실질적인 새로운 측면을 추가하지 않습니다.
Hans-Martin Mosner


9
이것은 단순히 reductio ad absurdum입니다. 모든 클래스에 문자 그대로 하나의 메소드 만 허용 되면 문자 그대로 어떤 프로그램도 둘 이상의 작업을 수행 할 수있는 방법이 없습니다.
대럴 호프만

6
@DarrelHoffman 사실이 아닙니다. 만약 모든 클래스가 단지 "call ()"메소드를 가진 functor라면, 객체 지향 프로그래밍으로 기본 절차 프로그래밍을 기본적으로 모방 한 것입니다. 클래스의 "call ()"메소드는 다른 많은 클래스의 "call ()"메소드를 호출 할 수 있으므로 다른 방법으로 수행 할 수있는 모든 작업을 계속 수행 할 수 있습니다.
Vaelus

답변:


29

단일 책임은 단일 기능이 수행 할 수있는 것이 아닐 수도 있습니다.

 class Location { 
     public int getX() { 
         return x;
     } 
     public int getY() { 
         return y; 
     } 
 }

이 수업은 단일 책임 원칙을 위반할 수 있습니다. 이 두 가지 기능을 가지고 있지만, 코드 경우에 때문이 아니라 getX()getY()변경을 요구할 수있다 다른 이해 관계자를 만족해야한다. X 부통령이 모든 숫자를 부동 소수점 숫자로 표현해야한다는 메모를 보내면 회계 부사장 Y는 Mr. X의 생각에 관계없이 부서 검토가 모든 숫자를 정수로 유지해야한다고 주장합니다. 일이 혼란스러워지기 때문에 누가 책임이 있는지에 대한 단일 아이디어.

SRP를 준수했다면 Location 클래스가 X와 그의 그룹이 노출되는 것에 기여하는지가 분명합니다. 클래스가 무엇을 담당하는지 명확하게하고 어떤 클래스가이 클래스에 영향을 미치는지 알고 있습니다. 둘 다이 클래스에 영향을주는 경우 변경의 영향을 최소화하도록 제대로 설계되지 않았습니다. "수업을 변경할 이유가 하나만 있어야한다"고해서 학급 전체가 단 한 가지만 할 수있는 것은 아닙니다. 그것은 수업을 보지 못하고 X와 Y 부인이이 수업에 관심이 있다고 말해서는 안된다는 것을 의미합니다.

그런 것 말고 아니요, 여러 방법이 좋습니다. 클래스에 속하는 메소드와 그렇지 않은 메소드를 명확히하는 이름을 지정하십시오.

Bob 아저씨의 SRP는 Curly의 법칙 보다 Conway의 법칙 에 관한 것 입니다. Bob 아저씨는 Curly 's Law (한 가지만)를 클래스가 아닌 함수에 적용하는 것을 옹호합니다. SRP는 혼합 이유를 함께 변경하지 않도록주의합니다. Conway 's Law에 따르면 시스템은 조직의 정보 흐름 방식을 따릅니다. 당신이 듣지 못하는 것에 신경 쓰지 않기 때문에 SRP를 따르게됩니다.

"모듈은 오직 한 명의 행위자 만 담당해야합니다"

Robert C Martin-클린 아키텍처

사람들은 SRP가 범위를 제한하는 모든 이유에 대해 계속 원합니다. SRP보다 범위를 제한해야하는 이유가 더 있습니다. 나는 내부를 들여다 보는 것이 당신을 놀라게하지 않을 것임을 보장하는 이름을 취할 수있는 추상화라고 클래스를 주장함으로써 범위를 제한 합니다 .

Curly 's Law를 수업에 적용 할 수 있습니다. 당신은 밥 아저씨가 말한 것 밖에 있지만 당신은 할 수 있습니다. 당신이 잘못하는 것은 그것이 하나의 기능을 의미한다고 생각하기 시작할 때입니다. 그것은 한 가정에 아이가 하나만 있어야한다고 생각하는 것과 같습니다. 자녀가 두 명 이상 있어도 가족이되는 것을 막을 수는 없습니다.

Curly의 법칙을 수업에 적용하면 수업의 모든 내용이 단일 통일 아이디어에 관한 것이어야합니다. 그 아이디어는 광범위 할 수 있습니다. 아이디어는 지속성 일 수 있습니다. 일부 로깅 유틸리티 기능이 있으면 명확하지 않습니다. X가이 코드에 관심이있는 유일한 사람인지는 중요하지 않습니다.

여기에 적용되는 고전적인 원칙을 우려 분리 라고 합니다. 모든 우려 사항을 분리하면 한 곳에 남아있는 것이 한 가지 우려 사항이라고 주장 할 수 있습니다. 1991 년 영화 City Slickers가 Curly라는 캐릭터를 소개하기 전에 우리가이 아이디어를 불렀습니다.

이건 괜찮아. 밥 아저씨가 책임을지는 것은 문제가되지 않습니다. 그에 대한 책임은 당신이 집중하는 것이 아닙니다. 그것은 당신이 변화하도록 강요 할 수있는 것입니다. 하나의 관심사에 초점을 맞추고 여전히 다른 의제를 가진 다른 그룹의 사람들을 담당하는 코드를 작성할 수 있습니다.

아마 당신은 그것에 대해 신경 쓰지 않을 것입니다. 좋아. "한 가지 일을하는 것"을 유지하면 모든 디자인 문제를 해결할 수 있다고 생각하면 "한 가지"가 무엇인지에 대한 상상력이 부족해집니다. 범위를 제한하는 또 다른 이유는 조직입니다. 모든 "쓰레기통"이 생길 때까지 다른 "한 가지"안에 많은 "한 가지"를 넣을 수 있습니다. 나는 전에 그것에 대해 이야기했습니다

물론 범위를 제한하는 고전적인 OOP 이유는 클래스에 개인 필드가 있고 getter를 사용하여 해당 데이터를 공유하기 때문입니다. 우리는 데이터를 필요로하는 모든 메소드를 클래스에서 데이터를 비공개로 사용할 수 있도록합니다. 많은 사람들이이 방법이 범위 제한 자로 사용하기에는 너무 제한적이라는 것을 알고 있습니다. 데이터를 모은 아이디어가 메소드를 모은 아이디어와 동일하다는 것을 확신합니다.

이 보는 기능성 방법은 인 a.f(x)a.g(x)f를 단순히 g (x)와 (X). 두 기능이 아니라 서로 다른 기능 쌍의 연속체입니다. 는 심지어 데이터를 가지고 있지 않습니다. 사용하려는 구현 과 구현 방법을 간단히 알 수 있습니다. 함께 바뀌는 기능은 서로 속해 있습니다. 그것은 좋은 오래된 다형성입니다.afg

SRP는 범위를 제한하는 많은 이유 중 하나 일뿐입니다. 좋은 것입니다. 그러나 유일한 것은 아닙니다.


25
나는이 답변이 SRP를 알아 내려고하는 누군가에게 혼란 스럽다고 생각합니다. Mr. President와 Mrs Director 간의 싸움은 기술적 수단을 통해 해결되지 않으며이를 사용하여 엔지니어링 결정을 정당화하는 것은 의미가 없습니다. 콘웨이의 법칙.
whatsisname

8
@whatsisname 반대로. SRP는 이해 당사자에게 명시 적으로 적용되었습니다. 기술 설계와 관련없습니다 . 당신은 그 접근법에 동의하지 않을 수도 있지만, 이것이 바로 삼촌이 SRP를 정의한 방식이며, 어떤 이유로 사람들은이 단순한 개념을 이해할 수없는 것처럼 보이기 때문에 반복해서 반복해야했습니다. 실제로 유용한 것은 완전히 직교하는 질문입니다).
루안

Tim Ottinger가 설명한 Curly의 법칙 은 변수가 지속적으로 한 가지를 의미 해야한다고 강조합니다 . 나에게 SRP는 그것보다 조금 강하다. 클래스는 개념적으로 "하나의 것"을 나타낼 수 있지만, 변화의 두 외부 운전자가 그 "하나의 것"의 일부 측면을 다른 방식으로 취급하거나 두 개의 다른 측면에 대해 우려하는 경우 SRP를 위반할 수 있습니다. 문제는 모델링 중 하나입니다. 하나의 클래스로 무언가를 모델링하기로 선택했지만, 그 선택에 문제를 일으키는 도메인에 관한 것이 있습니다 (코드베이스가 발전함에 따라 문제가 발생하기 시작합니다).
Filip Milovanović

2
@ FilipMilovanović Conway의 법칙과 SRP 사이의 유사점은 Bob 아저씨가 클린 아키텍처 (Clean Architecture) 책에서 SRP를 설명한 방식이 조직이 깨끗한 비순환 조직도를 가지고 있다는 가정에서 비롯됩니다. 이것은 오래된 생각입니다. 성서조차도 "한 사람은 두 주인을 섬길 수 없습니다"라고 인용합니다.
candied_orange

1
@TKK im은 Curly의 법칙이 아닌 Conways의 법칙과 관련이 있습니다. 나는 SRP가 Curly의 법칙이라는 생각을 반박하고 있습니다. Bob 아저씨가 Clean Architecture 책에서 그렇게 말했기 때문입니다.
candied_orange

48

여기서 핵심은 scope 또는 원한다면 granularity 입니다. 클래스가 나타내는 기능의 일부는 기능의 일부로 더 분리 될 수 있으며, 각 부분은 메소드입니다.

다음은 예입니다. 시퀀스에서 CSV를 생성해야한다고 상상해보십시오. RFC 4180을 준수하려면 알고리즘을 구현하고 모든 주요 사례를 처리하는 데 시간이 오래 걸립니다.

단일 방법으로 코드를 작성하면 코드를 읽을 수 없으며 특히이 방법은 한 번에 여러 가지 작업을 수행합니다. 따라서 여러 방법으로 분할합니다. 예를 들어, 그중 하나는 헤더, 즉 CSV의 첫 번째 줄을 생성하는 책임이있을 수 있지만 다른 방법은 모든 유형의 값을 CSV 형식에 적합한 문자열 표현으로 변환하는 반면 다른 방법은 값은 큰 따옴표로 묶어야합니다.

이러한 방법에는 자체 책임이 있습니다. 큰 따옴표를 추가해야하는지 여부를 확인하는 방법에는 고유 한 따옴표가 있고 헤더를 생성하는 방법에는 따옴표가 있습니다. 이것은 메소드에 적용되는 SRP 입니다.

이제 이러한 모든 방법에는 하나의 공통된 목표가 있습니다. 즉 시퀀스를 취하고 CSV를 생성합니다. 이것은 수업 의 단일 책임입니다 .


파블로 H 는 논평했다 :

좋은 예이지만, 여전히 SRP가 클래스에 둘 이상의 공개 메소드를 허용하는 이유에 대해서는 답변하지 않습니다.

과연. 내가 준 CSV 예제에는 이상적으로 하나의 공개 방법이 있으며 다른 모든 방법은 비공개입니다. 더 좋은 예는 Queue클래스에 의해 구현 된 큐 입니다. 이 클래스에는 기본적으로 push(이라고도 함 enqueue) 및 pop(이라고도 함 dequeue)의 두 가지 메소드가 포함 됩니다.

  • 책임은 Queue.push대기열의 꼬리에 객체를 추가하는 것입니다.

  • 책임은 Queue.pop큐의 헤드에서 오브젝트를 제거하고 큐가 비어있는 경우를 처리하는 것입니다.

  • Queue클래스 의 책임은 큐 로직을 제공하는 것입니다.


1
좋은 예이지만, 여전히 SRP가 클래스에 둘 이상의 공개 메소드 를 허용하는 이유에 대해서는 답변하지 않습니다 .
Pablo H

1
@PabloH : 공평합니다. 클래스에 두 개의 메소드가있는 다른 예제를 추가했습니다.
Arseni Mourzenko

30

함수는 함수입니다.

책임은 책임입니다.

정비공은 진단, 일부 간단한 유지 보수 작업, 실제 수리 작업, 다른 사람에게 작업 위임 등을 포함하여 자동차를 수리해야 할 책임이 있습니다.

컨테이너 클래스 (목록, 배열, 사전, 맵 등)는 객체를 저장하고 삽입, 액세스, 어떤 종류의 순서 등을 포함하는 객체를 저장해야 할 책임이 있습니다.

단일 책임은 코드 / 기능이 거의 없음을 의미하지 않으며, 동일한 책임하에 "함께 포함 된"기능이 무엇이든 의미합니다.


2
동의합니다. @Aulis Ronkainen-두 답변을 묶습니다. 또한 중첩 된 책임의 경우 정비 적 비유를 사용하여 차고는 차량 유지 관리를 담당합니다. 차고 다른 기계는 자동차의 다른 부분에 대한 책임을 가지고 있지만 이러한 역학의 각각의 응집력에 함께 작동
wolfsshield

2
@wolfsshield가 동의했습니다. 한 가지만하는 기계공은 쓸모가 없지만, 하나의 책임이있는 기계공은 (적어도 반드시) 그런 것은 아닙니다. 실제 OPS 개념이 항상 추상 OOP 개념을 설명하는 최선은 아니지만 이러한 차이점을 구분하는 것이 중요합니다. 나는 그 차이를 이해하지 못한다는 것이 처음에는 혼란을 야기하는 것이라고 생각합니다.
Aulis Ronkainen

3
@AulisRonkainen 외형, 냄새, 느낌과 비슷하지만 SRP에서 책임 이라는 용어의 특정 의미를 강조하기 위해이 기법을 사용하려고했습니다 . 나는 당신의 대답에 전적으로 동의합니다.
피터

20

단일 책임이 반드시 한 가지 일만한다는 것을 의미하지는 않습니다.

예를 들어 사용자 서비스 클래스를 보자.

class UserService {
    public User Get(int id) { /* ... */ }
    public User[] List() { /* ... */ }

    public bool Create(User u) { /* ... */ }
    public bool Exists(int id) { /* ... */ }
    public bool Update(User u) { /* ... */ }
}

이 클래스에는 여러 가지 방법이 있지만 책임은 분명합니다. 데이터 저장소의 사용자 레코드에 대한 액세스를 제공합니다. 유일한 종속성은 사용자 모델과 데이터 저장소입니다. SRP가 느슨하게 결합하고 응집력이 뛰어 나기 때문에 SRP가 생각하려고합니다.

SRP를 "인터페이스 분리 원리"와 혼동해서는 안됩니다 ( SOLID 참조 ). 인터페이스 분리 원리 (ISP)에 따르면 더 크고 일반화 된 인터페이스보다 작고 가벼운 인터페이스가 바람직합니다. Go는 표준 라이브러리 전체에서 ISP를 많이 사용합니다.

// Interface to read bytes from a stream
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Interface to write bytes to a stream
type Writer interface {
    Write(p []byte) (n int, err error)
}

// Interface to convert an object into JSON
type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

SRP와 ISP는 확실히 관련이 있지만 하나는 다른 것을 의미하지 않습니다. ISP는 인터페이스 수준이고 SRP는 클래스 수준입니다. 클래스가 여러 간단한 인터페이스를 구현하면 더 이상 하나의 책임이 없을 수도 있습니다.

ISP와 SRP의 차이점을 지적한 Luaan에게 감사합니다.


3
실제로 인터페이스 분리 원리 (SOLID의 "I")를 설명하고 있습니다. SRP는 상당히 다른 짐승입니다.
루안

옆으로, 어떤 코딩 규칙을 사용하고 있습니까? 나는 기대 오브젝트를 UserService 하고 UserUpperCamelCase 할 수 있지만, 방법 Create , Exists그리고 Update나는 lowerCamelCase 만들었을 것입니다.
KlaymenDK

1
@KlaymenDK 당신 말이 맞아, 대문자는 Go를 사용하는 습관입니다 (대문자 = 수출 / 공공, 소문자 = 개인)
Jesse

@Luaan 지적 해 주셔서 감사합니다. 제 대답을 명확하게 해 드리겠습니다
Jesse

1
@KlaymenDK 많은 언어들이 클래스뿐만 아니라 메소드에도 PascalCase를 사용합니다. 예를 들어 C #.
오메가 스틱

15

식당에 요리사가 있습니다. 그의 유일한 책임은 요리하는 것입니다. 그러나 스테이크, 감자, 브로콜리 및 기타 수백 가지를 요리 할 수 ​​있습니다. 메뉴에서 요리당 한 명의 요리사를 고용 하시겠습니까? 또는 각 요리의 각 구성 요소마다 한 명의 요리사가 있습니까? 아니면 자신의 단일 책임을 충족시킬 수있는 한 요리사 : 요리?

그 요리사에게 급여도 요청하면 SRP를 위반하게됩니다.


4

반대 예 : 변경 가능한 상태 저장.

가장 간단한 클래스를 가지고 있다고 가정 해 봅시다 int.

public class State {
    private int i;


    public State(int i) { this.i = i; }
}

하나의 방법으로 제한 된 경우 캡슐화를 중단하고 공개 하지 않는 한 setState(), 또는을 가질 수 있습니다 .getState()i

  • 세터는 게터 없이는 쓸모가 없습니다 (정보를 읽을 수는 없습니다)
  • 게터는 세터 없이는 쓸모가 없습니다 (정보를 절대 변경할 수 없습니다).

그래서 분명히이 하나의 책임은 필요로 이 클래스에 적어도 2 가지 방법을 가지고. QED.


4

단일 책임 원칙을 잘못 해석하고 있습니다.

단일 책임은 단일 방법과 같지 않습니다. 그것들은 다른 것을 의미합니다. 소프트웨어 개발에서 응집력 에 대해 이야기 합니다. "함께"높은 응집력을 가지며 단일 책임을 수행하는 것으로 계산 될 수있는 기능 (방법).

단일 책임 원칙이 충족되도록 시스템을 설계하는 것은 개발자의 책임입니다. 이것을 추상화 기술로 볼 수 있으므로 때로는 의견의 문제입니다. 단일 책임 원칙을 구현하면 코드를 주로 테스트하고 아키텍처 및 디자인을 쉽게 이해할 수 있습니다.


2

모든 언어에서, 특히 OO 언어에서 종종 사물을보고 기능보다는 데이터 의 관점에서 정리하는 것이 도움이됩니다.

따라서 클래스의 책임은 무결성을 유지하고 소유 한 데이터를 올바르게 사용하는 데 도움을주는 것입니다. 모든 코드가 여러 클래스에 분산되어있는 것이 아니라 하나의 클래스에 있으면 더 쉽게 수행 할 수 있습니다. 클래스 의 Point add(Point p)메소드를 사용하여 Point다른 곳에 배치하는 것보다 두 포인트를 더 안정적으로 수행하고 코드를 더 쉽게 유지 보수 할 수 있습니다.

특히, 클래스는 일관성이 없거나 부정확 한 데이터를 초래할 수있는 것은 노출시키지 않아야합니다. 예를 들어, Pointa가 (0,0) ~ (127,127) 평면 내에 있어야 하는 경우 생성자와 새로 수정하거나 생성하는 모든 메소드 Point는 제공된 값을 확인하고이를 위반하는 변경을 거부해야합니다. 요구 사항. (종종 a와 같은 것은 Point변경 불가능하며, Point생성 된 후 를 수정할 수있는 방법이 없다는 것을 보장 하면 클래스의 책임이기도합니다)

여기서 레이어링은 완벽하게 허용됩니다. Point개별 포인트 Polygon를 다루는 클래스와 Points 세트를 다루는 클래스 가있을 수 있습니다 . 이것들은 여전히 ​​별도의 책임을 지니고 있기 때문에 클래스에 대한 (점 과 가치를 모두 갖도록 보장하는 것과 같은) Polygon모든 일을 다루는 모든 책임을 위임 하기 때문 입니다.PointxyPoint

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