Today I Learned

03 설계 원칙 (2) 본문

클린 아키텍처

03 설계 원칙 (2)

하이라이터 2021. 7. 16. 03:15
728x90

9장. LSP : 리스코프 치환 원칙

리스코프는 하위타입을 다음과 같이 정의했다.

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

 

정사각형/직사각형 문제

LSP를 위반하는 전형적인 문제로 유명한 정사각형/직사각형 문제가 있다.

Rectangle은 높이와 너비가 서로 독립적으로 변경될 수 있는 반면, Square는 높이와 너비가 반드시 함께 변경되야한다.

따라서 Square는 Rectangle의 하위타입으로는 적합하지 않다.

LSP 위반을 막기 위해서는 (if문 등을 이용해서)Rectangle이 실제로는 Square인지 검사하는 매커니즘을 User에 추가해야한다. 하지만 이렇게 하면 User의 행위가 사용하는 타입에 의존하게 되므로, 결국 타입을 치환할 수 없다. 

 

LSP와 아키텍처

LSP는 상속을 사용하도록 가이드하는 방법일 뿐 아니라, 인터페이스와 구현체에도 적용되는 광범위한 소프트웨어 설계 원칙이다. 

LSP는 아키텍처 수준까지 확장할 수 있고, 반드시 확장해야만 한다. 치환 가능성을 조금이라도 위배하면 시스템 아키텍처가 오염되어 상당량의 별도 매커니즘을 추가해야 할 수 있기 때문이다.


10장. ISP : 인터페이스 분리 원칙

인터페이스를 분리하지 않으면 다음과 같은 문제가 발생한다.

User1은 op1, User2는 op2, User3 op3 만을 사용한다고 가정하자.

OPS가 정적 타입 언어로 작성된 클래스라면 op2의 소스코드가 변경되었을 때, User1도 다시 컴파일한 후 재배포해야 한다.

 

ISP와 언어

정적 타입 언어는 사용자가 import, use 또는 include와 같은 타입 선언문을 사용하도록 강제한다.

이로인해 소스코드 의존성이 발생하고, 이로 인해 재컴파일 또는 재배포가 강제되는 상황이 초래된다.

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


11장. DIP : 의존성 역전 원칙

안정화된 추상화

추상 인터페이스에 변경이 생기면 이를 구체화한 구현체들도 따라서 수정해야한다. 반대로 구현체에 변경이 생기더라도 그 구현체가 구현하는 인터페이스는 변경될 필요가 없다. 따라서 인터페이스는 구현체보다 변동성이 낮다.

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

 

 DIP를 위한 구체적인 코딩 실천법

  • 변동성이 큰 구체 클래스를 참조하지 말라
  • 변동성이 큰 구체 클래스로부터 파생하지 말라
  • 구체 함수를 오버라이드 하지 말라
  • 구체적이며 변동성이 크다면 절대로 그 이름을 언급하지 말라

 

팩토리

객체를 생성할때 해당 객체를 구체적으로 정의한 코드에 대해 소스 코드 의존성이 발생하게 된다.

이러한 바람직하지 못한 의존성을 처리하기 위해 추상 팩토리가 사용된다.

Application은 Service 인터페이스를 통해 ConcreteImpl을 사용하지만, Apllication에서는 어떤식으로든 ConcreteImpl의 인스턴스를 생성해야 한다.

ConcreteImpl에 대해 소스 코드 의존성을 만들지 않으면서 이 목적을 이루기 위해 Apllication은 ServiceFactory 인터페이스의 makeSvc 메서드를 호출한다. 이 메서드는 ServiceFactory로부터 파생된 ServiceFactoryImpl에서 구현된다.

그리고 ServiceFactoryImpl 구현체가 ConcreteImpl의 인스턴스를 생성한 후 Service 타입으로 반환한다.

곡선을 기준으로 추상 컴포넌트와 구체 컴포넌트가 나뉜다. 추상 컴포넌트는 애플리케이션의 모든 고수준 업무 규칙을 포함하며, 구체 컴포넌트는 업무 규칙을 다루기 위해 필요한 모든 세부사항을 포함한다.

제어흐름과 정반대로, 소스코드 의존성은 구체적인 쪽 → 추상적인 쪽으로 향한다. 이러한 이유로 이 원칙을 의존성 역전이라 부른다.

 

구체 컴포넌트

위의 그림에서 ServiceFactoryImpl 구체 클래스가 ConcreteImpl 구체 클래스에 의존하는 구체적인 의존성이 있다. 따라서 DI에 위배된다. 하지만 DIP를 위배하는 클래스들은 적은 수의 구체 컴포넌트 내부로 모을 수 있고, 이를 통해 시스템의 나머지 부분과 분리할 수 있다.

이러한 구체 컴포넌트를 흔히 (Main)이라 부른다. main 함수는 ServiceFactoryImpl의 인스턴스를 생성한 후, 이 인스턴스를 ServiceFactory 타입으로 전역 변수에 저장할 것이다. 그 다음 Application은 이 전역 변수를 이용해서 Service Factory Impl의 인스턴스에 접근할 것이다.

728x90

'클린 아키텍처' 카테고리의 다른 글

04 컴포넌트 (2)  (0) 2021.07.30
04 컴포넌트 (1)  (0) 2021.07.23
03 설계 원칙 (1)  (0) 2021.07.09
02 벽돌부터 시작하기: 프로그래밍 패러다임  (0) 2020.02.02
01 소개  (0) 2020.02.02
Comments