인수의 유효성을 검사하는 생성자가 SRP를 위반합니까?


66

가능한 한 SRP (Single Responsibility Principle)를 준수하려고 노력하고 있으며 대의원에 크게 의존하는 특정 패턴 (SRP on Methods)에 익숙해졌습니다. 이 방법이 올바른지 또는 심각한 문제가 있는지 알고 싶습니다.

예를 들어, 생성자에 대한 입력을 확인하기 위해 다음 방법을 도입 Stream할 수 있습니다 ( 입력은 임의적이며 무엇이든 가능)

private void CheckInput(Stream stream)
{
    if(stream == null)
    {
        throw new ArgumentNullException();
    }

    if(!stream.CanWrite)
    {
        throw new ArgumentException();
    }
}

이 방법은 틀림없이 하나 이상의 일을합니다.

  • 입력 확인
  • 다른 예외를 던져라

SRP를 준수하기 위해 논리를 다음과 같이 변경했습니다.

private void CheckInput(Stream stream, 
                        params (Predicate<Stream> predicate, Action action)[] inputCheckers)
{
    foreach(var inputChecker in inputCheckers)
    {
        if(inputChecker.predicate(stream))
        {
            inputChecker.action();
        }
    }
}

한 가지만 수행하는 것은 무엇입니까? 실제로 입력을 확인하고 예외를 던지기 위해 다음과 같은 방법을 소개했습니다.

bool StreamIsNull(Stream s)
{
    return s == null;
}

bool StreamIsReadonly(Stream s)
{
    return !s.CanWrite;
}

void Throw<TException>() where TException : Exception, new()
{
    throw new TException();
}

그리고 CheckInput같이 전화 할 수 있습니다

CheckInput(stream,
    (this.StreamIsNull, this.Throw<ArgumentNullException>),
    (this.StreamIsReadonly, this.Throw<ArgumentException>))

이것이 첫 번째 옵션보다 낫거나 불필요한 복잡성을 도입합니까? 이 패턴이 실제로 가능한 경우 계속 개선 할 수있는 방법이 있습니까?


26
나는이 주장 할 수 있습니다 CheckInput여전히 여러 일을하고있다 : 그것은 모두 배열 반복이다 술어 함수를 호출 하고 조치의 함수를 호출. 그렇다면 SRP를 위반하지 않습니까?
Bart van Ingen Schenau

8
그렇습니다, 그것이 제가 만들려고하는 요점입니다.
Bart van Ingen Schenau

135
그것이 단일 책임 원칙 이라는 것을 기억하는 것이 중요합니다 . 단일 행동 원칙이 아닙니다 . 스트림이 정의되고 쓰기 가능한지 확인하는 것은 하나의 책임이 있습니다.
David Arno

40
이러한 소프트웨어 원칙의 요점은 코드를보다 읽기 쉽고 유지 관리하기 쉽게 만드는 것입니다. 원래 CheckInput은 리팩토링 된 버전보다 훨씬 읽고 이해하기 쉽습니다. 실제로 코드베이스에서 최종 CheckInput 메소드를 발견 한 경우 모든 것을 스크랩하고 원래 있던 것과 일치하도록 다시 작성합니다.
26 개 중

17
이 "원칙"은 원래의 아이디어가 무엇이든 원하는 방식으로 "단일 책임"을 정의 할 수 있기 때문에 실제로는 쓸모가 없습니다. 그러나 엄격하게 적용하려고하면 솔직히 이해하기 어려운 이런 종류의 코드가 생길 것입니다.
Casey

답변:


151

SRP는 아마도 가장 오해 된 소프트웨어 원칙 일 것입니다.

소프트웨어 응용 프로그램은 모듈로 구축되며 모듈로 구축되며 ...

맨 아래에는 하나의 함수 (예 : CheckInput약간의 논리 만 포함)가 있지만 위쪽으로 갈수록 각 연속 모듈 점점 더 많은 논리를 캡슐화 하며 이는 정상 입니다.

SRP는 단일 원자 행동 을하는 것이 아닙니다 . 책임이 여러 작업을 요구하더라도 단일 책임을 갖는 것입니다. 그리고 궁극적으로 유지 보수테스트 가능성 에 관한 것입니다 .

  • 캡슐화를 촉진하고 (신의 대상을 피함)
  • 우려의 분리를 촉진합니다 (전체 코드베이스를 통한 졸졸 변화 방지)
  • 책임 범위를 좁혀 테스트 가능성을 돕습니다.

CheckInput두 가지 검사로 구현되고 두 가지 다른 예외가 발생 한다는 사실 은 어느 정도 관련없습니다 .

CheckInput입력이 요구 사항을 준수하는지 확인하는 것은 좁은 책임이 있습니다. 예, 여러 요구 사항이 있지만 이것이 여러 책임이 있다는 것을 의미하지는 않습니다. 예, 수표를 분할 할 수는 있지만 어떻게 도움이됩니까? 어떤 시점에서 수표는 어떤 방식 으로든 나열되어야합니다.

비교해 봅시다 :

Constructor(Stream stream) {
    CheckInput(stream);
    // ...
}

대:

Constructor(Stream stream) {
    CheckInput(stream,
        (this.StreamIsNull, this.Throw<ArgumentNullException>),
        (this.StreamIsReadonly, this.Throw<ArgumentException>));
    // ...
}

이제는 CheckInput덜 ...하지만 발신자는 더 많은 일을합니다!

요구 사항 목록 CheckInput이 캡슐화 된 Constructor위치에서 보이는 위치 로 이동했습니다.

좋은 변화입니까? 그것은 달려있다 :

  • 경우 CheckInput에만이 호출됩니다 그것은 코드를 클러 한편,이 요구 사항을 볼 수있게 한 손으로, 논쟁의 여지가 있어요;
  • 동일한 요구 사항으로CheckInput 여러 번 호출 되면 DRY를 위반하고 캡슐화 문제가 있습니다.

단일 책임이 많은 작업을 의미 할 수 있음을 인식하는 것이 중요합니다 . 자율 주행 자동차의 "두뇌"는 단일 책임이 있습니다.

목적지까지 차를 운전.

그것은 하나의 책임이지만, 결정을 많이 복용, 센서와 배우의 톤을 조정 필요, 심지어 가능성이 충돌하는 요구 사항이 1 ...

...하지만 모두 캡슐화되어 있습니다. 따라서 클라이언트는 신경 쓰지 않습니다.

1 승객의 안전, 타인의 안전, 규정 존중 ...


2
"캡슐화"라는 단어와 그 파생어를 사용하는 방식이 혼란 스럽다고 생각합니다. 그 외에는 좋은 대답입니다!
Fabio Turati

4
나는 당신의 대답에 동의하지만, 자율 주행 자동차 두뇌 논쟁은 종종 사람들이 SRP를 깰 유혹합니다. 당신이 말했듯이, 그것은 모듈로 만들어진 모듈로 만들어진 모듈입니다. 전체 시스템의 목적을 식별 할 수 있지만 해당 시스템 자체적으로 분리 되어야 합니다. 거의 모든 문제를 해결할 수 있습니다.
Sava B.

13
@SavaB .: 물론이지만 원칙은 동일합니다. 모듈은 구성 요소보다 범위가 크지 만 단일 책임을 가져야합니다.
Matthieu M.

3
자, "운전"은 어떻습니까. 실제로 "운전"은 책임이며 "안전하게"그리고 "법적으로"는 그 책임을 수행하는 방법에 대한 요구 사항입니다. 그리고 우리는 종종 책임을 진술 할 때 요구 사항을 열거합니다.
Brian McCutchon

1
"SRP는 아마도 가장 오해 된 소프트웨어 원칙 일 것입니다." 이 답변에서 알 수 있듯이 :)
Michael

41

SRP에 대한 Bob 아저씨 인용 ( https://8thlight.com/blog/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html ) :

SRP (Single Responsibility Principle)에 따르면 각 소프트웨어 모듈에는 변경해야 할 이유가 하나만 있어야합니다.

...이 원칙은 사람들에 관한 것입니다.

... 소프트웨어 모듈을 작성할 때 변경이 요청 될 때 이러한 변경이 단일 개인 또는 좁은 정의 된 단일 비즈니스 기능을 나타내는 하나의 밀접하게 연결된 단일 그룹에서만 발생할 수 있는지 확인하려고합니다.

... 이것이 우리가 SQL을 JSP에 넣지 않는 이유입니다. 이것이 결과를 계산하는 모듈에서 HTML을 생성하지 않는 이유입니다. 이것이 비즈니스 규칙이 데이터베이스 스키마를 알 수없는 이유입니다. 이것이 우리가 우려를 분리하는 이유입니다.

그는 소프트웨어 모듈이 특정 이해 관계자의 걱정을 해결해야한다고 설명했다. 따라서 귀하의 질문에 대답하십시오 :

이것이 첫 번째 옵션보다 낫거나 불필요한 복잡성을 도입합니까? 이 패턴이 실제로 가능한 경우 계속 개선 할 수있는 방법이 있습니까?

IMO, 당신은 더 높은 수준 (이 경우 클래스 수준)을 볼 때 하나의 방법 만보 고 있습니다. 아마도 우리는 당신의 수업이 현재 무엇을하고 있는지 살펴볼 것입니다 (그리고 이것은 당신의 시나리오에 대한 더 많은 설명이 필요합니다). 현재, 당신의 수업은 여전히 ​​똑같은 일을하고 있습니다. 예를 들어, 내일 일부 유효성 검사에 대한 변경 요청이있는 경우 (예 : "현재 스트림이 null 일 수 있음")이 클래스로 이동하여 해당 클래스 내의 항목을 변경해야합니다.


4
가장 좋은 답변입니다. 가드 검사가 두 개의 서로 다른 이해 관계자들 / 부서에서 오는 경우, OP에 대해 자세히 설명하려면 다음 checkInputs()분할한다로 말을 checkMarketingInputs()하고 checkRegulatoryInputs(). 그렇지 않으면 그것들을 모두 하나의 방법으로 결합하는 것이 좋습니다.
user949300

36

아니요,이 변경 사항은 SRP에서 알리지 않습니다.

검사기에 "전달 된 객체가 스트림" 인지 확인하지 않는 이유를 스스로에게 물어보십시오 . 대답은 명백합니다. 언어는 호출자 가 비 스트림으로 전달 되는 프로그램컴파일 하지 못하게합니다 .

C #의 유형 시스템이 사용자의 요구를 충족시키기에 충분하지 않습니다. 당신의 수표는 오늘날 유형 시스템으로 표현 될 수없는 불변량의 시행을 구현하고 있습니다 . 메소드가 널 입력 불가능한 쓰기 가능 스트림을 사용한다고 말할 방법이 있다면이를 작성했을 것입니다. 그러나 그렇지 않은 경우 다음으로 최선을 다했습니다. 런타임시 유형 제한을 적용했습니다. 여러분의 메소드를 사용하는 개발자가 메소드를 위반하거나 테스트 케이스를 실패한 후 문제점을 해결할 필요가 없도록 문서화하기를 바랍니다.

메소드에 유형을 지정하는 것이 단일 책임 원칙을 위반하지 않습니다. 전제 조건을 시행하거나 사후 조건을 주장하는 방법도 아니다.


1
또한 생성 된 객체를 유효한 상태로 유지하는 것은 생성자가 기본적으로 항상 담당하는 책임입니다. 언급 한 것처럼 런타임 및 / 또는 컴파일러가 제공 할 수없는 추가 검사가 필요한 경우 실제로 그 방법이 없습니다.
SBI

23

모든 책임이 평등 한 것은 아닙니다.

여기에 이미지 설명을 입력하십시오

여기에 이미지 설명을 입력하십시오

여기에 두 개의 서랍이 있습니다. 그들에게는 하나의 책임이 있습니다. 그들 각각은 그들에게 속한 것을 알려주는 이름을 가지고 있습니다. 하나는 식기 서랍입니다. 다른 하나는 쓰레기통입니다.

차이점은 무엇입니까? 식기 서랍은 그 안에 속하지 않은 것을 명확하게합니다. 정크 드로어는 그러나 적합한 것을 받아들입니다. 식기 서랍에서 숟가락을 꺼내는 것은 매우 잘못된 것 같습니다. 그러나 나는 쓰레기통에서 제거되면 놓칠 수있는 것을 생각하기가 힘들다. 진실은 하나의 책임이 있다고 주장 할 수 있지만 더 집중된 단일 책임이 있다고 생각 하는가?

단일 책임을 갖는 개체가 여기에서 한 가지 일만 발생할 수있는 것은 아닙니다. 책임은 중첩 될 수 있습니다. 그러나 이러한 중첩 책임은 이해가되어야합니다. 여기에서 찾을 때 놀라지 말아야하며 사라 졌을 경우 놓쳐 야합니다.

그래서 당신이 제공 할 때

CheckInput(Stream stream);

입력을 확인하고 예외를 던지는 것에 대해 걱정하지 않습니다. 입력을 확인하고 입력을 저장하는 것이 염려됩니다. 놀랍습니다. 사라 졌다면 놓치지 않을 것입니다.


21

중요한 소프트웨어 원칙을 준수하기 위해 매듭을 묶고 이상한 코드를 작성할 때 일반적으로 원칙을 잘못 이해 한 것입니다 (때로는 원칙이 잘못됨). Matthieu의 탁월한 답변이 지적했듯이 SRP의 전체 의미는 "책임"의 정의에 달려 있습니다.

숙련 된 프로그래머는이 원칙을보고이를 우리가 망친 코드의 기억과 관련시킵니다. 경험이 적은 프로그래머는 그것들을보고 전혀 관련이 없습니다. 그것은 공간에 떠 다니는 추상화이며, 모든 미소와 고양이는 없습니다. 그래서 그들은 추측합니다. 그리고 그것은 보통 나빠집니다. 프로그래밍 말 감각을 개발하기 전에 이상한 복잡한 코드와 일반 코드의 차이점은 분명하지 않습니다.

이것은 개인적인 결과에 관계없이 순종해야하는 종교적인 계명이 아닙니다. 프로그래밍 말 감각의 한 요소를 공식화하고 가능한 한 간단하고 명확하게 코드를 유지하는 데 도움이되는 규칙입니다. 반대 효과가있는 경우 외부 입력을 찾으십시오.

프로그램에서, 당신은 단지 그것을 쳐다보고에 의해 첫 번째 원칙에서 식별자의 의미를 추론하는 것보다 훨씬 wronger 갈 수 없어, 그것은 서면 식별자에 간다 대해 실제 코드에서 식별자만큼이나 프로그램.


14

CheckInput 역할

첫째, 나, 거기에 명백한을 두게 CheckInput 되는 이 다양한 측면을 확인하는 경우에도 하나의 일을. 궁극적으로 입력확인합니다 . 호출 된 메소드를 다루는 경우 한 가지가 아니라고 주장 할 수 DoSomething있지만 입력 확인이 모호하지 않다고 가정하는 것이 안전하다고 생각합니다.

술어에이 패턴을 추가하면 입력을 검사하기위한 논리를 클래스에 배치하지 않으려는 경우 유용 할 수 있지만이 패턴은 달성하려는 작업에 대해 장황하게 보입니다. IStreamValidator단일 메소드를 사용 하여 인터페이스 를 전달하는 것이 훨씬 직접적인 방법 isValid(Stream)일 수 있습니다. 구현하는 모든 클래스 IStreamValidator와 같은 조건을 사용할 수 있습니다 StreamIsNull또는 StreamIsReadonly그들이 원하는 경우,하지만 다시 중앙 지점에 도착, 단일 책임 원칙을 유지하는 이익을 만들 수있는 다소 우스꽝스러운 변화입니다.

위생 검사

적어도 널이 아닌 쓰기 가능한 Stream을 처리 할 수 ​​있도록 "위생성 검사"를 허용한다는 것이 제 생각입니다. 기본 검사는 클래스 를 스트림 의 유효성 검사기 로 만드는 것이 아닙니다 . 더 정교한 검사는 수업 외부에 두는 것이 가장 좋지만 그 선이 그려지는 곳입니다. 당신이 그것을 읽거나 검증으로 자원을 전용하여 스트림의 상태를 변경 시작할 필요하면 정식 공연 시작했습니다 확인 스트림의 및 자신의 클래스에 들어갔습니다해야하는 것이다.

결론

내 생각은 당신이 수업의 양상을 더 잘 조직하기 위해 패턴을 적용한다면, 그것은 자신의 수업에 있다는 것이 장점이라는 것입니다. 패턴이 적합하지 않기 때문에 패턴이 실제로 자체 클래스에 속하는지 여부도 질문해야합니다. 내 생각은 스트림의 유효성 검사가 미래에 변경 될 것이라고 생각하지 않는 한, 특히이 유효성 검사가 본질적으로 역동적 일 가능성이 있다고 생각되면 설명 한 패턴이 좋습니다. 처음에는 사소합니다. 그렇지 않으면 임의로 프로그램을 더 복잡하게 만들 필요가 없습니다. 스페이드를 스페이드라고 부를 수 있습니다. 유효성 검사는 한 가지이지만 null 입력을 확인하는 것은 유효성 검사가 아니므로 단일 책임 원칙을 위반하지 않고 클래스에 안전하게 보관할 수 있다고 생각합니다.


4

이 원칙은 코드 조각에 "단일 작업 만 수행"해야한다는 것을 강조하지 않습니다.

SRP의 "책임"은 요구 사항 수준에서 이해해야합니다. 코드의 책임은 비즈니스 요구 사항을 충족시키는 것입니다. 개체가 둘 이상의 독립적 인 비즈니스 요구 사항을 충족하면 SRP가 위반됩니다 . 독립적으로 이는 하나의 요구 사항이 변경 될 수 있고 다른 요구 사항은 그대로 유지됨을 의미합니다.

새로운 비즈니스 요구 사항이 도입되었다는 것을 생각할 수 있는데, 이는이 특정 개체 읽을 수 있는지 확인 하지 않아야 하는 반면 다른 비즈니스 요구 사항은 여전히 ​​개체가 읽을 수 있는지 확인해야합니까? 비즈니스 요구 사항은 해당 수준에서 구현 세부 정보를 지정하지 않기 때문에 아니요.

SRP 위반의 실제 예는 다음과 같습니다.

var message = "Your package will arrive before " + DateTime.Now.AddDays(14);

이 코드는 매우 간단하지만 텍스트는 비즈니스의 다른 부분에 의해 결정되므로 예상 배달 날짜와 독립적으로 텍스트가 변경 될 수 있습니다.


사실상 모든 요구 사항에 대한 다른 클래스는 악의없는 악몽처럼 들립니다.
whatsisname

@ whatsisname : 그렇다면 아마도 SRP는 당신을위한 것이 아닙니다. 모든 종류와 규모의 프로젝트에는 디자인 원칙이 적용되지 않습니다. (그러나 우리는 단지 요구 사항 이 아니라 독립적 인 요구 사항 (즉, 독립적으로 변경 될 수 있음)에 대해서만 이야기하고 있다는 점에
유의하십시오

나는 SRP가 상황에 맞는 판단 요소를 필요로한다고 생각한다.
whatsisname

@whatsisname : 전적으로 동의합니다.
JacquesB

개체가 둘 이상의 독립적 인 비즈니스 요구 사항을 충족하면 SRP의
Juzer Ali

3

@EricLippert의 답변 에서 요점을 좋아합니다 .

검사기에서 전달 된 객체가 스트림 인지 확인하지 않는 이유를 스스로에게 물어보십시오 . 대답은 명백합니다. 언어는 호출자 가 비 스트림으로 전달 되는 프로그램컴파일 하지 못하게합니다 .

C #의 유형 시스템이 사용자의 요구를 충족시키기에 충분하지 않습니다. 당신의 수표는 오늘날 유형 시스템으로 표현 될 수없는 불변량의 시행을 구현하고 있습니다 . 메소드가 널 입력 불가능한 쓰기 가능 스트림을 사용한다고 말할 방법이 있다면이를 작성했을 것입니다. 그러나 그렇지 않은 경우 다음으로 최선을 다했습니다. 런타임시 유형 제한을 적용했습니다. 여러분의 메소드를 사용하는 개발자가 메소드를 위반하거나 테스트 케이스를 실패한 후 문제점을 해결할 필요가 없도록 문서화하기를 바랍니다.

EricLippert는 이것이 유형 시스템의 문제라고 맞습니다. 그리고 단일 책임 원칙 (SRP)을 사용하고자하므로 기본적으로이 작업을 담당 할 유형 시스템이 필요합니다.

실제로 C # 에서이 작업을 수행 할 수 있습니다. 우리는 문자 그대로 잡을 수 null'의 다음 이외의 문자 잡기, 컴파일시에들 null실행시의'의. 그것은 완전한 컴파일 타임 점검만큼 좋지는 않지만 컴파일 타임을 놓치지 않는 것에 비해 엄격한 개선입니다.

그래서 C #이 어떻게 작동하는지 알고 Nullable<T>있습니까? 그것을 뒤집고 NonNullable<T>:

public struct NonNullable<T> where T : class
{
    public T Value { get; private set; }
    public NonNullable(T value)
    {
        if (value == null) { throw new NullArgumentException(); }
        this.Value = value;
    }
    //  Ease-of-use:
    public static implicit operator T(NonNullable<T> value) { return value.Value; }
    public static implicit operator NonNullable<T>(T value) { return new NonNullable<T>(value); }

    //  Hack-ish overloads that prevent null-literals from being implicitly converted into NonNullable<T>'s.
    public static implicit operator NonNullable<T>(Tuple<T> value) { return new NonNullable<T>(value.Item1); }
    public static implicit operator NonNullable<T>(Tuple<T, T> value) { return new NonNullable<T>(value.Item1); }
}

글쓰기 대신

public void Foo(Stream stream)
{
  if (stream == null) { throw new NullArgumentException(); }

  // ...method code...
}

, 그냥 써:

public void Foo(NonNullable<Stream> stream)
{
  // ...method code...
}

그런 다음 세 가지 사용 사례가 있습니다.

  1. Foo()널이 아닌 사용자 호출 Stream:

    Stream stream = new Stream();
    Foo(stream);

    이것은 원하는 유스 케이스이며 with 또는 without없이 작동합니다 NonNullable<>.

  2. Foo()null 인 사용자 호출 Stream:

    Stream stream = null;
    Foo(stream);

    이것은 호출 오류입니다. 여기 NonNullable<>에서는 사용자가이 작업을 수행해서는 안된다고 알려주지 만 실제로는 중지하지 않습니다. 어느 쪽이든, 결과적으로 런타임이 발생합니다 NullArgumentException.

  3. 사용자는 전화 Foo()null:

    Foo(null);

    null암시 적으로로 변환되지 않으므로 런타임 전에NonNullable<> IDE에서 오류가 발생합니다 . 이것은 SRP가 조언하는 것처럼 널 검사를 유형 시스템에 위임하는 것입니다.

이 방법을 확장하여 인수에 대한 다른 사항을 주장 할 수도 있습니다. 예를 들어, 쓰기 가능한 스트림을 원 하므로 생성자 와 생성자 struct WriteableStream<T> where T:Stream모두를 검사 하는 을 정의 할 수 있습니다 . 여전히 런타임 유형 검사이지만 다음과 같습니다.nullstream.CanWrite

  1. WriteableStream한정자를 사용 하여 유형을 장식하여 호출자의 필요성을 알립니다.

  2. 코드에서 한 곳에서 검사를 수행하므로 throw InvalidArgumentException매번 검사를 반복 할 필요가 없습니다 .

  3. 형식 검사 업무를 형식 시스템에 밀어 넣어 (일반 데코레이터가 확장 한대로) SRP를 더 잘 준수합니다.


3

귀하의 접근 방식은 현재 절차 적입니다. Stream객체를 분리 하여 외부에서 확인하고 있습니다. 그렇게하지 마십시오-캡슐화가 깨집니다. (가)하자 Stream자신의 검증에 대한 책임. SRP를 적용 할 클래스가 생길 때까지 SRP를 적용 할 수 없습니다.

Stream유효성 검사를 통과 한 경우에만 작업을 수행하는 a는 다음과 같습니다 .

class Stream
{
    public void someAction()
    {
        if(!stream.canWrite)
        {
            throw new ArgumentException();
        }

        System.out.println("My action");
    }
}

그러나 지금 우리는 SRP를 위반하고 있습니다! "수업을 변경해야 할 이유는 한 가지뿐입니다." 우리는 1) 유효성 검사와 2) 실제 논리가 혼합되어 있습니다. 변경해야 할 두 가지 이유가 있습니다.

우리는 이것을 데코레이터검증 하여 해결할 수 있습니다 . 먼저 Stream인터페이스 로 변환하고 이를 구체적인 클래스로 구현해야합니다.

interface Stream
{
    void someAction();
}

class DefaultStream implements Stream
{
    @Override
    public void someAction()
    {
        System.out.println("My action");
    }
}

이제 a를 감싸고 Stream, 유효성 검사를 수행하고 Stream, 동작의 실제 논리에 대해 지정된 데코레이터를 작성할 수 있습니다 .

class WritableStream implements Stream
{
    private final Stream stream;

    public WritableStream(final Stream stream)
    {
        this.stream = stream;
    }

    @Override
    public void someAction()
    {
        if(!stream.canWrite)
        {
            throw new ArgumentException();
        }
        stream.someAction();
    }
}

이제 원하는 방식으로 구성 할 수 있습니다.

final Stream myStream = new WritableStream(
    new DefaultStream()
);

추가 검증을 원하십니까? 다른 데코레이터를 추가하십시오.


1

수업의 직업은 계약 충족시키는 입니다. 클래스에는 항상 계약이 있습니다. 클래스를 사용하기위한 요구 사항 세트이며 요구 사항이 충족되면 해당 상태 및 출력에 대해 약속합니다. 이 계약은 문서 및 / 또는 주장을 통해 명시 적이거나 암시적일 수 있지만 항상 존재합니다.

클래스 계약의 일부는 호출자가 생성자가 null이 아니어야하는 인수를 생성자에게 제공한다는 것입니다. 계약 이행 입니다 호출 측이 계약의 그 부분을 충족 쉽게 클래스의 책임의 범위 내에있는 것으로 간주 될 수 있음을 확인할 수 있도록 클래스의 책임.

클래스가 계약을 구현한다는 아이디어 는 Eiffel 프로그래밍 언어의 디자이너 인 Bertrand Meyer 와 계약에 의한 디자인 아이디어 때문입니다 . 에펠 언어는 언어의 계약 부분을 지정하고 확인합니다.


0

다른 답변에서 지적했듯이 SRP는 종종 오해됩니다. 하나의 기능만을 수행하는 원자 코드를 갖는 것이 아닙니다. 객체와 메소드가 한 가지 작업 만 수행하고 한 가지 작업은 한 곳에서만 수행해야합니다.

의사 코드에서 좋지 않은 예를 살펴 보겠습니다.

class Math
    private int a;
    private int b;
    def constructor(int x, int y) 
        if(x != null)
          a = x
        else if(x < 0)
          a = abs(x)
        else if (x == -1)
          throw "Some Silly Error"
        else
          a = 0
        end
        if(y != null)
           b = y
        else if(y < 0)
           b = abs(y)
        else if(y == -1)
           throw "Some Silly Error"
        else
         b = 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

다소 터무니없는 예에서 Math # constructor의 "책임"은 수학 객체를 사용 가능하게 만드는 것입니다. 먼저 입력을 삭제 한 다음 값이 -1이 아닌지 확인하십시오.

생성자가 한 가지 작업 만 수행하므로 유효한 SRP입니다. Math 객체를 준비 중입니다. 그러나 유지 관리가 쉽지 않습니다. 드라이를 위반합니다.

그래서 또 다른 패스를

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        cleanX(x)
        cleanY(y)
    end
    def cleanX(int x)
        if(x != null)
          a = x
        else if(x < 0)
          a = abs(x)
        else if (x == -1)
          throw "Some Silly Error"
        else
          a = 0
        end
   end
   def cleanY(int y)
        if(y != null)
           b = y
        else if(y < 0)
           b = abs(y)
        else if(y == -1)
           throw "Some Silly Error"
        else
         b = 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

이 패스에서는 DRY에 대해 조금 더 나아졌지 만 DRY와 함께 갈 수있는 방법이 있습니다. 반면에 SRP는 약간 벗어난 것 같습니다. 우리는 이제 같은 일을하는 두 가지 기능을 가지고 있습니다. cleanX와 cleanY는 모두 입력을 삭제합니다.

또 다른 걸 줄 수 있습니다

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        a = clean(x)
        b = clean(y)
    end
    def clean(int i)
        if(i != null)
          return i
        else if(i < 0)
          return abs(i)
        else if (i == -1)
          throw "Some Silly Error"
        else
          return 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

이제 DRY에 대해 마침내 나아졌고 SRP는 동의 한 것으로 보입니다. "위생"업무를 수행하는 곳은 한 곳뿐입니다.

코드는 이론적으로 유지 관리가 쉽고 우수하지만 버그를 수정하고 코드를 강화할 때 한곳에서만 수행하면됩니다.

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        a = clean(x)
        b = clean(y)
    end
    def clean(int i)
        if(i == null)
          return 0
        else if (i == -1)
          throw "Some Silly Error"
        else
          return abs(i)
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

대부분의 실제 상황에서 객체는 더 복잡하고 SRP는 여러 객체에 적용됩니다. 예를 들어 나이는 아버지, 어머니, 아들, 딸에 속할 수 있습니다. 따라서 생년월일로부터 나이를 계산하는 4 개의 클래스 대신에이를 수행하는 Person 클래스가 있고 4 개의 클래스가 상속됩니다. 그러나이 예제가 설명하는 데 도움이되기를 바랍니다. SRP는 원 자성 행동이 아니라 "작업"에 관한 것입니다.


-3

SRP에 관해 말하면, 밥 아저씨는 모든 곳에서 널 체크를하는 것을 좋아하지 않습니다. 일반적으로 팀은 가능할 때마다 생성자에 null 매개 변수를 사용하지 않아야합니다. 팀 외부에 코드를 게시하면 상황이 변경 될 수 있습니다.

문제의 클래스의 응집력을 보장하지 않고 생성자 매개 변수의 널 (null)을 강제하지 않으면 호출 코드, 특히 테스트가 부풀어 오른다.

그러한 계약을 실제로 시행 Debug.Assert하려면 혼란을 줄이기 위해 또는 유사한 것을 사용하는 것이 좋습니다 .

public AClassThatDefinitelyNeedsAWritableStream(Stream stream)
{
   Assert.That(stream.CanWrite, "Put crucial information here, and not inane bloat.");

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