[CA] SOLID

rbw·2023년 6월 23일
0

TIL

목록 보기
82/99

클린아키텍처 공부용 게시글 ~

SOLID

SRP

하나의 책임을 가지는게 아닌, 모듈의 변경이유는 하나여야 한다가 마즘 최종적으로 내린 정의는 '하나의 모듈은 하나의, 오직 하나의 액터에 대해서만 책임져야한다.' 이다.

해결책으로 Facade 패턴이 있고, 다른 방법으로는 클래스를 다 분리하는 방법도 존재.

이 원칙은 메서드와 클래스 수준의 원칙임

OCP

A 컴포넌트로 부터 발생한 변경으로부터 B 컴포넌트를 보호하려면 반드시 A 컴포넌트가 B 컴포넌트에 의존해야 한다.

예제를 보면, View에서 발생한 변경으로부터 Presenter를 보호하려고 View는 Presenter를 의존하고 있다. View -> Presenter로 화살표가 진행되는 모습

열린 화살표는 사용(using)관계의 의미, 닫힌 화살표는 구현 or 상속 관계라는 의미이다. 화살표의 방향이 가리키고 있다면, A -> B 의 예시라면 A는 B를 호출하지만, B는 A에 대해 전혀 모르는 상태

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

정보은닉

예제에서 한 인터페이스의 목적은 의존성 역전이 아닌 정보 은닉의 목적으로 생성했다고 설명했다.

이것의 의미는, 추이 종속성을 가지게 되는 부분을 해결하기 위해서라고 한다. 추이 종속성이란, A 클래스가 B에 의존하고, B 클래스가 C 클래스에 의존한다면 A 클래스는 C 클래스에 의존하게 된다. 하지만 '자신이 직접 사용하지 않는 요소에는 절대로 의존해서는 안 된다' 라는 소프트웨어 원칙을 위반하게 된다.

따라서 이러한 부분을 해결하기 위해, 너무 많은 정보를 노출하지 않게 하는 인터페이스가 존재한다.

LSP

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

상속을 사용하도록 가이드 하는 방법 정도로 간주되었찌만 시간이 지나면서 LSP 는 인터페이스와 구현체에도 적용되는 더 광범위한 소프트웨어 설계 원칙으로 변모해왔다.

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

예제로 택시기사 파견 시스템을 설명하였다. 특정 회사의 URI에서는 다른 포맷으로 불러와야 한다면 URI를 키로 사용하는 설정용 DB(Configuration Database)를 이용하는 파견 명령 생성 모듈을 만들어야 할 수도 있다.

ISP

이를 사용하는 근본적인 동기를 살펴보면, 일반적으로 필요 이상으로 많은 걸 포함하는 모듈에 의존하는 것은 해로운 일 이라는 점에서 나온 원칙이다.

불필요한 재컴파일과 재배포를 강제하기 때문이다.

DIP

여기에서 말하는 '유연성이 극대화된 시스템'이란 소스코드 의존성이 추상(abstraction)에 의존하며 구체(concretion)에는 의존하지 않는 시스템을 말한다.

소스 코드 의존 관계에서 구체 모듈은 참조해서는 안 된다. 하지만 이를 지키기는 상당히 어려운데 예를 들어 Java에서 String은 구체 클래스이며 이를 추상 클래스로 만드는 것은 현실성이 없다. 그리고 이 클래스에 대한 의존성은 벗어날 수 없고, 벗어나서도 안 된다.

하지만 String 클래스는 매우 안정적이며, 변경 될 일은 거의 없다. 따라서 이러한 부분은 무시된다. 변경되지 않는다면 의존할 수 있다는 사실을 이미 알고 있기 때문이다.

뛰어난 소프트웨어 설계자와 아키텍트라면 인터페이스의 변동성을 낮추기 위해 애쓴다. 인터페이스를 변경하지 않고도 구현체에 기능을 추가할 수 있는 방법을 찾기 위해 노력한다. 이는 소프트웨어 설계의 기본이다.

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

구체적인 코딩실천법으로는 다음과 같다

  • 변동성이 큰 구체 클래스를 참조하지마라: 대신 추상 인터페이스를 참조하라, 이 규칙은 객체 생성 방식을 강하게 제약하며, 일반적으로 추상팩토리(abstract factory)를 사용하도록 강제한다
  • 변동성이 큰 구체 클래스로부터 파생하지 말라: 상속은 신중에 신중을 거듭해 사용해야 한다
  • 구체 함수를 오버라이드 하지 말라: 대체로 구체 함수는 소스 코드 의존성을 필요로 한다. 따라서 이를 오버라이드 하면 이러한 의존성을 제거할 수 없게 되며, 실제로는 그 의존성을 상속하게 된다. 이러한 의존성을 제거하려면 추상함수로 선언하고 구현체들에서 각자의 용도에 맞게 구현해야 한다

제어흐름은 소스 코드 의존성과는 정반대 방향으로 곡선을 가로지른다는 점이다. 이는 소스 코드 의존성은 제어흐름과는 반대방향으로 역전된다 라는 뜻이다. 이러한 이유로 이 원칙을 의존성 역전이라고 부른다.

보통 main 함수를 포함하는 메인컴포넌트는 이 원칙을 위배한다. DIP 위배를 모두 없앨 수는 없다. 하지만 이를 위배하는 클래스들은 적은 수의 구체 컴포넌트 내부로 모을 수 있고 이를 통해 시스템의 나머지 부분과는 분리할 수 있다.

main에서 ServiceFactoryImpl의 인스턴스를 생성 후 이 인스턴스를 ServiceFactory 타입으로 전역변수에 저장할 것, 다음 Application은 이 전역변수를 이용해서 ServiceFactoryImpl의 인스턴스에 접근할 것이다.

profile
hi there 👋

0개의 댓글