> For the complete documentation index, see [llms.txt](https://develop-footprint.gitbook.io/o/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://develop-footprint.gitbook.io/o/programming/object/9.md).

# 9장: 유연한 설계

## 개방-폐쇄 원칙

* Open-Closed Principle
* 소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.
* 애플리케이션의 요구사항이 변경될 때 이 변경에 맞게 새로운 ‘동작’을 기존의 코드를 수정하지 않고도 추가해서 애플리케이션의 기능을 확장할 수 있어야 한다.
* 런타임 의존성은 실행 시에 협력에 참여하는 객체들 사이의 관계이고, 컴파일타임 의존성은 코드에서 드러나는 클래스들 사이의 관계다.
* 개방-폐쇄 원칙을 수용하는 코드는 컴파일타임 의존성을 수정하지 않고도 런타임 의존성을 쉽게 변경 할 수 있다.
* 추상화에 의존하면 컨텍스트가 바뀌더라도 공통적인 부분은 변하지 않고 공통적이지 않은 부분은 생략한다. 즉, 수정에 대해 닫혀있는 것이므로 추상화에 의존하면 개방-폐쇄 원칙을 지키게 된다.
* 변경에 의한 파급효과를 최대한 피하기 위해서는 변하는 것과 변하지 않는 것이 무엇인지를 이해하고 이를 추상화의 목적으로 삼아야만 한다.

## 객체 생성과 사용 분리

* 추상화에만 의존하려면 구체 클래스의 인스턴스를 생성하면 안된다. 왜냐하면 동작을 추가하거나 변경하기 위해서 기존 코드를 수정해야 하기 때문이다.
* 유연하고 재사용 가능한 설계를 위해서는 객체에 대한 생성과 사용을 분리해야 한다.
* 가장 보편적인 생성과 사용 분리 방법은 객체를 생성할 책임을 컨텍스트에 대한 결정권을 갖고 있는 클라이언트로 옮기는 것이다.
* 클라이언트가 객체를 생성하고 객체를 사용하는 경우 객체 생성과 사용의 분리가 되지 않을 수 있다. 만약 이를 분리함으로써 클라이언트도 특정 컨텍스트에 묶이지 않길 바란다면 팩토리 방식을 사용할 수 있다.
* 팩토리 클래스는 객체 생성의 책임을 가지고, 클라이언트는 객체의 사용에 관련한 책임만 가질 수 있다.

<figure><img src="https://946524419-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZqOTWctN1y9hEXEl4sKd%2Fuploads%2F6hArL2RbhNkxjonunsNg%2Fimage.png?alt=media&amp;token=6d60131a-9f72-49d5-bfa5-1a2624f729ba" alt=""><figcaption></figcaption></figure>

* 이전에 책임 할당 원칙 중 Information Expert 원칙이 있었는데, 이는 도메인 모델 안의 개념 중 적절한 후보가 있는지 확인하고 책임 수행에 필요한 정보를 가장 많이 아는 객체에 책임을 할당하는 것이다.
* 팩토리 클래스는 도메인 개념과 아무런 상관이 없지만 객체 생성 책임을 가진다.
* 시스템을 객체로 분해하는 방법에는 도메인에 존재하는 사물 또는 개념을 표현하는 객체들을 이용해 시스템을 분해하는 **표현적 분해**와 도메인 개념들을 초월하여 설계자의 편의를 위한 임의의 가공 객체에 책임을 할당하는 **행위적 분해**로 나뉜다.
* Pure Fabrication
  * 책임을 할당하기 위해 창조되는 도메인과 무관한 인공적인 객체이다.
  * 순수히 전체 설계의 품질을 높이기 위해 설계자의 임의에 따라 추가한 가공물인 객체이다.
  * 어떤 행동을 추가하고자 하지만 책임질만한 도메인 개념이 없다면 이 객체에게 책임을 할당하면 된다.
  * 일반적으로 행위적 분해에 의해 생성된다.
  * 대부분의 디자인 패턴은 Pure Fabrication을 포함한다.

## 의존성 주입

* 외부의 독립적인 객체가 인스턴스를 생성한 후 이를 전달해서 의존성을 해결하는 방법
* 의존성을 해결하기 위해 의존성을 객체의 퍼블릭 인터페이스에 명시적으로 드러내서 외부에서 필요한 런타임 의존성을 전달할 수 있도록 만드는 방법을 포괄한다.
* 의존성 주입은 **의존성을 명시적으로 표현**할 수 있다.
* 의존성을 해결하는 세 가지 방법은 아래와 같다.

  * 생성자 주입(constructor injection)
    * 객체를 생성하는 시점에 생성자를 통한 의존성 해결
    * 객체의 생명주기 전체에 걸쳐 관계를 유지한다.
  * setter 주입(setter injection)
    * 객체 생성 후 setter 메서드를 통한 의존성 해결
    * 의존성의 대상을 런타임에 변경할 수 있다.
    * 객체가 올바른 상태가 되기 위해 어떤 의존성이 필수적인지 명시적으로 표현이 불가능하다.
  * 메서드 주입(method injection)
    * 메서드 실행 시 인자를 이용한 의존성 해결

    * 주입된 의존성이 특정 메서드에서만 사용된다면, 메서드 인자로 전달받는 것이 좋다.

  > 인터페이스 주입
  >
  > * 주입할 의존성을 명시하기 위해 인터페이스를 사용하는 방법이다.
  > * 특정 인터페이스에 의존성을 입력받는 메서드를 선언해두고, 인터페이스의 구현체에서 해당 메서드를 재정의하면서 의존성이 주입된다.
* Service Locator 패턴

  * 객체가 Service Locator에게 의존성 해결을 위해 요청을 보내는 방식으로 의존성을 해결할 수 있도록 하는 방법이다.

  ```java
  public class Movie {
  	
  	private DiscountPolicy discountPolicy;
  	
  	public Movie(String title, Duration runningTime, Money fee) { 
  		this.title = title;
  		this.runningTime = runningTime;
  		this.fee = fee;
  		this.discountPolicy = ServiceLocator.discountPolicy();
  	}
  }

  public class ServiceLocator {
  	private static ServiceLocator soleInstance = new ServiceLocator();
  	private DiscountPolicy discountPolicy;
  	
  	public static DiscountPolicy discountPolicy() {
  		return soleInstance.discountPolicy;
  	}
  	
  	public static void provide(DiscountPolicy discountPolicy) {
  		soleInstance.discountPolicy = discountPolicy;
  	}
  }

  public static void main(String[] args) {
  	// Movie 객체 생성
  	ServiceLocator.provide(new AmountDiscountPolicy(...)); 
  	Movie avatar = new Movie("아바타", Duration.ofMinutes(120), Money.wons(10000));
  }
  ```

  * 이 패턴은 의존성을 감추기 때문에 의존성과 관련된 문제가 컴파일 타임이 아닌 런타임에 가서 발견된다. (생성자 주입이었다면 컴파일 에러부터 났을 것이다.)
  * 그리고 의존성을 이해하기 위해 코드의 내부 구현을 이해할 것을 강요하므로 캡슐화를 위반한다. 필요한 의존성은 클래스의 퍼블릭 인터페이스에 명시적으로 드러나야 한다.
  * 의존성을 숨기는 코드는 단위 테스트 작성도 어렵다. 각 단위테스트는 서로 고립되어야 하는 원칙이 있지만 ServiceLocator가 내부적으로 정적 변수를 이용해 객체를 관리하기 때문에 모든 단위 테스트에 거쳐 ServiceLocator의 상태를 공유하게 된다.
  * 깊은 호출 계층에 걸쳐 동일한 객체를 계속해서 전달해야 하는 고통을 견디기 어려운 경우에만 어쩔 수 없이 SERVICE LOCATOR 패턴을 사용하는 것을 고려하도록 한다.

## 의존성 역전 원칙

* 어떤 협력에서 중요한 정책이나 의사결정, 비즈니스의 본질을 담고 있는 것을 상위 수준의 클래스라고 하며, 이러한 상위 수준 클래스가 하위 수준 클래스에 의존하면 하위 수준의 변경에 의해 상위 수준 클래스가 영향을 받을 수 있다.
* 상위 수준의 클래스가 하위 수준의 클래스에 의존하는 대신, **상위 수준의 클래스와 하위 수준의 클래스가 모두 추상화에 의존해야 한다**.

<figure><img src="https://946524419-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZqOTWctN1y9hEXEl4sKd%2Fuploads%2F9Y2ROSXpyWm4aMZwQ0Cf%2Fimage.png?alt=media&amp;token=ec81ee30-ea0c-4001-9c76-c3f46abb2d49" alt=""><figcaption></figcaption></figure>

* 의존성 역전 원칙을 따르는 설계는 의존성의 방향이 전통적인 절차형 프로그래밍과는 반대 방향으로 나타난다.
* Seperated Interface 패턴

  * 자바의 모듈을 구현할 수 있는 패키지 단위를 기준으로 추상화 인터페이스가 구현 클래스와 같은 패키지에 존재한다면, 구현 클래스 수정 시 할인 정책 패키지가 재컴파일 되어야 하고, 이에 따라 영화 패키지도 재컴파일되어야 할 수 있다.
  * 아래는 추상화 인터페이스인 DiscountPolicy와 하위 수준 클래스가 함께 패키지에 존재하여, 상위 수준 클래스인 Movie가 하위 수준 클래스의 변경에 영향을 받을 수 있는 구조이다.

  <figure><img src="https://946524419-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZqOTWctN1y9hEXEl4sKd%2Fuploads%2FCdyTbLXt1PfTA9Aa7gEF%2Fimage.png?alt=media&amp;token=3cd873a5-4450-4864-8afe-bff44b68d467" alt=""><figcaption></figcaption></figure>

  * 이를 방지하기 위해 추상화를 별도의 독립적인 패키지가 아니라 클라이언트가 속한 패키지에 포함시켜야 한다.
  * 의존성 역전 원칙에 따라 상위 수준의 협력 흐름을 재사용하기 위해서는 추상화가 제공하는 인터페이스의 소유권을 서버가 아닌 클라이언트로 역전시켜야 한다.

  <figure><img src="https://946524419-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZqOTWctN1y9hEXEl4sKd%2Fuploads%2FUVY5GDp82D9qW4DgJ30M%2Fimage.png?alt=media&amp;token=9c4b1f52-a2c0-4b00-8108-0d89356e6f31" alt=""><figcaption></figcaption></figure>

## 유연성

* 유연성은 복잡성과 암시성을 수반하며, 유연하지 않은 설계는 단순하고 명확하다.
* 복잡성에 대한 걱정보다 유연하고 재사용 가능한 설계의 필요성이 더 크다면 코드의 구조와 실행 구조를 다르게 만들어야 한다.
* 설계를 유연하게 만들기 위해서는 결국 **역할, 책임, 협력**에 초점을 맞춰야 하며, 이에 대한 **설계의 유연성과 재사용성을 위해 의존성 관리가 필요하다**는 것을 기억해야 한다.
* 다양한 컨텍스트에서 협력을 재사용할 수 있도록 설계한다면, 공통의 추상화를 도출하고 동일한 역할을 여러 객체들 간 대체해가며 수행할 수 있도록 할 수 있다.
* 중요한 비즈니스 로직을 처리하기 위해 책임을 할당하고 협력의 균형을 맞춘 후에 객체를 생성하는 방법을 결정해야 한다.
