서적/Object

9장 유연한 설계

Mo_bi!e 2026. 3. 4. 22:43

1. 개방 폐쇄 원칙

  • 개방 폐쇄 원칙의 핵심은 '추상화에 의존'이다 (런타임 의존성으로 대체)
    • 문맥에 따라 변하는 부분은 생략된다
    • 생략된 부분에서 확장의 여지가 생긴다.
  • 변경에 의한 파급효과 최소화 위해서는 변하는것과 변하지 않는것이 무엇인지 이해하고 추상화의 목적으로 삼아야한다

2. 생성 사용 분리

  • Movie 클래스 내부에서 AmointDiscoutPolicy 같은 구체 클래스의 인스턴스를 생성해서는 안된다
    • 개방 폐쇄 원칙 위반
    • 이것은 동일 클래스 내 객체 생성과 사용이라는 '이질적인' 목적을 가진 코드가 공존하는것이 문제다
  • 결국 '생성과 사용을 분리'해야한다
    • 객체 생성은 클라이언트로 옮긴다.
    • 클라이언트의 컨텍스트에 대한 지식으로 옮기므로, 특정 클라이언트에 결합되지 않고 독립적이다

Factory 추가하기

  • 하지만 client 입장에서는 생성과 사용 책임을 모두 가지고 있다.
public Money getAvatarFee() {
    Movie avatar = new Movie("아바타",
            Duration.ofMinutes(120),
            Money.wons(10000),
            new AmountDiscountPolicy(
                Money.wons(800),
                new SequenceCondition(1),
                new SequenceCondition(10)));
    return avatar.getFee();
}
  • 생성하는 책임을 모두 factory 로 옮길 수있다.
    • 이렇게 되면 clien 는 오직 사용과 관련된 책임만 지고, 생성관련 지식은 가질 필요가 없다.
    public Money getAvatarFeeByFactory() {
        return factory.createAvatarMovie().getFee();
    }

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

  • 표현적 분해와 행위적 분해
    • 전자는 도메인 모델에 있는 개념과 관계를 따르며, 도메인과 sw 사이 표현적 차이 최소화가 목표 (OOP 의 기본적 접근법)
    • 후자는 전자로 부족한경우 도메인 개념을 초월하는 기계적인 개념
  • 행위적 분해
    • Pure Fabrication (순수한 가공물) 이라 부른다.
    • OOP는 실세계 모방이라는 말은 옳지않다.
    • 마치 현대적인 도시가 자연물보다는 건물이나 도로와 같은 인공물로 가득 차 있는 것과 유사하다
    • Pure FABRICATION 은 INFORMATION EXPERT 패턴에 따라 책임을 할당한 결과가 바람직하지 않은 대안으로 사용된다

3. 의존성 주입

  • 생성과 사용을 분리하면 Movie에는 오로지 인스턴스를 사용하는 책임만 남는다.
    • 사용하는 객체가 아니라, 외부 독립적인 객체가 인스턴스 생성 후 전달해서 의존성 해결하는 방법을 의존성 주입(DI)이라 한다.
    • DI 라고 하는 이유는 외부에서 의존성 대상을 해결하고, 사용하는 쪽에 주입하기 때문이다.
  • 주입 방법 3가지 : 생성자 주입 / setter 주입 / 메서드 주입

숨겨진 의존성은 나쁘다. (SERVICE LOCATOR)

  • DI 외 의존성 해결 방법으로 SERVICE LOCATOR 패턴이 있다.
  • SERVICE LOCATOR 는 의존성을 해결할 객체들을 보관하는 저장소이다.
    • 클라이언트 코드가 서비스가 누구인지, 어디에 있는지를 몰라도 되게 해준다
  1. 하지만 가장 큰 문제점은 의존성을 감춘다. Movie의 퍼블릭 인터페이스 어디에도 의존성에 대한 정보가 없다.
    • 따라서 SERVICE LOCATOR 를 호출하지 않고 Movie 생성 시 NPE 발생가능
    • 컴파일시점에는 모르고 런타임 시점에만 알게 되므로 디버깅이 어려움
  2. 단위테스트 작성도 어렵다
    • 모든 단위테스트가 SERVICE LOCATOR 의 상태를 공유하게 된다. -> 단위테스트는 서로 고립되어야 하는 원칙 위반
  3. 코드의 내부구현을 이해할 것을 강요한다
    • 의존성을 구현 내부로 감추도록 강요하는 SERVICE LOCATOR는 캡슐화를 위반 할 수밖에 없다.
  • 숨겨진 의존성은 의존성의 대상을 설정하는 시점과 의존성이 해결되는 시점을 유리시킨다.
    • 코드 이해와 디버깅이 어려운데 인스턴스 설정하는 부분을 찾으면되지만, 개발환경에 따라 코드검색의 난이도는 천차만별...
  • 권장경우 : DI 프레임웍 사용을 못학거나, 깊은 호출계층에 걸쳐 동일한 객체 전달하는 고통이 견딜 수없는경우에 권장
    • (후자 : 지난주 내 고민같은)

4. 의존성 역전 원칙

추상화와 의존성 역전

  • 의존성은 변경의 전파와 관련되므로 설계는 변경의 영향을 최소화 하도록 의존성을 관리해야한다.
    • 의존성은 Movie 에서 AmountDiscountPolicy 로 흘러서는 안되고 그 반대여야한다.
    • 즉 상위수준의 클래스는 어떤 경우에도 하위수준에 의존해서는 안된다 (DIP 원칙)
  • 가장 중요한 조언 : 추상화에 의존하라
    • 구체 클래스는 의존성의 시작이어야한다. / 의존성의 목적지가 돼서는 안 된다.
  • 왜 역전? : 전통적인 절차적인 프로그래밍과는 반대 방향으로 나타나므로

의존성 역전 원칙과 패키지

  • 역전은 의존성의 방향 뿐만 아니라 인터페이스 소유권에도 적용된다.
  • DiscoutPolicy 내 구현체들이 같은 패키지에 있으면 전체가 같이 재배포 되어야한다.

  • 재사용될 필요가 없는 클래스들을 별도의 독립적인 패키지에 모아야한다 (Seperated interface 패턴)

5. 유연성에 대한 조언

1. 유연한 설계는 유연한 설계가 필요할 때만 옳다

  • 유연하고 재사용 가능한 설계가 항상 좋은것은 아니다.
    • 설계의 미덕은 단순함과 명확인데 버리게 될 수도 있다.
  • 유연한 설계라는 말의 이면에는 복잡한 설계라는 의미가 숨어있다. (양면성)
  • 유연한지 여부 등의 판단은 공학이기보다는 심리학에 가깝다.
    • 변경은 예상이 아니라 현실이어야한다
    • 막연한 불안감은 불필요하게 복잡한 설계를 낳는다.
  • 유연한 설계를 단순하고 명확하게 만드는 방법은 사람들간의 긴밀한 커뮤니케이션 이다
    • 복잡성의 이유와 근거를 제시하지 않으면 어느 누구도 만족스러운 해법으로 받아들이지 않는다
    • 유연성은 읽는 사람이 복잡성을 수용할 때 가치가 있다.

2. 협력과 책임이 중요하다

  • 설계를 유연하게 만들기 위해서는 협력에 참여하는 객체가 다른 객체에게 어떤 메시지를 전송하는지가 중요하다
  • 객체의 역할, 책임 자리잡기전 객체 생성에 집중하면 안된다. 생성은 마지막 단계이다.
    • 그렇지 않으면 싱글턴 패턴을 이른시기 도입하고 여기에 종속되기도 한다.