본문 바로가기
스터디/오브젝트

8장 - 의존성 관리하기

by 검은도자기 2023. 5. 22.

개요

이번 장에서는 협력적이면서도 유연한 객체를 만들기 위해 의존성을 관리하는 방법에 대해 다룹니다.

 

 

의존성이란?

  • 의존하고 있는 대상의 변경에 영향을 받을 수 있는 가능성을 말합니다.

 

의존성 이해하기

  • 어떤 객체가 협력하기 위해 다른 객체를 필요할 때 두 객체 사이에 의존성이 존재한다고 말합니다.
  • 의존성은 실행 시점과 구현 시점에 서로 다른 의미를 가집니다. 
    • 실행 시점: 의존하는 객체가 정상적으로 동작하기 위해서는 실행 시에 의존 대상 객체가 반드시 존재해야 합니다.
    • 구현 시점: 의존 대상 객체가 변경될 경우 의존하는 객체도 함께 변경됩니다.
  • 두 요소 사이의 의존성은 의존되는 요소가 변경될 때 의존하는 요소도 함께 변경될 수 있다는 것을 의미하며 이 의미는 변경에 의한 영향의 전파 가능성을 암시합니다.

 

의존성 전이

  • 모든 경우에 의존성이 전이되는 것은 아니며, 의존성이 실제로 전이될지 여부는 변경의 방향과 캡슐화의 정도에 따라 달라집니다.
  • 의존성은 전이될 수 있기 때문에 의존성의 종류를 직접 의존성과 간접 의존성으로 나누기도 합니다.
    • 직접 의존성: 말 그대로 한 요소가 다른 요소에 직접 의존하는 경우
    • 간접 의존성: 직접적인 관계는 존재하지 않지만 의존성 전이에 의해 영향이 전파되는 경우

 

런타임 의존성과 컴파일타임 의존성

  • 의존성과 관련해서 다뤄야 하는 또 다른 주제는 런타임 의존성과 컴파일타임 의존성의 차이입니다.
  • 일반적으로 컴파일타임이란 작성된 코드를 컴파일하는 시점을 가리키지만 문맥에 따라서는 코드 그 자체를 가리키기도 합니다.
    • 객체지향에서 런타임의 주인공은 객체이며, 런타임 의존성이 다루는 주제는 객체사이의 의존성입니다.
    • 코드 관점에서 주인공은 클래스이며, 컴파일타임 의존성이 다루는 주제는 클래스 사이의 의존성입니다.
  • 어떤 클래스의 인스턴스가 다양한 클래스의 인스턴스와 협력하기 위해서는 협력할 인스턴스의 구체적인 클래스를 알아서는 안 되며, 실제로 협력할 객체가 어떤 것인지는 런타임에 해결해야 합니다.
  • 왜냐하면 클래스가 협력할 객체의 클래스를 명시적으로 드러내고 있다면 다른 클래스의 인스턴스와 협력할 가능성 자체가 없어집니다.
  • 따라서 컴파일타임 구조와 런타임 구조 사이의 거리가 멀수록 설계가 유연해지고 재사용 가능해집니다.

 

컨텍스트 독립성

  • 클래스가 사용될 특정한 문맥에 대해 최소한의 가정만으로 이뤄져 있다면 다른 문맥에서 재사용하기가 더 수월해지며 이를 컨텍스트 독립성이라 부릅니다.
  • 설계가 유연해지기 위해서는 가능한 한 자신이 실행될 컨텍스트에 대한 구체적인 정보를 최대한 적게 알아야 합니다.
  • 왜냐하면 컨텍스트에 대한 정보가 적을수록 더 다양한 컨텍스트에서 재사용될 수 있기 때문입니다.
  • 결과적으로 설계는 더 유연해지고 변경에 탄력적으로 대응할 수 있게 될 것입니다.

 

의존성 해결하기

  • 컴파일타임 의존성을 실행 컨텍스트에 맞는 적절한 런타임 의존성으로 교체하는 것을 의존성 해결이라고 부르며, 일반적으로 다음과 같은 세 가지 방법을 사용합니다.

 

객체를 생성하는 시점에 생성자를 통해 의존성 해결

Movie avata = new Movie(..., new AmountDiscountPolicy(...))

 

객체 생성 후 setter 메서드를 통해 의존성 해결

Movie avata = new Movie(...);
avata.calculateMovieFee(... new AmountDiscountPolicy(...));

 

메서드 실행 시 인자를 이용해 의존성 해결

Movie avata = new Movie(...);
avata.calculateMovieFee(... new AmountDiscountPolicy(...));

 

어떤 방식이 좋을까?

  • 더 좋은 방법은 생성자 방식과 setter 방식을 혼합하는 것입니다.
  • 생성자 방식과 setter 방식을 혼합한 방식
Movie avata - new Movie(..., new AmountDiscountPolicy(...))
...
avata.setDiscountPolicy(new AmountDiscountPolicy(...));
  • 항상 객체를 생성할 때 의존성을 해결해서 완전한 상태의 객체를 생성한 후, 필요에 따라 setter 메서드를 이용해 의존 대상을 변경할 수 있게 할 수 있습니다.
  • 이 방법은 시스템의 상태를 안정적으로 유지하면서도 유연성을 향상시킬 수 있기 때문에 의존성 해결을 위해 가장 선호되는 방법입니다.

 

유연한 의존성 설계

  • 객체들은 협력을 위해 상호간의 역할과 책임을 알아야 하며 이러한 지식은 객체들 사이의 의존성을 초래합니다.
  • 의존성은 객체들의 협력을 가능케 하는 필수적인 연결고리로 볼 수 있지만 지나치게 많아지면 문제가 될 수 있습니다.

 

바람직한 의존성이란 무엇일까?

  • 어떤 의존성이 다양한 환경에서 클래스를 재사용할 수 없도록 제한한다면 그 의존성은 바람직하지 못한 것이며, 의존성이 다양한 환경에서 재사용할 수 있다면 그 의존성은 바람직한 것입니다.
  • 다른 환경에서 재사용하기 위해 내부 구현을 변경하게 만드는 모든 의존성은 바람직하지 않은 의존성입니다.
  • 따라서 바람직한 의존성이란 설계를 재사용하기 쉽게 만드는 의존성을 의미하며, 설계를 재사용하기 어렵게 만드는 의존성을 바람직하지 못한 의존성이라 부릅니다.
  • 의존성을 좀 더 세련된 용어로 바꾸자면 결합도가 바로 그것입니다.
  • 결합도를 느슨하게 만들기 위해서는 협력하는 대상에 대해 필요한 정보 외에는 최대한 감추는 것이 중요합니다.

 

추상화에 의존하라

  • 추상화를 사용하면 대상에 대해 알아야 하는 지식의 양을 줄일 수 있기 때문에 결합도를 느슨하게 유지할 수 있습니다.
  • 일반적으로 추상화와 결합도의 관점에서 의존 대상을 다음과 같이 구분하는 것이 유용합니다.
  • 목록에서 아래쪽으로 갈수록 클라이언트가 알아야 하는 지식의 양이 적어지기 때문에 결합도가 느슨해집니다.
    • 구체 클래스 의존성
    • 추상 클래스 의존성
    • 인터페이스 의존성
  • 구체 클래스에 비해 추상 클래스는 메서드의 내부 구현과 자식 클래스의 종류에 대한 지식을 클라이언트에게 숨길 수 있습니다.
  • 따라서 클라이언트가 알아야 하는 지식의 양이 더 적기 때문에 구체 클래스보다 추상 클래스에 의존하는 것이 결합도가 더 낮습니다.
  • 하지만 추상 클래스의 클라이언트는 여전히 협력하는 대상이 속한 클래스 상속 계층이 무엇인지에 대해서는 알고 있어야 합니다.
  • 인터페이스에 의존하면 상속 계층을 모르더라도 협력이 가능해집니다.
  • 인터페이스 의존성은 협력하는 객체가 어떤 메시지를 수신할 수 있는지에 대한 지식만을 남기기 때문에 추상 클래스 의존성보다 결합도가 낮습니다.
  • 이것은 다양한 클래스 상속 계층에 속한 객체들이 동일한 메시지를 수신할 수 있도록 컨텍스트를 확장하는 것을 가능하게 합니다.
  • 여기서 중요한 것은 실행 컨텍스트에 대해 알아야 하는 정보를 줄일수록 결합도가 낮아진다는 것입니다.
  • 결합도를 느슨하게 만들기 위해서는 구체적인 클래스보다 추상 클래스에 추상 클래스보다 인터페이스에 의존하도록 만드는 것이 효과적입니다.
  • 다시 말해 의존하는 대상이 더 추상적일수록 결합도는 더 낮아진다는 것입니다.

 

명시적인 의존성

  • 결합도를 느슨하게 만들기 위해서는 인스턴스 변수의 타입을 추상 클래스나 인터페이스로 선언하는 것만으로는 부족하며 클래스 안에서 구체 클래스에 대한 모든 의존성을 제거해야만 합니다.
  • 의존성의 대상을 생성자의 인자로 전달받는 방법과 생성자 안에서 직접 생성하는 방법 사이의 가장 큰 차이점은 퍼블릭 인터페이스를 통해 정책을 설정할 수 있는 방법을 제공하는지 여부입니다.
    • 명시적인 의존성: 의존성이 명시적으로 퍼블릭 인터페이스에 노출되는 경우
    • 숨겨진 의존성: 의존성이 퍼블릭 인터페이스에 표현되지 않는 경우
  • 의존성이 명시적이지 않으면 의존성을 파악하기 위해 내부 구현을 직접 살펴볼 수밖에 없으며, 클래스를 다른 컨텍스트에서 재사용하기 위해 내부 구현을 직접 변경해야 한다는 것입니다.
  • 의존성을 명시적으로 드러내면 코드를 직접 수정해야 하는 위험을 피할 수 있습니다.
  • 왜냐하면 실행 컨텍스트에 적절한 의존성을 선택할 수 있기 때문입니다.
  • 유연하고 재사용 가능한 설계란 퍼블릭 인터페이스를 통해 의존성이 명시적으로 드러나는 설계입니다.
  • 명시적인 의존성을 사용해야만 퍼블릭 인터페이스를 통해 컴파일타임 의존성을 적절한 런타임 의존성으로 교체할 수 있습니다.

 

유연하고 재사용 가능한 설계란?

  • 객체가 어떻게(how) 하는지를 장황하게 나열하지 않고도 객체들의 조합을 통해 무엇(what)을 하는지를 표현하는 클래스들로 구성된 것을 의미합니다.
  • 따라서 클래스의 인스턴스를 생성하는 코드를 보는 것만으로 객체가 어떤 일을 하는지를 쉽게 파악할 수 있습니다.
  • 어떤 객체와 협력하느냐에 따라 객체의 행동이 달라지는 것은 유연하고 재사용 가능한 설계가 가진 특징입니다.
  • 유연하고 재사용 가능한 설계는 응집도 높은 책임들을 가진 작은 객체들을 다양한 방식으로 연결함으로써 앱의 기능을 쉽게 확장할 수 있습니다.
  • 훌륭한 객체지향 설계란 객체가 어떻게 하는지를 표현하는 것이 아니라 객체들의 조합을 선언적으로 표현함으로써 객체들이 무엇을 하는지를 표현하는 설계입니다.
  • 그리고 지금까지 설명한 것처럼 이런 설계를 창조하는 데 있어서의 핵심은 의존성을 관리하는 것입니다.

 

마무리

오늘은 오브젝트 책 8장을 스터디하면서 중요하다고 생각한 부분을 정리해 보았습니다.

이번 장에서는 의존성에 대해 좀 더 자세하게 알수 있어서 좋았으며, 유연한 설계를 하기 위해 추가적으로 알아야할 내용들을 논리적으로 이해할 수 있게 되어서 좋았다고 생각합니다.

하지만 책에 제공된 예제들로는 실제 개발에 바로 이해해서 적용하기에는 좀 부족하지 않을까? 라는 생각이 드네요.

이번 포스팅은 마무리하면서 다음 포스팅에서 뵙겠습니다.

 

 

참고

http://www.yes24.com/Product/Goods/74219491

 

오브젝트 - YES24

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

www.yes24.com

 

'스터디 > 오브젝트' 카테고리의 다른 글

10장 - 상속과 코드 재사용  (0) 2023.08.06
9장 - 유연한 설계  (0) 2023.07.24
7장 - 객체 분해  (0) 2023.04.30
6장 - 메시지와 인터페이스  (0) 2023.04.17
5장 - 책임 할당하기  (0) 2023.04.10