Android/아키텍처&디자인패턴

소프트웨어 설계 원칙 SOLID

SimSim 2021. 6. 22. 07:54

설계 원칙 SOLID

SOLID 원칙은 함수와 데이터 구조를 클래스로 배치하는 방법, 그리고 이들 클래스를 서로 결합하는 방법을 설명한다.
'클래스'라는 단어를 사용했다고 해서 SOLID 원칙이 객체 지향 소프트웨어에만 적용된다는 뜻은 아니다.
여기에서 클래스는 단순히 함수와  데이터를 결합한 집합을 가리킨다. SOLID 원칙은 이러한 집합에 적용된다.

SOLID 원칙의 목적

중간 수준(모듈 수준)의 소프트웨어 구조가 아래의 특징을 같도록 만드는 데 있다.
코드 수준보다는 조금 상위에서 적용되며 모듈과 컴포넌트 내부에서 사용되는 소프트웨어 구조를 정의하는데 도움을 준다.

* 변경에 유연하다.
* 이해하기 쉽다.
* 많은 소프트웨어 시스템에 사용될 수 있는 컴포넌트의 기반이 된다.

SRP: 단일 책임 원칙 Single Responsibility Principle

소프트웨어 시스템이 가질 수 있는 최적의 구조는 시스템을 만드는 조직의 사회적 구조에 커다란 영향을 받는다.
따라서 각 소프트웨어 모듈은 변경의 이유가 하나, 단 하나여야만 한다.


※ 하나의 일만 해야한다는 원칙이 아니다.
  <Clean Architecture> 책에서는 "하나의 모듈은 하나의, 오직 하나의 액터에 대해서만 책임져야 한다"고 한다.
  여기서 책임은 '변경의 이유에 대한 책임'이라고 보면된다.
  여기서 액터(actor)란 변경을 요청하는 한 명 이상의 사람들, 이러한 집단을 액터라고 부른다.
* 단일 액터를 책임지는 코드를 함께 묶어주는 힘이 바로 응집성(cohesive)이다.
* 단일 책임을 갖게하는 SRP에 따른 설계를 하면 응집도는 높아지고 결합도는 낮아진다.
* SRP를 위반
 -> 사례(우발적 중복, 병합) : Employ 클래스가 가지고 있는 3가지 메서드가 서로 매우 다른 세 명의 액터를 책임짐
 -> 해결: 퍼사드패턴(facade)으로 서로 다른 액터가 의존하는 코드를 서로 분리
* 단일 책임 원칙은 메서드와 클래스 수준의 원칙이다. 이보다 상위 수준의 두 수준에서도 다른 형태로 다시 등장한다.
컴포넌트 수준에서는 공통 폐쇄 원칙, 아키텍처 수준에서는 아키텍처 경계의 생성을 책임지는 변경의 축이 된다.
 


OCP: 개방 폐쇄 원칙 Open Close Principle

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

 

소프트웨어 개체(artifact)는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.

* OCP는 시스템의 아키텍처를 떠받치는 원동력 중 하나다. OCP의 목표는 시스템을 확장하기 쉬운 동시에
변경으로 인해 시스템이 너무 많은 영향을 받지 않도록 하는 데 있다. 이러한 목표를 달성하려면 시스템을 컴포넌트 단위
로 분리하고, 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있는 향태의 의존성 계층구조가 만들어지도록
해야 한다.


LSP: 리스코프 치환 원칙 Liskov Substitution Principle

하위 타입에 관한 유명한 원칙이다. 상호 대체 가능한 구성요소를 이용해 소프트웨어 시스템을 만들 수 있으려면
이들 구성 요소는 반드시 서로 치환 가능해야 한다는 원칙이다.

* 상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야한다"는 원칙
* (참고) 상속을 했는데 isCheck() 함수나 instanceOf 같은 키워드가 필요하다는 것은 둘의 관계가 상속 관계에 있지 않다는 반증이다.

흔한 리스코프 치환의 원칙 위반 사례
1. 명시된 명세에서 벗어난 값을 리턴한다.
2. 명시된 명세에서 벗어난 익셉션을 발생시킨다.
3. 명시된 명세에서 벗어난 기능을 수행한다.

ISP: 인터페이스 분리 원칙 Interface Segregation Principle

소프트웨어 설계자는 사용하지 않은 것에 의존하지 않아야 한다.

 

정적 타입 언어는 사용자가 import, use 또는 include와 같은 타입 선언문을 사용하도록 강제한다.
이처럼 소스 코드에 포함된 선언문으로 인해 소스 코드 의존성이 발생하고, 이로 인해 재컴파일 또는 재배포가 강제되는
상황이 무조건 초래된다.

파이썬처럼 동적 타입 언어에서는 소스 코드에 이러한 선언문이 존재하지 않는다.
대신 런타임에 추론이 발생한다. 따라서 소스코드의 의존성이 아예 없고 결국 재컴파일과 재배포가
필요없다. 동적 타입 언어를 사용하면 정적 타입 언어를 사용할 때보다 유연하며 결합도가 낮은 시스템을
만들 수 있는 이유는 바로 이 때문이다.

이러한 사실로 인해 ISP를 아키텍처가 아니라 언어와 관련된 문제라고 결론내릴 여지가 있다.

한걸음 물러서서 ISP를 사용하는 근본적인 동기를 살펴보면 잠재되어 있는 더 깉은 우려사항을 볼 수 있다.
일반적으로 필요 이상으로 많은 걸 포함하는 모듈에 의존하는 것은 해로운 일이다. 소스 코드 의존성의 경우
이는 분명한 사실인데 불필요한 재컴파일과 재배포를 강제하기 때문이다.

 

DIP: 의존성 역전 원칙 Dependency Inversion Principle

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

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

자바와 같은 정적 타입 언어에서 이 말은 use, import, include 구문은 오직 인터페이스나
추상 클래스 같은 추상적인 선언만을 참조해야 한다는 뜻이다. 구체적인 대상에는 절대로 의존해서는
안 된다.

안정된 추상화를 위한 코딩 실천법
* 변동성이 큰 구체 클래스를 참조하지 말라.
* 변동성이 큰 구체 클래스로부터 파생하지 말라.
* 구체 함수를 오버라이드 하지 말라.
* 구체적이며 변동성이 크다면 절대로 그 이름을 언급하지 말라.

 

 

 

- 로버트 C. 마틴 <클린 아키텍처> -