클린 아키텍처 - 설계원칙 SOLID

jaehee kim·2022년 1월 22일
1

Clean Architecture

목록 보기
1/7
post-thumbnail

SOLID?

좋은 코드로 좋은 아키텍처를 정의하는 원칙. 함수와 데이터 구조를 클래스로 배치하는 방법으로 이 클래스를 서로 결합하는 방법

목적

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


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

각 소프트웨어 모듈은 변경의 이유가 하나여야만 한다.

여기서 '변경의 이유' 는 변경을 요청하는 집단(actor 액터)라고 정의할 수 있다.

원칙을 위반하는 징후 예시 : 우발적 중복


3가지 메서드가 서로 다른 3명의 액터를 책임지고 있기 때문에 SRP를 위반한다.
Actor1, Actor2, Actor3이 각각 calculatePay(), reportHours(), save() 메서드를 사용한다고 했을때, 한 Actor가 변경한 요구사항 다른 곳에도 영향을 줄 가능성이 있는 구조이다.
SRP는 서로 다른 Actor가 의존하는 코드를 서로 분리하라고 말한다.

해결책

문제의 해결책은 다양하다.
1. 데이터와 메서드를 분리하는 방식

아무런 메서드가 없는 간단한 데이터 구조인 EmployeeData 클래스를 만들어, 3개의 클래스가 공유하도록 한다.
3개의 클래스는 서로의 존재를 모른다.

2. Facade pattern

EmployeeFacade 클래스는 3개의 클래스의 객체를 생성하고, 요청된 메서드를 가지는 객체로 위임하는 일을 책임진다.

3. Employee 클래스를 덜 중요한 나머지 메서드들에 대한 Facade로 사용

가장 중요한 메서드는 기존의 Employee 클래스에 유지하고 Employee 클래스를 덜 중요한 나머지 메서드들에 대한 Facade로 사용한다.

결론

SRP는 메서드와 클래스 수준의 원칙이다.




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

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

요구사항을 살짝 확장하는데 소프트웨어를 엄청나게 수정해야 한다면 엄청난 실패인 것이다.

화살표가 A 클래스에서 B 클래스로 향하면, A 클래스에서 B 클래스를 호출하지만 반대로는 하지 않음을 말한다.
모든 컴포넌트 관계는 단방향으로 이루어진다. 화살표는 변경으로부터 보호려는 컴포넌트를 향하도록 그려진다.

Presenter에서 발생한 변경으로부터 Controller를 보호
View에서 발생한 변경으로부터 Presenter를 보호
Interactor가 가장 높은 수준의 개념이고 View가 가장 낮은 수준의 개념이다.

아키텍트는 기능이 어떻게, 왜, 언제 발생하는지에 따라서 기능을 분리하고, 분리한 기능을 컴포넌트의 계층구조로 조직화한다.
컴포넌트 계층구조를 이와 같이 조직화하면 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있다.

결론

OCP의 목표는 시스템을 확장하기 쉬운 동시에 변경으로 인해 시스템이 너무 많은 영향을 받지 않도록 하는 데 있다.




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

구성요소는 서로 치환 가능해야 한다는 계약을 지켜야 한다.

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

Billing app이 License의 calcFee()를 호출하고 License에는 PersonalLicense와 BusinessLicense라는 2가지 하위타입이 존재한다. 이 2가지 하위 타입은 서로다른 알고리즘을 이용해서 License 비용을 계산한다.
이 설계는 LSP를 준수하고 있다.
Billing app의 행위가 하위타입 중 무엇을 사용하는지 의존하지않고 있기 때문이다. 그리고 하위 타입은 모두 License타입을 치환할 수 있다.

정사각형/직사각형 문제

LSP를 위반하는 전형적인 문제이다.
Rectangle의 높이, 너비는 독립적으로 변경할 수 있지만 Square는 높이와 너비가 함께 변경되기 때문에 Square는 Rectangle의 하위타입으로 적합하지 않다.

결론

LSP는 아키텍처 수준까지 확장할 수 있고, 반드시 확장해야한다. 치환 가능성을 조금이라도 위배하면 시스템 아키텍처가 오염된다.




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

사용하지 않은 것에 의존하지 않아야 한다.

User1의 소스 코드는 U1Ops와 op1에는 의존하지만 OPS에는 의존하지 않게 된다.

ISP와 아키텍처

systems S에 framework F를 도입하려고 하는데, framework F는 database D를 반드시 사용하도록 만들어졌고, F에서는 불필요한 기능, S와는 전혀 관계없는 기능이 D에 포함된다고 가정했을 때, D 내부가 변경되면 F를 재배포해야 할 수도 있고, S까지 재배포해야 할 수 있다.

결론

불필요한 짐을 실은 무언가에 의존하면 예상치도 못한 문제에 빠진다.




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

고수준 정책을 구현하는 코드가 저수준 세부사항을 구현하는 코드에 의존하면 안된다.

DIP에서 말하는 '유연성이 극대화된 시스템' 은 소스 코드 의존성이 추상(abstract) 에 의존하며 구체(concretion)에는 의존하지 않는 시스템이다.
여기서 의존하지 않도록 피하고자 하는 것은 변동성이 큰 구체적인 요소이다.

안정된 추상화

안전된 소프트웨어 아키텍처란 변동성이 큰 구현체에 의존하는 일은 지양하고, 안정된 추상 인터페이스를 선호하는 아키텍처이다.

  • 변동성이 큰 구체 클래스를 참조하지 말고 추상 인터페이스를 참조하라.
  • 변동성이 큰 구체 클래스로부터 파생하지 말라. 상속을 신중하게 해라.
  • 구체 함수를 오버라이드 하지 말라.
    구체 함수는 소스 코드 의존성을 필요로 한다. 이를 오버라이드하면 이러한 의존성을 제거할 수 없게 된다.
    의존성을 제거하려면 추상함수로 선언하고 구현체들에서 각자의 용도에 맞게 구현한다.

팩토리


의존성을 관리하기 위해 abstract factory pattern을 사용한다.
주황색 곡선은 아키텍처 경계를 의미한다. 구체적인 것들과 추상적인 것들을 분리한다. 소스 코드 의존성은 추상적인 쪽으로 향한다.
추상 컴포넌트는 애플리케이션의 모든 고수준 업무 규칙을 포함하고, 구체 컴포넌트는 업무 규칙을 다루기 위해 필요한 모든 세부사항을 포함한다.
소스 코드 의존성은 제어흐름과 반대방향으로 역전된다. (Dependency Inversion)

구체 컴포넌트

그림에서 ServiceFactoryImpl이 ConcreteImpl에 의존하고 있고, 이는 DIP에 위배된다. DIP 위배를 모두 없앨 수는 없다.
그림 예시의 경우 main함수는 ServiceFactoryImpl의 인스턴스를 생성한 후, 이 인스턴스를 ServiceFactory 타입으로 전역 번수에 저장할 것이다. 그런 다음 애플리케이션은 이 전역변수를 이용해서 ServiceFactoryImpl의 인스턴스에 접근할 것이다.





Reference

Clean Architecture - Robert C. Martin

0개의 댓글