BookReview/오브젝트

08_의존성 관리하기

Fkaa 2023. 2. 9. 17:35

오브젝트 - 코드로 이해하기 8장 의존성 관리하기

오브젝트 - 코드로 이해하는 객체지향 설계의 8번째 챕터 의존성 관리하기 파트다.

08_의존성 관리하기에서는 영화 예매 시스템을 통해 충분히 협력적이면서도 유연한 객체를 만들기 위해 의존성을 관리하는 방법을 살펴보고 있다.


00_들어가기

  • 작고 응집도 높은 객체
    • 책임의 초점이 명확하고 한 가지 일만 잘하는 객체를 의미
    • 단독으로 수행할 수 있는 작업은 거의 없기 때문에 일반적으로 애플리케이션의 기능을 구현하기 위해서는 다른 객체에게 도움을 요청해야 한다.
    • 이런 요청이 객체 사이의 협력을 낳는다.
  • 협력은 객체가 다른 객체의 존재와 다른 객체가 수신할 수 있는 메시지를 알고 있어야 한다.
    • 이런 지식이 객체 사이의 의존성을 낳는다.
  • 협력을 위해서는 의존성이 필요하지만, 과도한 의존성을 애플리케이션을 수정하기 어렵게 한다.
  • 객체시향 설계의 핵심
    • 협력을 위해 필요한 의존성을 유지하면서도 변경을 방해하는 의존성을 제거
    • 객체지향 설계란 의존성을 관리하는 것이고 객체가 변화를 받아들일 수 있게 의존성을 정리하는 기술

01_의존성 이해하기

  • 변경과 의존성
    • 의존성의 의미
      • 실행 시점 : 의존하는 객체가 정상적으로 동작하기 위해서는 실행 시에 의존 대상 객체가 반드시 존재해야 한다.
      • 구현 시점 : 의존 대상 객체가 변경될 경우 의존하는 객체도 함께 변경
      • 의존성은 항상 단방향성의 방향을 가진다.
        • 영화 예매 시스템에서 PeriodCondition 클래스의 isSatisfiedBy 메서드는 Screening 인스턴스에게 getStartTime 메시지를 전송한다.
        • Screening이 변경될 때 PeriodCondition은 영향을 받게 되지만, 역은 성립하지 않음
        • PeriodCondition은 Screening에 의존하고 있다.
          그림 8-1. PeriodCondition과 Screening의 의존관계
    • 설계와 관련된 대부분의 용어는 변경과 관련이 있음
      • 의존성 : 의존되는 요소(Screening)가 변경될 때 의존하는 요소(PeriodCondition)도 함께 변경될 수 있다는 것을 의미
      • 의존성은 변경에 의한 영향의 전파 가능성을 암시
      • 그림 8-2. PeriodCondition이 가지는 의존성
        그림 8-3. PeriodCondition이 가지는 의존성의 종류를 강조
      • 그림 8-2에서 나타나듯 PeriodCondition은 복수의 의존성 대상을 가지고 있으나, 각각 그 특성이 다르다.
        • DiscountCondition의 경우 인터페이스에 정의된 오퍼레이션들을 퍼블릭 인터페이스의 일부로 포함시키기 때문
        • DayOfWeek, LocalTime의 경우 인스턴스 변수로 사용
        • Screening의 경우 메서드의 인자로 사용
      • 그림 8-3과 같이 의존성의 종류를 구분 가능하도록 서로 다른 방식으로 표현하는 것이 유용하다.
    • UML과 의존성
      • UML에서는 두 요소 사이의 관계로 실체화된 관계(realization), 연관 관계(association), 의존관계(dependency), 일반화/특수화 관계(generalizaion/specialization), 합성 관계(composition), 집합 관계(aggregation)등을 정의
      • UML에서는 두 요소 사이에 존재할 수 있는 다양한 관계의 하나로 의존 관계를 정의
      • 이번 장에서의 의존성은 두 요소 사이에 변경에 의해 영향을 주고받는 힘의 역학관계가 존재한다는 사실에 초점을 맞춤
        • UML에 정의된 모든 관계는 의존성이라는 개념을 포함한다.
  • 의존성 전이(Transitive Dependency)
    • A 객체가 B에 의존할 경우 A 객체는 B가 의존하는 대상에 대해서도 자동적으로 의존하게 됨
    • 의존성은 함께 변경될 수 있는 가능성을 의미하기 대문에 모든 경우에 의존성이 전이되는 것은 아님
    • 의존성 실제 전이 여부는 변경의 방향과 캡슐화의 정도에 따라 달라짐
    • 의존성 전이의 종류
      • 직접 의존성(Direct Dependency)
        • 한 요소가 다른 요소에 직접 의존하는 경우
          • PeriodCondition이 Screening에 의존하는 경우
        • 코드에 명시적으로 드러남
      • 간접 의존성(Indirect Dependency)
        • 직접적인 관계는 존재하지 않지만 의존성 전이에 의해 영향이 전파되는 경우
        • 코드에 명시적으로 드러나지 않음
    • 의존성의 대상은 객체일 수도 있고 모듈이나 더 큰 규모의 실행 시스템일 수 있다.
  • 런타임 의존성과 컴파일타임 의존성
    • 런타임 : 애플리케이션이 실행되는 시점
    • 컴파일 타임 : 일반적으로 작성된 코드를 컴파일하는 시점을 가리키지만 문맥에 따라서는 코드 그 자체를 가리키기도 함
    • 런타임 의존성의 주제는 객체 사이의 의존성이며, 컴파일 타임 의존성의 주제는 클래스 사이의 의존성이다.
    • 유연하고 재사용 가능한 코드를 설계하기 위해서는 두 종류의 의존성을 서로 다르게 만들어야 한다.
    • 유연하고 재사용 가능한 설계를 창조하기 위해서는 동일한 소스코드 구조를 가지고 다양한 실행구조를 만들 수 있어야 한다.
  • 컨텍스트 독립성
    • 클래스가 사용될 특정한 문맥에 대해 최소한의 가정만으로 이뤄져 있다면 다른 문맥에서 재사용하기 더 수월해진다.
    • 컨텍스트에 대한 정보가 적으면 적을수록 더 다양한 컨텍스트에서 재사용 될 수 있다.
  • 의존성 해결하기
    • 컴파일 타임 의존성을 실행 컨텍스트에 맞는 적절한 런타임 의존성으로 교체하는 것
    • 의존성 해결을 위한 방법
      • 객체를 생성하는 시점에 생성자를 통해 의존성 해결
      • 객체 생성 후 setter 메서드를 통해 의존성 해결
        • 장점 : 실행 시점에 의존 대상을 변경할 수 있기 때문에 설계를 더 유연하게 한다.
        • 단점 : 객체가 생성된 후에 협력에 필요한 의존 대상을 설정하기 대문에 객체를 생성하고 의존하는 대상을 설정하기 전까지는 객체의 상태가 불완전할 수 있다.
      • 메서드 실행 시 인자를 이용해 의존성 해결
        • 협력대상에 대해 지속적으로 의존관계를 맺을 필요 없이 메서드가 실행되는 동안만 일시적으로 의존관계가 존재해도 무방하거나, 메서드가 실행될 때마다 의존 대상이 매번 달라져야 하는 경우에 유용
        • 매번 동일한 객체를 인자로 전달한다면 생성자 또는 setter 메서드를 이용해 의존성을 지속적으로 유지하도록 변경하는 것이 유용하다.

02_유연한 설계

  • 의존성과 결합도
    • 의존성은 협력을 위해 반드시 필요한 것
    • 의존성에서 문제를 야기하는 것은 바람직하지 못한 의존성
    • 바람직한 의존성
      • 다양한 환경에서 클래스를 재사용할 수 있는 것
      • 컨텍스트에 독립적인 의존성
    • 어떤 두 요소 사이에 존재하는 의존성이 바람직할 때 두 요소가 느슨한 결합도(Loose Coupling) 또는 약한 결합도(Week Coupling)를 가진다.
  • 지식이 결합을 낳는다
    • 결합도의 정도는 한 요소가 자신이 의존하고 있는 다른 요소에 대해 알고 있는 정보의 양으로 결정된다.
      • 한 요소가 다른 요소에 대해 더 많은 정보를 알고 있을수록 두 요소는 강하게 결합된다.
    • 결합도를 느슨하게 만들기 위해서는 협력하는 대상에 대해 필요한 정보 외에는 최대한 감추는 것이 중요하다.
  • 추상화에 의존하라
    • 추상화는 대상에 대해 알아야 하는 지식의 양을 줄일 수 있기 때문에 결합도를 느슨하게 유지할 수 있다.
    • 추상화와 결합도의 관점에서의 의존 대상 구분
      • 구체 클래스 의존성(Concrete Class Dependency)
      • 추상 클래스 의존성(Abstract Class Dependency)
      • 인터페이스 의존성(Interface Dependency)
      • 구체 클래스 의존성보다 추상 클래스 의존성이 결합도가 느슨하고, 추상 클래스 의존성에 비해 인터페이스 의존성이 결합도가 느슨하다.
        • 실행 컨텍스트에 대해 알아야 하는 정보를 줄일수록 결합도가 낮아진다.
        • 의존하는 대상이 더 추상적일수록 결합도는 더 낮아진다.
  • 명시적인 의존성
    • 결합도를 느슨하게 만들기 위해서는 인스턴스 변수의 타입을 추상 클래스나 인터페이스로 선언하는 것만으로는 부족하다.
      • 클래스 안에서 구체 클래스에 대한 모든 의존성을 제거해야 한다.
    • 명시적인 의존성(Explicit Dependency)
      • 의존성이 명시적으로 퍼블릭 인터페이스에 노출되는 경우
    • 숨겨진 의존성(Hidden Dependency)
      • 이존성이 퍼블릭 인터페이스에 표현되지 않는 경우
    • 의존성이 명시적이지 않으면 의존성을 파악하기 위해 내부 구현을 직접 살펴보아야만 한다.
      • 코드를 파악하는 것이 어려움
      • 클래스를 다른 컨텍스트에서 재사용하기 위해 내부 구현을 직접 변경해야 한다.
    • 유연하고 재사용 가능한 설계는 퍼블릭 인터페이스를 통해 의존성이 명시적으로 드러나는 설계이다.
      • 퍼블릭 인터페이스를 통해 컴파일 타임 의존성을 적절한 런타임 의존성으로 교체 가능
  • new는 해롭다
    • new를 잘못 사용할 경우 클래스 사이의 결합도가 극단적으로 높아진다.
      • new 연산자를 사용하기 위해서는 구체 클래스의 이름을 직접 기술해야 한다. 따라서 new를 사용하는 클라이언트는 추상화가 아닌 구체 클래스에 의존할 수밖에 없기 대문에 결합도가 높아진다.
      • new 연산자는 생성하려는 구체 클래스뿐만 아니라 어떤 인자를 이용해 클래스의 생성자를 호출해야 하는지도 알아야 한다. 따라서 new를 사용하면 클라이언트가 알아야 하는 지식의 양이 늘어나기 때문에 결합도가 높아진다.
    • new는 협력할 클래스의 인스턴스를 생성하기 위해 어떤 인자를 필요하고 그 인자들을 어떤 순서로 사용해야 하는지에 대한 정보도 노출시킬 뿐만 아니라 인자로 사용되는 구체 클래스에 대한 의존성울 추가한다.
    • 해결방법
      • 인스턴스를 생성하는 로직과 생성된 인스턴스를 사용하는 로직을 분리하는 것.
    • 사용과 생성의 책임을 분리하고, 의존성을 생성자에게 명시적으로 드러내고, 구체 클래스가 아닌 추상 클래스에 의존하게 함으로써 설계를 유연하게 만들 수 있다.
  • 가끔은 생성해도 무방하다
    • 클래스 안에서 객체의 인스턴스를 직접 생성하는 방식이 유용한 경우도 있다.
      • 주로 협력하는 기본 객체를 설정하고 싶은 경우
        • 기본객체를 생성하는 생성자와 생성자에서 인스턴스를 인자로 받는 생성자를 체이닝 하는 것(복수의 생성자)
        • 메서드를 오버로딩하는 경우에도 사용 가능
    • 설계는 트레이드오프의 산물이다.
  • 표준 클래스에 대한 의존은 해롭지 않다
    • 변경될 확률이 거의 없는 클래스라면 의존성이 문제가 되지 않는다.
      • JDK에 포함된 표준 클래스( ex/ ArrayList)
    • 클래스에서 직접 생성하더라도 가능한 추상적인 타입을 사용하는 것이 확장성 측면에서 유리
      ex_1/ ArrayList<String\> strList = new ArrayList<\>(); X
      ex_2/ List<String\> strList = new ArrayList<\>(); O
  • 컨텍스트 확장하기
    • 영화 예매 애플리케이션의 컨텍스트 확장하기
      • 할인 정책이 없는 경우 확장
        • Movie Class에서 예외케이스 추가를 통한 구현이 아닌 DiscountPolicy의 자식 클래스 추가를 통한 구현
      • 복수개의 할인 정책이 있는 경우 확장
        • Movie Class 에서 예외케이스 추가를 통한 구현이 아닌 DiscountPolicy의 자식 클래스 추가와 그 클래스를 일급컬렉션으로 구현
  • 조합 가능한 행동
    • 어떤 객체와 협력하느냐에 따라 객체의 행동이 달라지는 것은 유연하고 재사용 가능한 설계가 가진 특징
    • 유연하고 재사용 가능한 설계는 응집도 높은 책임들을 가진 작은 객체들을 가진 작은 객체들을 다양한 방식으로 연결함으로써 애플리케이션의 기능을 쉽게 확장할 수 있다.
    • 유연하고 재사용 가능한 설계는 객체가 어떻게(How) 하는지를 장황하게 나열하지 않고도 객체들의 조합을 통해 무엇(What)을 하는지를 표현하는 클래스들로 구성된다.
      • 클래스의 인스턴스를 생성하는 코드를 보는 것만으로 객체가 어떤 일을 하는지를 쉽게 파악할 수 있다.
    • 선언적으로 객체의 행동을 정의할 수 있다.
    • 유연하고 재사용 가능한 설계는 작은 객체들의 행동을 조합함으로써 새로운 행동을 이끌어 낼 수 있는 설계이다.
    • 훌륭한 객체지향 설계란 객체가 어떻게 하는지를 표현하는 것이 아니라 객체들의 조합을 선언적으로 표현함으로써 객체들이 무엇을 하는지를 표현하는 설계다.
    • 유연한 설계를 창조하는 데 있어 핵심은 의존성을 관리하는 것

03_Review

  • 책을 읽어가며 배우는 점과 일을 하면서 알아가는 프로그래밍 패러다임에 일맥상통하는 부분이 있어 책 내용을 읽기 점점 쉬워진다. 물론 이번 챕터가 전 챕터에 비해 비교적 쉬운 내용이어서 그런 것 같기도 하다. 이미 알고 있는 내용이어서 그런 것 일 수 도 있겠지만, 문득 느낀 것이 일을 하면서 내가 설계해 둔 코드와 굉장히 유사하게 설계되어 있다는 것을 알게 되었다. 알게 모르게 조금씩 객체지향적 사고가 붙고 있는 것 같다.
  • 그리고 이번 챕터에서 new는 해롭다 와 관련된 부분에서도 그렇게 책을 읽으면서 항상 느끼는 것은 결국 프로그래밍은 트레이드오프의 산물이다라는 것을 다시 한번 느꼈다. 인생은 실전이야..

 
오브젝트
객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 존재로 바라보는 것이다. 세번째 걸음을 내디딜 수 있는지 여부는 협력에 참여하는 객체 들에게 얼마나 적절한 역할과 책임을 부여할 수 있느냐에 달려 있다. 객체지향의 마지막 걸음은 앞에서 설명한 개념들을 여러분이 사용하는 프로그래밍 언어라는 틀에 흐트러짐 없이 담아낼 수 있는 기술을 익히는 것이다. 《객체지향의 사실과 오해》가 첫번째 걸음과 두번째 걸음인 객체와 협력에 초점을 맞췄다면 《오브젝트: 코드로 이해하는 객체지향 설계》는 세번째와 네번째 걸음인 책임의 할당과 그 구현에 초점을 맞춘다. 이 책을 읽고 나면 객체에 적절한 역할과 책임을 부여하는 방법과 유연하면서도 요구사항에 적절한 협력을 설계하는 방법을 익히게 될 것이다. 나아가 프로그래밍 언어라는 도구를 이용해 객체지향의 개념과 원칙들을 오롯이 표현할 수 있는 방법 역시 익힐 수 있을 것이다. ★ 이 책에서 다루는 내용 ★ ◎ 역할, 책임, 협력에 기반해 객체지향 프로그램을 설계하고 구현하는 방법 ◎ 응집도와 결합도를 이용해 설계를 트레이드오프하는 방법 ◎ 설계를 유연하게 만드는 다양한 의존성 관리 기법 ◎ 타입 계층을 위한 상속과 코드 재사용을 위한 합성의 개념 ◎ 다양한 설계 원칙과 디자인 패턴
저자
조영호
출판
위키북스
출판일
2019.06.17

(참고문헌: 조영호, 오브젝트 - 코드로 이해하는 객체지향 설계, 위키북스, 2022.11.30(6쇄 발행), p.253 ~ p.281)

'BookReview > 오브젝트' 카테고리의 다른 글

07_객체 분해  (0) 2023.02.05
06_메시지와 인터페이스  (2) 2023.01.30
05_책임 할당하기  (0) 2023.01.18
04_설계 품질과 트레이드 오프  (2) 2023.01.09
03_역할, 책임, 협력  (0) 2023.01.08