소프트웨어 공부/아키텍쳐

SOLID 원칙 ( 좋은 아키텍처를 정의하는 원칙 )

야곰야곰+책벌레 2021. 4. 12. 13:53
728x90
반응형

1. SRP(Single Responsibility Principle) : 단일 책임 원칙

콘웨이(Conway) 법칙에 따름 정리 : 각 Software module은 변경의 이유가 하나, 단 하나여야만 한다.

Module이 하나의 일을 해야한다는 의미는 아니다. 해당 Module의 변경을 요청하는 한 명 이상의 사람들(actor)에 대해서만 책임을 져야 한다는 것이다.

이 때 Module은 소스가 될 수도 있고 함수나 데이터베이스가 될 수도 있다.

 

1.1. 우발적 중복

  이 클래스는 SRP를 위반하는데, 이들 3가지 Method가 서로 매우 다른 3명의 actor를 책임지기 때문이다.

calculatePay() method는 회계팀에서 기능을 정의하며, CFO 보고를 위해 사용한다.

reportHours() method는 인사팀에서 기능을 정의하며, COO 보고를 위해 사용한다.

save() method는 DB관리자(DBA)가 기능을 정의하고, CTO 보고를 위해 사용한다.

이 결합으로 CFO 팀에서 결정한 조치가 COO팀에 의존하는 무언가에 영향을 줄 수 있다.

 

SRP는 서로 다른 actor가 의존하는 코드를 서로 분리하라고 말한다.

 

1.3 병합

소스 파일에 다양하고 많은 method를 포함하면 병합이 자주 발생하리라고 짐작하는 것은 어려운 일이 아니다.

특히 이들 method가 서로 다른 actor를 책임진다면 병합이 발생할 가능성은 확실히 더 높다.

어떤 도구도 병합이 발생하는 모든 경우를 해결할 수는 없다. 결국 병합에는 항상 위험이 뒤따르게 된다.

이 문제를 벗어나는 방법은 서로 다른 actor를 뒷받침하는 코드를 서로 분리하는 것이다.

가장 중요한 업무 규칙을 data와 가깝게 배치하는 방식.

이 경우라면 가장 중요한 method는 기존의 Employee class에 그대로 유지하되, Employee class를 덜 중요한 나머지 method들에 대한 Facade로 사용할 수도 있다.

단일 책임 원칙은 method와 class 수준의 원칙이다.

 

2. OCP (Open-Closed Principle) : 개방-폐쇄 원칙

기존 코드를 수정하기보다는 반드시 새로운 코드를 추가하는 방식으로 시스템의 행위를 변경할 수 있도록 설계해야만 Software system을 쉽게 변경할 수 있다.

Software artifact는 확장에 열려 있어야 하고, 변경에는 닫혀 있어야 한다. 다시 말해 Software artifact의 행위는 확장할 수 있어야 하지만, 이때 개체를 변경해서는 안 된다.

Software architecture를 공부하는 가장 근본적인 이유가 바로 이 때문이다.

서로 다른 목적으로 변경되는 요소를 적절하게 분리하고 (단일 책임 원칙, SRP), 이들 요소 사이의 의존성을 체계화함으로써(의존성 역전 원칙, DIP) 변경량을 최소화할 수 있다.

여기에서 얻을 수 있는 가장 중요한 영감은 보고서 생성이 두 개의 책임으로 분리된다는 사실이다. 하나는 보고서용 데이터를 계산하는 책임이며, 나머지 하나는 이 데이터를

웹으로 보여주거나 종이로 프린트하기에 적합한 형태로 표현하는 책임이다.

이처럼 책임을 분리했다면, 두 책임 중 하나에 변경이 발생하더라도 다른 하나는 변경되지 않도록 소스 코드 의존성도 확실히 조직화해야 한다.

모든 Component 관계는 단방향으로 이루어진다.

Presenter에서 발생한 변경으로부터 Controller를 보호하고자 한다. 그리고 View에서 발생한 변경으로부터 Presenter를 보고하고자 한다.

View에서 발생한 변경으로부터 Presenter를 보고하고자 한다. Interactor는 다른 모든 것에서 발생하는 변경으로부터 보호하고자 한다.

Interactor는 OCP를 가장 잘 준수할 수 있는 곳에 위치한다. Database, Controller, Presenter, View에서 발생한 어떤 변경도 Interactor에 영향을 주지 않는다.

Interactor가 특별한 위치를 차지하는 것은 업무 규칙을 포함하기 때문이다. Interactor는 Application에서 가장 높은 수준의 정책을 포함한다.

Architecture는 기능이 어떻게, 왜, 언제 발생하는지에 따라서 기능을 분리하고, 분리한 기능을 컴포넌트의 계층 구조로 조직화 한다.

이와 같이 조직화하면 저수준 Component에서 발생한 변경으로부터 고수준 Component를 보호할 수 있다.

 

3. LSP ( Liskov Substitution Principle ) : 리스코프 치환 원칙

상호 대체 가능한 구송요소를 이용해 Software system을 만들 수 있으려면, 이들 구성요소는 반드시 서로 치환 가능해야 한다는 계약을 반드시 지켜야 한다.

S타입의 객체 O1 각각에 대응하는 T타입 객체 O2가 있고, T타입을 이용해서 정의한 모든 프로그램 P에서 O2의 자리에 O1을 치환하더라도 P의 행위가 변하지 않는다면, S는 T의 하위 타입이다.

3.1 상속을 사용하도록 가이드 하기

이 class는 CalcFee()라는 method를 가지며, Billing Application에서 이 method를 호출한다.

License에는 PersonalLicense와 BusinessLicense라는 두 가지 '하위 타입'이 존재한다.

이들 두 하위 타입은 서로 다른 알고리즘을 이용해서 License 비용을 계산한다.

이 설계는 LSP를 준수하는데, Billing Application의 행위가 License 하위 타입 중 무엇을 사용하는지에 전혀 의존하지 않기 때문이다.

 

4. ISP (Interface Segregation Principle) : 인터페이스 분리 원칙

Software 설계자는 사용하지 않은 것에 의존하지 않아야 한다.

다수의 사용자가 OPS class의 Operation을 사용한다. User1은 오직 op1을, User2는 op2만을, User3는 op3만을 사용한다고 하자.

그리고 OPS가 정적 타입 언어로 작성된 class라고 해보자. 이 경우 User1에서 op2와 op3를 전혀 사용하지 않음에도 User1의 소스 코드는 이 두 method에 의존하게 된다.

이러한 의존성으로 인해 OPS class에서 op2의 소스 코드가 변경되면 User1도 다시 컴파일한 후 새로 배포해야 한다.

이번에도 마찬가지로 정적 타입 언어로 이 Diagram을 구현했다고 가정하면, User1의 소스 코드는 U1Ops와 op1에 의존하지만 OPS에는 의존하지 않게 된다.

따라서 OPS에서 발생한 변경이 User1과는 전혀 관계없는 변경이라면, User1을 다시 컴파일하고 새로 배포하는 상황은 초래되지 않는다.

 

5. DIP (Dependency Inversion Principle) : 의존성 역전 원칙

고수준 정책을 구현하는 코드는 저수준 세부사항을 구현하는 코드에 절대로 의존해서는 안 된다. 
대신 세부사항이 정책에 의존해야 한다.

'유연성이 극대화된 시스템'이란 소스코드 의존성이 추상에 의존하며 구체에는 의존하지 않는 시스템이다.

JAVA 같은 정적 타입 언어에서 이 말은 use, import, include 구문은 오직 인터페이스나 추상 class 같은 추상적인 선언만을 참조해야 한다는 뜻이다.

추상 인터페이스에 변경이 생기면 이를 구체화한 구현체들도 따라서 수정해야 한다.

반대로 구체적인 구현체에 변경이 생기더라도 그 구현체가 구현하는 인터페이스는 대부분 변경될 필요가 없다. 인터페이스는 구현체보다 변동성이 낮다.

안정된 Software Architecture란 변동성이 큰 구현체에 의존하는 일은 지양하고, 안정된 추상 인터페이스를 선호하는 Architecture라는 뜻이다.

728x90
반응형