개방폐쇄 원칙생성 사용 분리순수한 가공물에게 책임 할당하기Factory 추가하기의존성 주입숨겨진 의존성은 나쁘다의존성 역전 원칙(DIP = Dependency Inversion Principle)의존성 역전 원칙과 패키지유연성에 대한 조언협력과 책임이 중요하다
개방폐쇄 원칙
- 의존성 관점에서 개방-폐쇄 원칙을 따르는 설계란 컴파일타임 의존성은 유지하면서 런타임 의존성의 가능성을 확장하고 수정할 수 있는 구조
- 개방-폐쇄 원칙의 핵심은
추상화에 의존하는 것
임
- 추상화란 핵심적인 부분만 남기고 불필요한 부분은 생략 함으로써 복잡성을 극복하는 기법임
- 변경에 의한 파급효과를 최대한 피하기 위해서는 변하는 것과 변하지 않는 것이 무엇인지를 이해하고 이를 추상화의 목적으로 삼아야만 한다.
생성 사용 분리
- 유연하고 재사용 가능한 설계를 원한다면 객체와 관련된 두 가지 책임을 서로 다른 객체로 분리해야 한다
- 하나는 객체를 생성하는 것이고
- 다른 하나는 객체를 사용하는 것
- 한마디로 객체에 대한
생성과 사용을 분리
해야 한다. - 현재의 컨텍스트에 관한 결정권을 가지고 있는 클라이언트로 컨텍스트에 대한 지식을 옮김으로써 Movie는 특정한 클라이언트에 결합되지 않고 독립적일 수 있음
public class Client { public Money getAvatarFee() { Movie avatar = new Movie("아바타", Duration.ofMinutes(120), Money.wons(10000), new AmountDiscountPolicy(..)); return avatar.getFee(); } }
순수한 가공물에게 책임 할당하기
- 시스템을 객체로 분해하는 큰 두가지 방식 :
표현적 분해
,행위적 분해
표현적 분해
: 도메인에 존재하는 사물 또는 개념을 표현하는 객체들을 이용해 시스템을 분해하는 것. 이 방법은 도메인 모델에 담겨 있는 개념과 관계를 따르며 도메인과 소프트웨어 사이의 표현적 차이를 최소화하는 것을 목적으로 함- 그러나 종종 도메인 개념을 표현하는 객체에게 책임을 할당하는 것만으로는 부족한 경우가 발생함. 도메인 모델은 설계를 위한 중요한 출발점이지만 단지 출발점이라는 사실을 명심해야 함
행위적 분해
: 어떤 행동을 추가하려고 하는데 이 행동을 책임질 마땅한 도메인 개념이 존재하지 않는다면PURE FABRICATION
을 추가하고 이 객체에게 책임을 할당하라. 그 결과로 추가된PURE FABRICATION
은 보통 특정한 행동을 표현하는 것이 일반적이다.- 따라서
PURE FABRICATION
은 표현적 분해보다는 행위적 분해에 의해 생성되는 것이 일반적 - 그래서 객체지향 애플리케이션이 실세계의 모방이라는 말은 옳지 않음. 도메인 개념 뿐 아니라 설계자들이 임의적으로 창조한 인공적인 추상화들을 포함하고 있음.
Factory 추가하기
FACTORY
는 객체의 생성 책임을 할당할 만한 도메인 객체가 존재하지 않을 때 선택할 수 있는PURE FABRICATION
임
의존성 주입
의존성을 해결하는 세 가지 방법
생성자 주입
setter 주입
메서드 주입
명시적인 의존성이 숨겨진 의존성보다 좋다.
가급적 의존성을 객체의 퍼블릭 인터페이스에 노출하라.
의존성을 구현 내부에 숨기면 숨길수록 코드를 이해하기도, 수정하기도 어려워진다.
명시적인 의존성에 초점을 맞추면 유연성을 향상시킬 수 있다.
숨겨진 의존성은 나쁘다
SERVICE LOCATOR
패턴 : 의존성을 해결할 객체들을 보관하는 일종의 저장소- 외부에서 객체에게 의존성을 전달하는 의존성 주입과 달리
SERVICE LOCATOR
의 경우 객체가 직접SERVICE LOCATOR
에게 의존성을 해결해줄 것을 요청함
SERVICE LOCATOR 패턴은 서비스를 사용하는 코드로부터 서비스가 누구인지(서비스를 구현한 구체 클래스의 타입이 무엇인지), 어디에 있는지(클래스 인스턴스를 어떻게 얻을지)를 몰라도 되게 해준다.
그러나, SERVICE LOCATOR 패턴의 가장 큰 단점은 의존성을 감춰버린다는 것
캡슐화는 코드를 읽고 이해하는 행위와 관련이 있다. 클래스의 퍼블릭 인터페이스만으로 사용 방법을 이해할 수 있는 코드가 캡슐화의 관점에서 훌륭한 코드다. 클래스의 사용법을 익히기 위해 구현 내부를 샅샅이 뒤져야 한다면 그 클래스의 캡슐화는 무너진 것이다.
숨겨진 의존성이 가지는 가장 큰 문제점은 의존성을 이해하기 위해 코드의 내부 구현을 이해할 것을 강요한다는 것 ⇒ 캡슐화 위반
의존성 역전 원칙(DIP = Dependency Inversion Principle)
1. 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 된다. 둘 모두 추상화에 의존해야 한다.
2. 추상화는 구체적인 사항에 의존해서는 안 된다. 구체적인 사항은 추상화에 의존해야 한다.
유연하고 재사용 가능하며 컨텍스트에 독립적인 설계는 전통적인 패러다임이 고수하는 의존성의 방향을 역전시킨다.
전통적인 패러다임에서는 상위 모듈이 하위 수준 모듈에 의존했다면 객체지향 패러다임에서는 둘 모두 추상화에 의존함.
전통적인 패러다임에서는 인터페이스가 하위 수준 모듈에 속했다면 객체지향 패러다임에서는 인터페이스가 상위 수준 모듈에 속함
public class Movie { private AmountDiscountPolicy discountPolicy; }
- 이 설계가 변경에 취약한 이유는 요금을 계산하는 상위 정책이 요금을 계산하는 데 필요한 구체적인 방법에 의존하기 때문임
Movie
는 가격 계산이라는 더 높은 수준의 개념을 구현하는데 비해AmountDiscountPolicy
는 영화의 가격에서 특정한 금액만큼을 할인해주는 더 구체적인 수준의 메커니즘을 담당함- 즉, 상위 수준 클래스인
Movie
가 하위 수준 클래스인AmountDiscountPolicy
에 의존하는 것
- 의존성은 변경의 전파와 관련된 것이기 때문에 설계는 변경의 영향을 최소화하도록 의존성을 관리해야 한다. 의존성은 Movie에서 AmountDiscountPolicy가 아닌 반대 방향으로 흘러야 한다.
- 상위 수준의 클래스는 어떤 식으로든 하위 수준의 클래스에 의존해서는 안 된다.

의존성 역전 원칙과 패키지


- 추상화를 별도의 독립적인 패키지가 아니라 클라이언트가 속한 패키지에 포함시켜야 한다. 그리고 함께 재사용될 필요가 없는 클래스들은 별도의 독립적인 패키지에 모아야 함 ⇒
SEPARATED INTERFACE
패턴(by Martin Fowler)
Movie
와 추상 클래스인DiscountPolicy
를 하나의 패키지로 모으는 것은Movie
를 특정한 컨텍스트로부터 완벽하게 독립시킨다. Movie를 다른 컨텍스트에서 재사용하기 위해서는 단지 Movie와 DiscountPolicy가 포함된 패키지만 재사용하면 됨(AmountDiscountPolicy와 PercentDiscountPolicy는 알 필요 없이.)
- 이렇게 인터페이스의 소유권을 역전시키게 되면 상위 수준의 협력 흐름을 재사용하기가 쉬워짐!
- 전통적인 설계 패러다임은 위의 그림과 같이 인터페이스의 소유권을 클라이언트 모듈이 아닌 서버 모듈에 위치시키는데 반해 잘 설계된 객체지향 어플리케이션에서는 인터페이스의 소유권을 서버가 아닌 클라이언트에 위치시킴.
유연성에 대한 조언
의존성을 관리해야 하는 이유는 역할, 책임, 협력의 관점에서 설계가 유연하고 재사용 가능해야 하기 때문이다. 따라서
역할, 책임, 협력에 먼저 집중하라.
이번 장에서 설명한 다양한 기법들을 적용하기 전에 역할, 책임, 협력의 모습이 선명하게 그려지지 않는다면 의존성을 관리하는 데 들이는 모든 노력이 물거품이 될 수도 있다는 사실을 명심하라.- 유연하고 재사용 가능한 설계가 항상 좋은 것은 아니다. 설계의 미덕은 단순함과 명확함으로부터 나온다.
- 변경하기 쉽고 확장하기 쉬운 구조를 만들기 위해서는 단순함과 명확함의 미덕을 버리게 될 가능성이 높다.
- 유연한 설계라는 말의 이면에는 복잡한 설계라는 의미가 숨어 있다
- 변경은 예상이 아니라 현실이어야 한다. 미래에 변경이 일어날지도 모른다는 막연한 불안감은 불필요하게 복잡한 설계를 낳는다. 아직 일어나지 않은 변경은 변경이 아니다.
불필요한 유연성은 불필요한 복잡성을 낳는다
. 단순하고 명확한 해법이 그런대로 만족스럽다면 유연성을 제거하라. 유연성은 코드를 읽는 사람들이 복잡함을 수용할 수 있을 때만 가치가 있다.
협력과 책임이 중요하다
- 설계를 유연하게 만들기 위해서는 먼저 역할, 책임, 협력에 초점을 맞춰야 한다.
- 다양한 컨텍스트에서 협력을 재사용할 필요가 없다면 설계를 유연하게 만들 당위성도 함께 사라진다.
- 초보자가 자주 저지르는 실수 중 하나는 객체의 역할과 책임이 자리를 잡기 전에 너무 성급하게 객체생성에 집중하는 것이다. 이것은 객체 생성과 관련된 불필요한 세부사항에 객체를 결합시킨다. 객체를 생성할 책임을 담당할 객체나 객체 생성 메커니즘을 결정하는 시점은 책임 할당의 마지막 단계로 미뤄야만 한다.
- 책임의 불균형이 심화되고 있는 상태에서 객체의 생성 책임을 지우는 것은 설계를 하부의 특정한 메커니즘에 종속적으로 만들 확률이 높다.