변경에 의한 파급효과 최소화 위해서는 변하는것과 변하지 않는것이 무엇인지 이해하고 추상화의 목적으로 삼아야한다
2. 생성 사용 분리
Movie 클래스 내부에서 AmointDiscoutPolicy 같은 구체 클래스의 인스턴스를 생성해서는 안된다
개방 폐쇄 원칙 위반
이것은 동일 클래스 내 객체 생성과 사용이라는 '이질적인' 목적을 가진 코드가 공존하는것이 문제다
결국 '생성과 사용을 분리'해야한다
객체 생성은 클라이언트로 옮긴다.
클라이언트의 컨텍스트에 대한 지식으로 옮기므로, 특정 클라이언트에 결합되지 않고 독립적이다
Factory 추가하기
하지만 client 입장에서는 생성과 사용 책임을 모두 가지고 있다.
public Money getAvatarFee() {
Movie avatar = new Movie("아바타",
Duration.ofMinutes(120),
Money.wons(10000),
new AmountDiscountPolicy(
Money.wons(800),
new SequenceCondition(1),
new SequenceCondition(10)));
return avatar.getFee();
}
생성하는 책임을 모두 factory 로 옮길 수있다.
이렇게 되면 clien 는 오직 사용과 관련된 책임만 지고, 생성관련 지식은 가질 필요가 없다.
public Money getAvatarFeeByFactory() {
return factory.createAvatarMovie().getFee();
}
순수한 가공물에 책임 할당하기
표현적 분해와 행위적 분해
전자는 도메인 모델에 있는 개념과 관계를 따르며, 도메인과 sw 사이 표현적 차이 최소화가 목표 (OOP 의 기본적 접근법)
후자는 전자로 부족한경우 도메인 개념을 초월하는 기계적인 개념
행위적 분해
Pure Fabrication (순수한 가공물) 이라 부른다.
OOP는 실세계 모방이라는 말은 옳지않다.
마치 현대적인 도시가 자연물보다는 건물이나 도로와 같은 인공물로 가득 차 있는 것과 유사하다
Pure FABRICATION 은 INFORMATION EXPERT 패턴에 따라 책임을 할당한 결과가 바람직하지 않은 대안으로 사용된다
3. 의존성 주입
생성과 사용을 분리하면 Movie에는 오로지 인스턴스를 사용하는 책임만 남는다.
사용하는 객체가 아니라, 외부 독립적인 객체가 인스턴스 생성 후 전달해서 의존성 해결하는 방법을 의존성 주입(DI)이라 한다.
DI 라고 하는 이유는 외부에서 의존성 대상을 해결하고, 사용하는 쪽에 주입하기 때문이다.
주입 방법 3가지 : 생성자 주입 / setter 주입 / 메서드 주입
숨겨진 의존성은 나쁘다. (SERVICE LOCATOR)
DI 외 의존성 해결 방법으로 SERVICE LOCATOR 패턴이 있다.
SERVICE LOCATOR 는 의존성을 해결할 객체들을 보관하는 저장소이다.
클라이언트 코드가 서비스가 누구인지, 어디에 있는지를 몰라도 되게 해준다
하지만 가장 큰 문제점은 의존성을 감춘다. Movie의 퍼블릭 인터페이스 어디에도 의존성에 대한 정보가 없다.
따라서 SERVICE LOCATOR 를 호출하지 않고 Movie 생성 시 NPE 발생가능
컴파일시점에는 모르고 런타임 시점에만 알게 되므로 디버깅이 어려움
단위테스트 작성도 어렵다
모든 단위테스트가 SERVICE LOCATOR 의 상태를 공유하게 된다. -> 단위테스트는 서로 고립되어야 하는 원칙 위반
코드의 내부구현을 이해할 것을 강요한다
의존성을 구현 내부로 감추도록 강요하는 SERVICE LOCATOR는 캡슐화를 위반 할 수밖에 없다.
숨겨진 의존성은 의존성의 대상을 설정하는 시점과 의존성이 해결되는 시점을 유리시킨다.
코드 이해와 디버깅이 어려운데 인스턴스 설정하는 부분을 찾으면되지만, 개발환경에 따라 코드검색의 난이도는 천차만별...
권장경우 : DI 프레임웍 사용을 못학거나, 깊은 호출계층에 걸쳐 동일한 객체 전달하는 고통이 견딜 수없는경우에 권장
(후자 : 지난주 내 고민같은)
4. 의존성 역전 원칙
추상화와 의존성 역전
의존성은 변경의 전파와 관련되므로 설계는 변경의 영향을 최소화 하도록 의존성을 관리해야한다.
의존성은 Movie 에서 AmountDiscountPolicy 로 흘러서는 안되고 그 반대여야한다.
즉 상위수준의 클래스는 어떤 경우에도 하위수준에 의존해서는 안된다 (DIP 원칙)
가장 중요한 조언 : 추상화에 의존하라
구체 클래스는 의존성의 시작이어야한다. / 의존성의 목적지가 돼서는 안 된다.
왜 역전? : 전통적인 절차적인 프로그래밍과는 반대 방향으로 나타나므로
의존성 역전 원칙과 패키지
역전은 의존성의 방향 뿐만 아니라 인터페이스 소유권에도 적용된다.
DiscoutPolicy 내 구현체들이 같은 패키지에 있으면 전체가 같이 재배포 되어야한다.
재사용될 필요가 없는 클래스들을 별도의 독립적인 패키지에 모아야한다 (Seperated interface 패턴)
5. 유연성에 대한 조언
1. 유연한 설계는 유연한 설계가 필요할 때만 옳다
유연하고 재사용 가능한 설계가 항상 좋은것은 아니다.
설계의 미덕은 단순함과 명확인데 버리게 될 수도 있다.
유연한 설계라는 말의 이면에는 복잡한 설계라는 의미가 숨어있다. (양면성)
유연한지 여부 등의 판단은 공학이기보다는 심리학에 가깝다.
변경은 예상이 아니라 현실이어야한다
막연한 불안감은 불필요하게 복잡한 설계를 낳는다.
유연한 설계를 단순하고 명확하게 만드는 방법은 사람들간의 긴밀한 커뮤니케이션 이다
복잡성의 이유와 근거를 제시하지 않으면 어느 누구도 만족스러운 해법으로 받아들이지 않는다
유연성은 읽는 사람이 복잡성을 수용할 때 가치가 있다.
2. 협력과 책임이 중요하다
설계를 유연하게 만들기 위해서는 협력에 참여하는 객체가 다른 객체에게 어떤 메시지를 전송하는지가 중요하다