728x90
반응형
01. 개방-폐쇄 원칙
소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.
여기서 키워드는 '확장'과 '수정'이다. 이 둘은 순서대로 애플리케이션의 '동작'과 '코드'의 관점을 반영
- 확장에 대해 열려 있다: 애플리케이션의 요구사항이 변경될 때 이 변경에 맞게 새로운 '동작'을 추가해서 애플리케이션의 기능을 확장할 수 있다.
- 수정에 대해 닫혀 있다. 기존의 '코드'를 수정하지 않고도 애플리케이션의 동작을 추가하거나 변경할 수 있다.
컴파일 의존성을 고정시키고 런타임 의존성을 변경하라
의존성 관점에서 개방-폐쇄 원칙을 따르는 설계란,
컴파일타임 의존성은 유지하면서 런타임 의존성의 가능성을 확장하고 수정할 수 있는 구조를 의미
컴파일 의존성: 코드에서 드러나는 클래스들 사이의 관계
런타임 의존성: 실행시에 협력에 참여하는 객체들 사이의 관계
개방-폐쇄 원칙 예제: 8장의 OverlappedDiscountPolicy(중복 할인 정책) 추가, NoneDiscountPolicy 추가
추상화가 핵심이다
개방-폐쇄 원칙의 핵심은 추상화에 의존하는 것
추상화: 핵심적인 부분만 남겨 복잡성을 줄이는 기법
- 생략된 부분을 문맥에 적합한 내용으로 채워넣어 문맥에 적합하게 기능을 구체화하고 확장
- 예시(p285): 상속을 통해 생략된 부분을 구체화함으로써 할인 정책 확장
- 변하는 것과 변하지 않는 것이 무엇인지 이해해 설계해야 추상화가 수정에 대해 닫혀있을 수 있다.
02. 생성 사용 분리
유연하고 재사용 가능한 설계를 원한다면, 객체에 대한 생성 책임과 사용 책임을 분리해야한다.
- 사용으로 부터 생성을 분리하는 보편적인 방법: 객체 생성 책임을 클라이언트로 옮기는 것
- 어떤 것을 사용할 지는 그 시점의 클라이언트만 알기 때문
FACTORY 추가하기
- FACTORY: 객체 생성에 특화된 객체 (생성과 사용을 분리하기 위함이 목적)
- Factory를 사용해 생성된 Movie 인스턴스를 반환받아 사용
- Movie와 AmountDiscountPolicy 생성 책임 모두 factory로 이동
순수한 가공물에게 책임 할당하기
어떤 행동을 추가하려고 하는데 이 행동을 책임질 마땅한 도메인 개념이 존재하지 않는다면 PURE FABRICATION을 추가하고 이 객체에게 책임을 할당하라.
- PURE FABRICATION(순수한 가공물): 책임을 할당하기 위해 창조되는 도메인과 무관한 인공적인 객체
- 모든 책임을 도메인 객체에게 할당하면 낮은 응집도, 높은 결합도, 재사용성 저하와 같은 심각한 문제점에 봉착하게 될 가능성이 높아진다.
- Factory는 재사용성을 높이기 위해 사용하는 도메인 개념과 아무런 상관없는 가공의 객체
- 도메인이 만족스럽지 않다면 인공적인 객체를 창조해 도메인 모델을 보충
03. 의존성 주입
의존성 주입이란?
- 사용하는 객체가 아닌 외부의 독립적인 객체가 인스턴스를 생성한 후 이를 전달해 의존성을 해결하는 방법
- 외부에서 의존성의 대상을 해결한 후 이를 사용하는 객체로 주입
의존성을 해결하는 3가지 방법
- 생성자 주입(constructor injection) : 객체를 생성하는 시점에 생성자를 통한 의존성 해결
→ 객체의 생명주기 전체에 걸쳐 관계 유지 - setter 주입(setter injection) : 객체 생성 후 setter 메서드를 통한 의존성 해결
→ 장점: 언제라도 의존 대상을 런타임에 변경할 수 있음
→ 단점: 어떤 의존성이 필수적인지 명시적으로 표현할 수 없음 (예. 객체 생성 뒤 호출해야 정상적으로 생성) - 메서드 (호출) 주입(method injection) : 메서드 실행 시 인자를 이용한 의존성 해결
→ 메서드가 의존성을 필요하는 유일한 경우일 때 사용
숨겨진 의존성은 나쁘다
SERVICE LOCATOR 패턴
- 의존성을 해결할 객체들을 보관하는 저장소
- 객체가 직접 SERVICE LOCATOR에게 의존성을 해결해줄 것을 요청
- 의존성을 해결할 수 있는 가장 간단한 도구이지만 의존성을 감춘다는 단점이 있음
- 의존성을 숨기는 코드는 단위테스트 작성도 어려움
(내부적으로 정적 변수를 사용해 객체 관리 -> 단위 테스트는 서로 고립되어야 한다는 원칙 위반) - 의존성을 이해하기 위해 코드 내부 구현을 이해해야 한다는 문제도 있음 (캡슐화 위반)
즉, 명시적인 의존성이 숨겨진 의존성 보다 좋다!
04. 의존성 역전 원칙
추상화와 의존성 역전
- 상위 수준의 클래스가 하위 수준의 클래스에 의존하면, 상위 수준의 클래스를 재사용할 때 하위 수준의 클래스도 필요하기 때문에 재사용이 어려워짐
- 객체 사이의 협력이 존재할 때 그 협력의 본질을 담고 있는 것은 상위 수준의 클래스
- 해결하기 위해선 "추상화" 사용
- 상위 수준의 클래스와 하위 수준의 클래스 모두 추상화에 의존해야 한다.
- 추상화는 구체적인 사항에 의존하면 안된다. 구체적인 사항이 추상화에 의존해야 한다.
의존성 역전 원칙과 패키지
- 의존성 역전 원칙에 따라 상위 수준의 협력 흐름을 재사용하기 위해서는 추상화가 제공하는 인터페이스의 소유권 역시 역전시켜야 한다.
- 인터페이스의 소유권을 서버가 아닌 클라이언트에 위치시킨다. (p303 vs p304)
05. 유연성에 대한 조언
- 설계의 미덕은 단순함 & 명확함에서 나온다. 즉, 이런 코드가 읽기 쉽고 이해하기 편함
- but, 유연한 설계는 단순함과 명확함의 미덕을 버리게 될 가능성이 높다.
- 유연한 설계는 복잡하고 암시적
- 불필요한 유연성은 불필요한 복잡성으로 낳기 때문에, 코드를 읽는 사람들이 그 복잡성을 수용할 수 있을 때만 가치가 높다.
- 설계를 유연하게 만들기 위해서는 역할, 책임, 협력에 초점을 맞춰야한다.
- 의존성을 관리해야하는 이유는 역할/책임/협력의 관점에서 설계가 유연하고 재사용 가능해야하기 때문
728x90
반응형
'개발 > Clean Code' 카테고리의 다른 글
[오브젝트] 14장. 일관성 있는 협력 (0) | 2021.07.14 |
---|---|
[오브젝트] 10장. 상속과 코드 재사용 (0) | 2021.06.04 |
[Clean Code] 15장. JUnit 들여다보기 (0) | 2021.03.08 |
[Clean Code] 10장. 클래스 (2) | 2021.01.03 |
[Clean Code] 5장. 형식맞추기 (0) | 2020.12.01 |
댓글