8장 의존성 관리하기

swucs·2022년 2월 9일
0

오브젝트

목록 보기
7/13

출처 : 오브젝트 (조영호 저)

협력을 위해서는 의존성이 필요하지만 과도한 의존성은 애플리케이션을 수정하기 어렵게 만든다. 객체지향 설계의 핵심은 협력을 위해 필요한 의존성은 유지하면서도 변경을 방해하는 의존성은 제거하는 데 있다.

변경과 의존성

의존성은 실행시점과 구현시점에 서로 다른 의미를 가진다.

  • 실행시점 : 의존하는 객체가정상적으로 동작하기 위해서는 실행시에 의존 대상 객체가 반드시 존재해야 한다.
  • 구현시점 : 의존 대상 객체가 변경될 경우 의존하는 객체도 함께 변경된다.

의존성은 방향성을 가지며 항상 단반향이다. 두 요소 사이의 의존성은 의존되는 요소가 변경될 때 의존하는 요소도 함께 변경될 수 있다는 것을 의미한다. 따라서 의존성은 변경에 의한 영향의 전파 가능성을 암시한다.

의존성의 종류

  • 상속
  • 멤버변수
  • 메서드의 파라미터

의존성 전이(Transitive dependency)

A가 B에 의존하고, B가 C에게 의존한다고 가정할 때 자동적으로 A가 C에게 의존하게 되는 것을 의존성 전이라고 한다. 즉, 의존성이 전파되는 것이다.
의존성이 실제로 전이될 지 여부는 변경의 방향과 캡슐화의 정도에 따라 달라진다. 의존성 전이는 변경에 의해 영향이 널리 전파될 수도 있다는 경고일 뿐이다.

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

런타임은 애플리케이션이 실행되는 시점을 가리킨다. 컴파일타임은 작성된 코드를 컴파일하는 시점을 가리키지만 문맥에 따라서는 코드 그 자체를 가리키기도 한다.

객체지향 애플리케이션에서 런타임의 주인공은 객체다. 반면 코드 관점에서 주인공은 클래스다. 여기서 중요한 것은 런타임 의존성과 컴파일타임 의존성이 다를 수 있다는 것이다.

어떤 클래스의 인스턴스가 다양한 클래스의 인스턴스와 협력하기 위해서는 협력할 인스턴스의 구체적인 클래스를 알아서는 안된다. 실제로 협력할 객체가 어떤 것인지는 런타임에 해결해야 한다. 클래스가 협력할 객체의 클래스를 명시적으로 드러내고 있다는 다른 클래스의 인스턴스와 협력할 가능성 자체가 없어진다.

컨텍스트 독립성

클래스가 특정한 문맥에 강하게 결합될수록 다른 문맥에서 사용하기는 더 어려워진다. 클래스가 사용될 특정한 문맥에 대해 최소한의 가정만으로 이뤄져 있다면 다른 문맥에서 재사용하기가 더 수월해진다. 이를 컨텍스트 독립성이라고 부른다.

설계가 유연해지기 위해서는 가능한 자신이 실행될 컨텍스트에 대한 구체적인 정보를 최대가 적게 알아서, 더 다양한 컨텍스트에서 재사용될 수 있도록 해야 한다.

시스템을 구성하는 객체가 컨텍스트 독립적이라면 해당 시스템은 변경하기 쉽다. 여기서 컨텍스트 독립적이라는 말은 각 객체가 해당 객체를 실행하는 시스템에 관해 아무것도 알지 못한다는 의미다.

의존성 해결하기

컴파일타임 의존성은 구체적인 런타인 의존성으로 대체돼야 한다. 컴파일 의존성을 실행 컨텍스트에 맞는 적절한 런타임 의존성으로 교체하는 것을 의존성 해결이라고 부른다. 의존성을 해결하기 위해서는 일반적으로 세 가지 방법을 사용한다.

  • 객체를 생성하는 시점에 생성자를 통해 의존성 해결
  • 객체 생성 후 setter 메서드를 통해 의존성 해결
  • 메서드 실행 시 인자를 이용해 의존성 해결

setter 메서드를 이용하는 방식은 객체를 생성한 이후에도 의존하고 있는 대상을 변경할 수 있는 가능성을 열어 놓고 싶은 경우 유용하다. 단점은 객체가 생성된 후에 협력에 필요한 의존 대상을 설정하기 때문에 객체의 상태가 불완전(NullPointerException 발생가능성)할 수 있다는 점이다.

메서드 인자를 사용하는 방식은 협력 대상에 대해 지속적으로 의존 관계를 맺을 필요 없이 메서드가 실행되는 동안만 의존관계가 존재해도 무방하거나, 메서드가 실행될 때마다 의존 대상이 달라져야 하는 경우에 유용하다.

의존성과 결합도

바람직한 의존성이란 무엇인가? 어떤 의존성이 다양한 환경에서 재사용할 수 있다면 그 의존성은 바람직한 것이다.컨텍스트에 독립적인 의존성은 바람직한 의존성이다.
두 요소 사이에 존재하는 의존성이 바람직할 때 느슨한 결합도(loose coupling) 또는 약한 결합도(weak coupling)를 가진다고 말한다.

지식이 결합을 낳는다.

결합도를 느슨하게 유지하려면 협력하는 대상에 대해 필요한 정보 외에는 최대한 감추는 것이 중요하다. 가장 효과적인 방법은 추상화이다.

추상화에 의존하라

추상화를 사용하면 현재 다루고 있는 문제를 해결하는 데 불필요한 정보를 감출 수 있다. 추상화와 결합도 관점에서 의존 대상을 다음과 같이 구분한다. 아래로 갈 수록 결합도가 느슨해진다.

  • 구체 클래스 의존성 (concrete class dependency)
  • 추상 클래스 의존성 (abstract class dependency)
  • 인터페이스 의존성 (interface dependency)

인터페이스 의존성은 협력하는 객체가 어떤 메시지를 수신하는지에 대한 지식만을 남기기 때문에 추상클래스 의존성보다 결합도가 낮다.
의존하는 대상이 더 추상적일 수록 결합도는 더 낮아진다. 이것이 핵심이다.

명시적인 의존성

결합도를 느슨하게 하기 위해서 인스턴스 변수의 타입을 추상 클래스나 인터페이스로 선언하는 것만으로는 부족하다. 만약 구체적인 클래스를 new를 통해 생성한다면 결국 결합도가 높아진다.

public class Movie {
	private DiscountPolicy disountPolicy;
    public Movie() {
    	this.discountPolicy = new AmountDiscountPolicy(...);
    }
}

의존성을 해결하는 방법에는 생성자, setter 메서드, 메서드 인자를 사용하는 방식이 존재한다.

의존성의 대상을 위와 같이 외부에서 전달 받는 방법과 내부에서 직접 생성하는 방법의 가장 큰 차이점은 퍼블릭 인터페이스를 통해 노출되는지 여부이다. 외부에서 전달받는 방식은 명시적인 의존성(explicit dependency)이라고 부른다. 명시적인 의존성을 사용해야만 런타임 의존성으로 교체할 수 있다.

new는 해롭다.

결합도 측면에서 new가 해로운 이유는 크게 두 가지이다.

  • new 연산자를 사용하기 위해서는 구체 클래스의 이름을 직접 기술해야 한다.
  • new 연산자는 생성하려는 구체 클래스뿐만 아니라 어떤 인자를 이용해 클래스의 생성자를 호출해야 하는지도 알아야 한다.

new는 결합도를 높이기 때문에 해롭다. new는 협력할 클래스의 인스턴스를 생성하기 위해 어떤 인자들이 필요하고 그 인자들을 어떤 순서로 사용해야 하는지에 대한 정보를 노출하고 의존성을 추가한다.

표준 클래스에 대한 의존은 해롭지 않다.

의존성이 불편한 이유는 변경에 대한 영향을 암시하기 때문이다. 따라서 변경될 확률이 거의 없는 클래스라면 의존성이 문제가 되지 않는다.

비록 클래스를 직접 생성하더라도 가능한 추상적인 타입을 사용하는 것이 확장성 측면에서 유리하다. (ArrayList 대신 List를 사용하는 것)

profile
백엔드 개발자

0개의 댓글

관련 채용 정보