9장 정리

이름을 가진 설계 원칙을 통해 기법들을 정리한다.

1. 개방-폐쇄 원칙

확장에 대해 열려 있고, 수정에 대해 닫혀 있는 설계
유연한 설계란 기존의 코드를 수정하지 않고도 애플리케이션의 동작을 확장할 수 있는 설계이다.

컴파일 타임 의존성을 고정시키고 런타임 의존성을 변경하라

의존성 관점에서 OCP를 따르는 설계란 컴파일 타임 의존성은 유지하면서 런타임 의존성을 확장하고 수정할 수 있는 구조이다.

추상화가 핵심이다

OCP의 핵심은 추상화에 의존하는 것이다.
생략되지 않고 남겨지는 부분은 다양한 상황에서의 공통점을 반영한 추상화의 결과물이다.
공통적인 부분은 문맥이 바뀌더라도 변하지 않아야 한다. 즉, 수정할 필요가 없어야 한다.
추상화 부분은 수정에 닫혀 있고, 추상화 과정 속에서 생략된 부분은 확장의 여지를 남긴다.
이를 통해 개방-폐쇄 원칙을 가능하게 만든다.
하지만 OCP가 가능한 설계는 공짜로 얻어지지 않는다.
추상화가 수정에 대해 닫혀 있을 수 있는 이유는 변경되지 않을 부분을 신중하게 결정하고 올바른 추상화를 했기 때문이다.
변경에 의한 파급효과를 최대한 피하기 위해 변하는 것과 변하지 않는 것이 무엇인지 이해하고, 이를 추상화의 목적으로 삼아야한다.

2. 생성 사용 분리

결합도가 높아질수록 OCP를 따르는 구조를 설계하기 어려워진다.
특히 객체 생성에 대한 지식은 과도한 결합도를 초래하는 경향이 있다.
객체 생성을 피할 수는 없고, 어딘가에서는 반드시 객체를 생성해야 한다.
동일한 클래스 안에서 객체 생성과 사용이라는 두 가지를 모두 하기 때문에 문제가 생긴다.
유연하고 재사용 가능한 설계를 원한다면 객체를 **생성**하는 책임과 객체를 **사용**하는 책임을 분리해야 한다.

FACTORY 추가하기

생성의 책임을 Client로 옮겼을 때 Client는 특정 컨텍스트에 묶이게 된다.
만약 Client가 특정한 컨텍스트에 묶이지 않기를 원한다면 Client도 생성과 사용의 책임을 분리한다.
이때 생성과 사용을 분리하기 위해 객체 생성에 특화된 객체를 사용하는데, 이를 FACTORY라고 부른다.

순수한 가공물에게 책임 할당하기

객체지향 애플리케이션은 도메인 개념뿐만 아니라 설계자들이 임의적으로 창조한 인공적인 추상화들을 포함하고 있다.
오히려 인공적으로 창조한 객체들이 도메인 개념을 반영하는 객체들보다 더 많은 비중을 차지하는 것이 일반적이다.
먼저 도메인의 본질적인 개념을 표현하는 추상화를 이용해 애플리케이션을 구축하고, 도메인 개념이 만족스럽지 못하다면 주저하지 말고 인공적인 객체를 창조하라.
객체지향이 실세계를 모방해야 한다는 주장에 현혹될 필요가 없다.

3. 의존성 주입

생성과 사용을 분리할 때, 사용의 책임을 지는 객체의 의존성은 어떡해야할까?
이 때는 사용의 책임을 지는 객체에 외부에서 생성된 인스턴스를 전달하는 방법으로 의존성을 해결해야 한다.
이를 의존성 주입(Dependency Injection)이라고 부른다.
의존성 주입의 방법에는 아래 세 가지 방법이 있다.

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

setter 주입은 런타임 시점에 의존성을 변경할 수 있지만, 객체가 올바로 생성되기 위해 어떤 의존성이 필수적인지를 명시적으로 표현할 수 없다는 단점이 있다. 
따라서 객체 생성 후 setter 메서드로 의존성 주입을 누락하면 비정상적인 상태의 객체가 생성된다.

숨겨진 의존성은 나쁘다

의존성 주입 외에도 의존성을 해결할 수 있는 다양한 방법이 존재한다.
하지만 의존성을 구현 내부로 감추는것을 주의해야 한다.
의존성을 구현 내부로 감출 경우 의존성과 관련된 문제는 컴파일 타임이 아닌 런타임에서야 발견할 수 있다.
숨겨진 의존성은 코드의 내부 구현을 이해할 것을 강요하므로 캡슐화를 위반하게 된다.
가능하다면 의존성을 명시적으로 표현할 수 있는 기법을 사용하라.

4. 의존성 역전 원칙

어떤 협력에서 중요한 정책이나 의사결정, 비즈니스의 본질을 담고 있는 것은 상위 수준의 클래스다.
이런 상위 수준의 클래스가 하위 수준의 클래스에 의존한다면 하위 수준의 변경에 의해 상위 수준 클래스가 영향을 받게 된다.
따라서 상위 수준의 클래스는 어떤 식으로든 하위 수준의 클래스에 의존해서는 안 된다.
요약하자면 추상화는 구체적인 사항에 의존해서는 안된다. 구체적인 사항은 추상화에 의존해야 한다.


5. 유연성에 대한 조언

유연한 설계는 유연성이 필요할 때만 옳다

유연하고 재사용 가능한 설계란 런타임 의존성과 컴파일 타임 의존성의 차이를 인식하고 
동일한 컴파일 타임 의존성으로부터 다양한 런타임 의존성을 만들 수 있는 코드 구조를 가지를 설계이다.
하지만 유연하고 재사용 가능한 설계가 항상 좋은 것은 아니다.
변경하기 쉽고 확장하기 쉬운 구조를 만들기 위해서는 단순함과 명확함의 미덕을 버려야할 가능성이 높다.
유연성은 항상 복잡성을 수반하고, 유연하지 않은 설계는 단순하고 명확하다.
복잡성이 필요한 이유와 합리적인 근거가 있을 때 유연하고 재사용 가능한 설계를 해야한다.

협력과 책임이 중요하다

설계를 유연하게 만들기 위해서는 협력에 참여하는 객체가 다른 객체에게 어떤 메시지를 전송하는지가 중요하다.
초보자가 자주 저지르는 실수 중 하나는 객체의 역할과 책임이 자리를 잡기 전에 너무 성급하게 객체 생성에 집중하는 것이다.
의존성을 관리해야 하는 이유는 역할, 책임, 협력의 관점에서 설계가 유연하고 재사용 가능해야 하기 때문이다.
따라서 역할, 책임, 협력에 먼저 집중하여 먼저 다양한 컨텍스트에서 협력을 재사용할 필요가 있는지 판단해야 한다.


인상깊었던 점

"유연한 설계는 유연성이 필요할 때만 옳다"
이번 장에서 이 표현이 가장 와닿았다.
얼마 전 객체지향적으로 로그 분석기를 만들 때 오랜 시간 고민하고, 신경을 많이 썼던 부분이었어서 더 와닿았던것 같다.
모든 코드를 유연하게 설계하려면 구현체가 하나 뿐인 클래스라도 인터페이스를 만들어야 하는 등의 불필요한 작업이 많아질 수 있다.
YAGNI 라는 말도 있듯 과한 설계는 결국 한 번도 쓰지 않을 수 있고, 고스란히 시간을 날리는 셈이 될 수 있다는걸 생각해봐야한다.
어떤 부분에서 유연한 설계가 필요하고, 어떤 부분에서 단순함이 필요한지 판단하는 능력이 중요한것 같다.

'공부방 > 북스터디' 카테고리의 다른 글

[오브젝트] 11장  (0) 2024.03.18
[오브젝트] 10장  (0) 2024.03.04
[오브젝트] 8장  (0) 2024.02.06
[오브젝트] 7장  (0) 2024.01.29
[오브젝트] 6장  (0) 2024.01.23

+ Recent posts