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

ppparkta·2025년 6월 7일
0

오브젝트

목록 보기
9/14

유연한 설계

이번 장의 목적은 다양한 의존성 관리 기법을 원칙의 관점에서 정리한다.

개방-폐쇄 원칙

소프트웨어 개체는 확장에 열려있고 수정에 닫혀있어야 한다. (OCP)

→ 여기서 확장은 동작에 연관되었고, 수정은 코드에 연관되었다.

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

OCP는 런타임 의존성과 컴파일 타임 의존성에 관련된 이야기다.

  • 런타임 의존성
    • 실행 시에 협력에 참여하는 객체 사이의 관계
  • 컴파일 타임 의존성
    • 코드에서 드러나는 클래스 사이의 관계

OCP의 기본 원칙은 기존 코드를 건들지 않고 코드(클래스)를 추가하여 기능을 확장한다.

  • OCP를 수용하는 코드는 컴파일 타임 의존성을 수정하지 않고도 런타임 의존성을 쉽게 변경할 수 있다
  • 의존성 관점에서 OCP를 따르는 설계는 컴파일 타임 의존성을 유지하면서도 런타임 의존성의 가능성을 확장하고 수정할 수 있는 구조

핵심은 추상화

OCP의 핵심은 추상화에 의존하는 것이다.

  • 추상화

    • 핵심만 남기고 불필요한 부분을 생략하여 복잡성을 극복하는 것이다.
    • 생략되지 않는 부분은 추상화의 결과물이다 (수정할 필요가 없다)
  • ex) 에서 변하는 부분 → 할인 금액을 계산하는 방법 (생략)

  • ex) 에서 변하지 않는 부분 → 할인 여부를 판단하는 방법 (고정)

추상화는 의존의 방향성을 통해 설계의 확장을 가능하게 한다.

즉, 모든 요소는 추상화에 의존해야 한다.

ex) Movie는 구현체가 아닌 DiscountPolicy에 의존한다.
안정된 추상화에 의지함으로서 코드에 영향받지 않는다.

2줄 요약

  • 추상화는 확장을 가능하게 한다
  • 확장에 대한 의존은 폐쇄를 가능하게 한다

🔥 주의할 점은 추상화 했다고 모든 수정에 대해 설계가 폐쇄되지 않는다는 점이다.

추상화의 목적은 변하는 것과 변하지 않는 것이 무엇인지 이해하고 변하지 않는 부분을 고정하고 변하는 부분을 생략하는 것이다.

올바른 추상화를 주의깊게 선택하라.

생성 사용 분리

Movie 내부에서 구체 클래스 인스턴스를 직접 생성해서는 안 된다. (OCP 위반)

discountPolicy = new AmountDiscountPolicy(...);   // 금지!

→ 객체는 어느 곳이든 생성될 수밖에 없다. <문제>는 부적절한 곳에서 객체를 생성하는 것이다.

동일 클래스에 객체 생성과 객체 사용이라는 두가지 이질적인 목적을 가진 코드가 공존하면 문제가 발생할 수 있다.

따라서 생성과 사용을 분리해서 사용해야 한다.

FACTORY 추가하기

  • 생성 책임
    • Movie는 어떤 컨텍스트에 묶여서는 안 된다. (추상화를 가져다 쓰는 부분이기 때문에)
    • 이에 비해 Client는 어떤 컨텍스트에 묶여도 상관없다.

Client를 써도 되지만 만약에 Client도 컨텍스트에 묶이기 싫다면? 객체 생성에 대한 정보를 외부에 노출하기도 싫다면? 그 때는 어떻게 처리할까?

→ 생성 책임만 전담하는 별도의 객체를 추가하면 된다. 생성 책임에 특화된 객체가 바로 FACTORY이다.

결국 추상화하면서 생성-사용 분리의 원칙을 지키기 위해서 FACTORY를 사용한다.

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

  • 책임 할당 원칙 중 → 정보 전문가에게 할당하기.
    • 도메인 모델 안의 개념 중 적절한 후보가 존재하는지 찾아봐야 함.

시스템이 객체를 분해하는 두 가지 방법

  1. 표현적 분해 (도메인에 책임 분리)
  2. 행위적 분해 (설계자 편의를 위해, 가공의 객체에 책임 분리)
    • 순수한 가공물

도메인은 설계를 위한 출발점일 뿐, 순수 가공물을 추가해야 하는 상황이 올수밖에 없다.

→ 애플리케이션은 도메인 개념에 대한 표현, 순수 창조 객체(순수 가공물)이 모여 자신의 역할과 책임을 다해야 한다.


의존성 주입

생성-사용 원칙을 적용하게 되면 Movie에 오로지 인스턴스를 사용하는 책임만 남게 된다.

외부 객체가 Movie에 인스턴스를 전달해야 함을 의미한다.

  • 의존성 주입
    • 외부의 독립적인 객체가 인스턴스 생성 후 이를 전달해서 의존성을 해결하는 방법이다
  • 종류
    • 생성자 주입
    • setter 주입 (프로퍼티 주입) → null 가능성
    • 메서드 주입 (매개변수를 이용한 주입) → 애매하다고 보는 관점도 있음

숨겨진 의존성은 나쁘다

  • SERVICE LOCATOR 패턴
    • 객체가 직접 service locator에게 의존성 해결해줄 것을 요청한다.
    • 그러나 의존성을 감추게 된다.

→ <문제> 숨겨진 의존성은 의존성 문제를 컴파일 타임이 아닌 런타임에 발견되게 한다.

이러한 숨겨진 의존성 문제는 캡슐화 위반에 해당한다. 캡슐화는 코드를 읽고 이해하는 행위에 관련있기 때문이다. 어떤 코드의 퍼블릭 인터페이스만으로 이해할 수 있어야 캡슐화 관점에서 훌륭한 코드라고 할 수 있다.

숨겨진 의존성보다 명시적인 의존성을 우선시 해야 한다. (반드시 의존성 주입을 쓰라는게 x, 명시적으로 사용하라는 말임)

물론 명시적인 의존성을 사용하지 못하는 경우도 존재한다. 이럴 때는 신중에 신중을 가해 service locator도 고려해보자.


의존성 역전 원칙

추상화와 의존성 역전

public class Movie {
		private AmountDiscountPolicy discountPolicy;
}

위의 코드에는 구체 클래스에 대한 의존성으로 인해 결합도가 높아지는 문제가 있다. 재사용성과 유연성이 저해된다.

  • 상위 수준의 Movie가 하위 수준의 AmountDiscountPolicy에 의존하고 있다.

→ 협력의 본질은 상위 수준의 클래스가 갖고있다. 즉, 어떤 협력에서 중요 정책이나 의사결정, 비즈니스 본질은 상위 수준이 담고있다.

의존성은 변경의 전파와 관련된다. 따라서 설계는 변경의 영향을 최소화하도록 의존성을 관리해야 한다.

  • 상위 수준의 변경으로 인해 하위 수준 모듈이 바뀌는 것은 납득 가능한 범위
  • 하위 수준 모듈 변경으로 상위 수준이 바뀌는 것은 곤란함

→ 해결책은 추상화다.
상위 수준과 하위 수준 모두 추상화에 의존하면 하위 수준 변경으로 인해 상위 수준이 변경되는 것을 방지할 수 있다.

2줄 요약

  • 상위 모듈은 하위 모듈에 의존해선 안 된다. 둘 다 추상화에 의존해야 한다.
  • 추상화는 구체적인 사항에 의존해선 안 된다. 구체사항이 추상화에 의존해야 한다.

⇒ 이를 의존성 역전 원칙. DIP라고 부른다.

의존성 역전 원칙과 패키지

역전은 의존성 방향뿐만 아니라 인터페이스 소유권에도 적용된다.

즉, 추상화가 제공하는 인터페이스의 소유권도 역전시켜야 한다.

2줄 요약

  • 상위 수준 모듈, 하위 수준 모듈 모두 추상화에 의존해야 한다.
  • 인터페이스는 상위 수준에 위치해야 한다.

유연성에 대한 조언

유연한 설계는 유연성이 필요할 때만 옳다

유연함은 단순성과 명확성의 희생 위에서 자라난다.
유연한 설계를 단순하고 명확하게 만드려면 커뮤니케이션이 필요하다.

아직 일어나지 않은 변경은 변경이 아니다.

협력과 책임이 중요하다

중요하다.

profile
겉촉속촉

0개의 댓글