본문 바로가기
개발/Clean Code

[오브젝트] 9장 유연한 설계

by 달사쿠 2021. 6. 3.
반응형

01. 개방-폐쇄 원칙

소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.

여기서 키워드는 '확장' '수정'이다. 이 둘은 순서대로 애플리케이션의 '동작''코드'의 관점을 반영

 

  • 확장에 대해 열려 있다: 애플리케이션의 요구사항이 변경될 때 이 변경에 맞게 새로운 '동작'을 추가해서 애플리케이션의 기능을 확장할 수 있다.
  • 수정에 대해 닫혀 있다. 기존의 '코드'를 수정하지 않고도 애플리케이션의 동작을 추가하거나 변경할 수 있다.

 

컴파일 의존성을 고정시키고 런타임 의존성을 변경하라

의존성 관점에서 개방-폐쇄 원칙을 따르는 설계란,
컴파일타임 의존성은 유지하면서 런타임 의존성의 가능성을 확장하고 수정할 수 있는 구조를 의미

 

컴파일 의존성: 코드에서 드러나는 클래스들 사이의 관계

런타임 의존성: 실행시에 협력에 참여하는 객체들 사이의 관계

출처: 오브젝트 p284

개방-폐쇄 원칙 예제: 8장의 OverlappedDiscountPolicy(중복 할인 정책) 추가, NoneDiscountPolicy 추가

 

추상화가 핵심이다

개방-폐쇄 원칙의 핵심은 추상화에 의존하는 것

 

추상화: 핵심적인 부분만 남겨 복잡성을 줄이는 기법

  • 생략된 부분을 문맥에 적합한 내용으로 채워넣어 문맥에 적합하게 기능을 구체화하고 확장
  • 예시(p285): 상속을 통해 생략된 부분을 구체화함으로써 할인 정책 확장
  • 변하는 것과 변하지 않는 것이 무엇인지 이해해 설계해야 추상화가 수정에 대해 닫혀있을 수 있다.

 


02. 생성 사용 분리

유연하고 재사용 가능한 설계를 원한다면, 객체에 대한 생성 책임과 사용 책임을 분리해야한다.

 

  • 사용으로 부터 생성을 분리하는 보편적인 방법: 객체 생성 책임을 클라이언트로 옮기는 것
  • 어떤 것을 사용할 지는 그 시점의 클라이언트만 알기 때문

 

FACTORY 추가하기

  • FACTORY: 객체 생성에 특화된 객체 (생성과 사용을 분리하기 위함이 목적)
  • Factory를 사용해 생성된 Movie 인스턴스를 반환받아 사용
  • Movie와 AmountDiscountPolicy 생성 책임 모두 factory로 이동

출처: 오브젝트(p290)

 

순수한 가공물에게 책임 할당하기

어떤 행동을 추가하려고 하는데 이 행동을 책임질 마땅한 도메인 개념이 존재하지 않는다면 PURE FABRICATION을 추가하고 이 객체에게 책임을 할당하라.

  •  PURE FABRICATION(순수한 가공물): 책임을 할당하기 위해 창조되는 도메인과 무관한 인공적인 객체
  • 모든 책임을 도메인 객체에게 할당하면 낮은 응집도, 높은 결합도, 재사용성 저하와 같은 심각한 문제점에 봉착하게 될 가능성이 높아진다. 
  • Factory는 재사용성을 높이기 위해 사용하는 도메인 개념과 아무런 상관없는 가공의 객체
  • 도메인이 만족스럽지 않다면 인공적인 객체를 창조해 도메인 모델을 보충

 


03. 의존성 주입

의존성 주입이란?

  • 사용하는 객체가 아닌 외부의 독립적인 객체가 인스턴스를 생성한 후 이를 전달해 의존성을 해결하는 방법
  • 외부에서 의존성의 대상을 해결한 후 이를 사용하는 객체로 주입

의존성을 해결하는 3가지 방법

  • 생성자 주입(constructor injection) : 객체를 생성하는 시점에 생성자를 통한 의존성 해결
    객체의 생명주기 전체에 걸쳐 관계 유지
  • setter 주입(setter injection) : 객체 생성 후 setter 메서드를 통한 의존성 해결
    장점: 언제라도 의존 대상을 런타임에 변경할 수 있음
     단점: 어떤 의존성이 필수적인지 명시적으로 표현할 수 없음 (예. 객체 생성 뒤 호출해야 정상적으로 생성)

  • 메서드 (호출) 주입(method injection) : 메서드 실행 시 인자를 이용한 의존성 해결
     메서드가 의존성을 필요하는 유일한 경우일 때 사용

 

숨겨진 의존성은 나쁘다

SERVICE LOCATOR 패턴

  • 의존성을 해결할 객체들을 보관하는 저장소
  • 객체가 직접 SERVICE LOCATOR에게 의존성을 해결해줄 것을 요청
  • 의존성을 해결할 수 있는 가장 간단한 도구이지만 의존성을 감춘다는 단점이 있음
  • 의존성을 숨기는 코드는 단위테스트 작성도 어려움
    (내부적으로 정적 변수를 사용해 객체 관리 -> 단위 테스트는 서로 고립되어야 한다는 원칙 위반)
  • 의존성을 이해하기 위해 코드 내부 구현을 이해해야 한다는 문제도 있음 (캡슐화 위반)

즉, 명시적인 의존성이 숨겨진 의존성 보다 좋다!

 


04. 의존성 역전 원칙

추상화와 의존성 역전

  • 상위 수준의 클래스가 하위 수준의 클래스에 의존하면, 상위 수준의 클래스를 재사용할 때 하위 수준의 클래스도 필요하기 때문에 재사용이 어려워짐
  • 객체 사이의 협력이 존재할 때 그 협력의 본질을 담고 있는 것은 상위 수준의 클래스
  • 해결하기 위해선 "추상화" 사용
  • 상위 수준의 클래스와 하위 수준의 클래스 모두 추상화에 의존해야 한다.
  • 추상화는 구체적인 사항에 의존하면 안된다. 구체적인 사항이 추상화에 의존해야 한다.

출처: 오브젝트 p301

 

의존성 역전 원칙과 패키지

  • 의존성 역전 원칙에 따라 상위 수준의 협력 흐름을 재사용하기 위해서는 추상화가 제공하는 인터페이스의 소유권 역시 역전시켜야 한다.
  • 인터페이스의 소유권을 서버가 아닌 클라이언트에 위치시킨다. (p303 vs p304)

 


05. 유연성에 대한 조언

 

  • 설계의 미덕은 단순함 & 명확함에서 나온다. 즉, 이런 코드가 읽기 쉽고 이해하기 편함
  • but, 유연한 설계는 단순함과 명확함의 미덕을 버리게 될 가능성이 높다.
  • 유연한 설계는 복잡하고 암시적
  • 불필요한 유연성은 불필요한 복잡성으로 낳기 때문에, 코드를 읽는 사람들이 그 복잡성을 수용할 수 있을 때만 가치가 높다.
  • 설계를 유연하게 만들기 위해서는 역할, 책임, 협력에 초점을 맞춰야한다.
  • 의존성을 관리해야하는 이유는 역할/책임/협력의 관점에서 설계가 유연하고 재사용 가능해야하기 때문

 

반응형

댓글