SRP는 확실하지 않은 용어로 클래스가 변경해야 할 이유가 하나만 있어야한다고 언급합니다.
질문에서 "report"클래스를 해체 할 때 세 가지 방법이 있습니다.
printReport
getReportData
formatReport
Report
모든 방법에서 중복 이 사용되는 것을 무시하면 이것이 왜 SRP를 위반하는지 쉽게 알 수 있습니다.
"인쇄"라는 용어는 일종의 UI 또는 실제 프린터를 의미합니다. 따라서이 클래스에는 일정량의 UI 또는 프리젠 테이션 로직이 포함됩니다. UI 요구 사항을 변경하려면 Report
클래스를 변경해야합니다 .
"데이터"라는 용어는 어떤 종류의 데이터 구조를 의미하지만 실제로 무엇을 지정하지는 않습니다 (XML? JSON? CSV?). 그럼에도 불구하고 보고서의 "내용"이 변경되면이 방법도 변경됩니다. 데이터베이스 또는 도메인에 연결되어 있습니다.
formatReport
는 일반적으로 메서드의 끔찍한 이름이지만 UI와 관련이 있으며 UI와는 다른 측면이 있다고 생각 printReport
합니다. 따라서 관련이없는 또 다른 이유가 있습니다.
따라서이 클래스는 데이터베이스, 스크린 / 프린터 장치 및 로그 나 파일 출력 등을위한 내부 형식 지정 논리 와 결합 될 수 있습니다 . 한 클래스에 세 가지 함수를 모두 사용하면 종속성 수를 곱하고 종속성 또는 요구 사항 변경으로 인해이 클래스 (또는 그에 의존하는 다른 클래스)가 중단 될 확률이 3 배가됩니다.
여기서 문제의 일부는 특히 가시적 인 예를 골랐다는 것입니다. 한 가지만Report
수행하더라도. 라는 클래스가 없어야합니다 . 왜냐하면 ... 어떤 보고서입니까? 다른 데이터와 다른 요구 사항에 따라 모든 "보고"가 완전히 다른 짐승이 아닌가? 그리고 스크린이나 인쇄용으로 이미 포맷 된 보고서가 아닌가 ?
그러나 과거를 살펴보고 가상의 구체적인 이름을 만들어 봅시다. IncomeStatement
하나는 매우 일반적인 보고서입니다. 적절한 "SRP"아키텍처는 세 가지 유형이 있습니다.
IncomeStatement
- 형식이 지정된 보고서에 나타나는 정보 를 포함 및 / 또는 계산 하는 도메인 및 / 또는 모델 클래스 .
IncomeStatementPrinter
, 아마도 같은 표준 인터페이스를 구현할 것입니다 IPrintable<T>
. 하나의 주요 방법 Print(IncomeStatement)
과 인쇄 별 설정 구성을위한 다른 방법 또는 속성이 있습니다.
IncomeStatementRenderer
화면 렌더링을 처리하며 프린터 클래스와 매우 유사합니다.
또한 IncomeStatementExporter
/ 와 같은 기능별 클래스를 추가 할 수도 있습니다 IExportable<TReport, TFormat>
.
이것은 제네릭 및 IoC 컨테이너를 도입하여 현대 언어에서 훨씬 더 쉬워졌습니다. 대부분의 응용 프로그램 코드는 특정 IncomeStatementPrinter
클래스 에 의존 할 필요가 없으며 모든 종류의 인쇄 가능한 보고서를 사용 IPrintable<T>
하여 작동 할 수 있습니다.이 방법을 사용 하면 기본 클래스 의 모든 이점을 방법과 함께 제공하며 일반적인 SRP 위반은 없습니다 . 실제 구현은 IoC 컨테이너 등록에서 한 번만 선언하면됩니다.Report
print
일부 사람들은 위의 디자인에 직면했을 때 다음과 같이 응답합니다. "이것은 절차 코드처럼 보이며 OOP의 요점은 데이터와 동작의 분리로부터 우리를 떠나는 것입니다!" 내가 말하는 것 : wrong .
는 IncomeStatement
것입니다 하지 그냥 "데이터", 및 상기 실수는 그들이에 관련이없는 기능을 모든 종류의 방해 시작 이후 이러한 "투명"클래스를 생성하여 뭔가 잘못을하고있다 느낌 OOP의 사람들의 많은 원인이 무엇 인 IncomeStatement
것을, (물론 게으름). 이 클래스는 데이터로 시작될 수 있지만 시간이 지남에 따라 더 많은 모델이 될 것 입니다.
예를 들어 실제 손익 계산서에는 총 수익 , 총 비용 및 순이익 라인이 있습니다. 적절하게 설계된 금융 시스템은 거래 데이터가 아니기 때문에 이러한 데이터를 저장 하지 않을 가능성이 높습니다 . 실제로 새로운 거래 데이터의 추가에 따라 변경됩니다. 그러나 이러한 선의 계산 은 보고서 인쇄, 렌더링 또는 내보내기에 관계없이 항상 동일합니다. 당신의 그래서 IncomeStatement
클래스의 형태로 그것에 행동의 공정한 금액을해야 할 것입니다 getTotalRevenues()
, getTotalExpenses()
및 getNetIncome()
방법, 그리고 아마도 몇 가지 다른 사람. 실제로 "하지"않는 것처럼 보이지만 자체 동작이있는 진정한 OOP 스타일의 객체입니다.
그러나 format
및 print
방법은 정보 자체와 관련이 없습니다. 실제로, 이러한 방법의 여러 가지 구현 , 예를 들어 경영진에 대한 자세한 설명 및 주주에 대한 자세한 설명이 필요하지는 않습니다. 이러한 독립 함수를 다른 클래스로 분리하면 모든 크기의 단일 print(bool includeDetails, bool includeSubtotals, bool includeTotals, int columnWidth, CompanyLetterhead letterhead, ...)
메소드에 대한 부담없이 런타임에 다른 구현을 선택할 수 있습니다 . 왝!
위의 대규모 매개 변수화 된 메소드가 잘못되는 부분과 별도의 구현이 올바른 곳을 알 수 있기를 바랍니다. 단일 개체의 경우 인쇄 논리에 새 주름을 추가 할 때마다 도메인 모델을 변경해야합니다 ( 재무 팀은 페이지 번호를 원하지만 내부 보고서에서만이를 추가 할 수 있습니까? ). 하나 또는 두 개의 위성 클래스에 구성 속성을 추가하기 만하면됩니다.
SRP를 올바르게 구현하는 것은 종속성 관리에 관한 것입니다 . 요컨대, 클래스가 이미 유용한 것을 수행하고 UI, 프린터, 네트워크, 파일 등과 같은 새로운 종속성을 도입하는 다른 메소드를 추가하는 것을 고려하고 있다면 안됩니다 . 대신 이 기능을 새 클래스 에 추가 할 수있는 방법과이 새 클래스를 전체 아키텍처에 적합하게 만드는 방법을 고려하십시오 (종속성 주입을 중심으로 디자인 할 때 매우 쉽습니다). 이것이 일반적인 원칙 / 과정입니다.
참고 사항 : Robert와 마찬가지로 SRP 호환 클래스에는 하나 또는 두 개의 상태 변수 만 있어야한다는 개념을 특허 적으로 거부합니다. 이러한 얇은 래퍼는 실제로 유용한 기능을 거의 기대하지 않습니다. 따라서 이것으로 배 밖으로 가지 마십시오.