////
Search

9장 - 유연한 설계

Date
2024/03/18 14:43
Tags

1. 개방-폐쇄 원칙

소프트웨어 개체는 확장에 열려 있어야 하고, 수정에는 닫혀 있어야 한다.
확장과 수정은 애플리케이션의 동작과 코드 관전에 반영할 수 있다.
확장에 열려 있다 : 애플리케이션 요구 사항이 변경에 맞게 새로운 동작을 추가해 기능을 확장할 수 있다.
수정에 닫혀 있다 : 기존의 코드를 수정하지 않고도 애플리케이션의 동작을 추가/변경할 수 있다.

1.1. 추상화가 핵심이다

개방-폐쇄 원칙(이하 OCP)의 핵심은 추상화에 의존하는 것이다.
추상화와 의존 두 개념 모두 중요하다.
추상화 = 핵심만 남기고 불필요한 부분은 생략해 복잡성을 극복하는 방법
이를 사용하면 문맥이 바뀌어도 변하지 않는 부분만 남게되어 변하는 부분은 생략됨
OCP의 관점에서 남겨지는 부분은 다양한 상황에서 공통점을 반영한 추상화의 결과물이라 말할 수 있음
다시말해 수정할 필요가 없어야 한다.
생략된 부분은 확장의 여지를 남긴다.
주의점으로 추상화 했다고 모든 수정에 대해 설계가 폐쇄되는 것은 아니기 때문에 파급효과를 줄이기 위해 변하는것/변하지 않는 것을 신중하게 결정해야 한다.

2. 생성 사용 분리

결합도가 높아질수록 OCP를 따르는 구조를 설계하기가 어려워진다.
객체 생성에 대한 지식은 과도한 결합도를 초래하는 경향이 있다.
컨텍스트를 바꾸기 위한 유일한 방법은 코드 안에 명시돼 있는 컨텍스트에 대한 정보를 직접 수정하는 것뿐이다.
물론 객체 생성은 피할 수 없고 어디선가 반드시 생성해야 한다.
문제는 생성이 아니라 위치의 문제다.
즉, 객체의 생성과 사용의 분리를 해야한다.

2.1. Factory 추가하기

팩토리 객체 = 객체 생성과 관련된 책임만 전담하는 별도의 객체

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

팩토리 객체는 순수하게 기술적인 결정으로 도메인 모델에 속하지 않는다.
객체를 분해하는 두 가지 방법
표현적 분해 = 도메인에 존재하는 사물/개념을 표현하는 객체들을 이용해 시스템을 분해
객체지향 설계를 위한 가장 기본적인 접근법
행위적 분해
모든 책임을 도메인 객체에게 할당하면 낮은 응집도/높은 결합도/재사용성 저하와 같은 심각한 문제점에 봉착할 가능성이 높아짐
이 경우 도메인 개념을 표현하는 객체가 아닌 설계자가 편의를 위해 임의로 만들어낸 가공의 객체에게 책임을 할당한다.
책임을 할당하기 위해 창조되는 도메인과 무관한 인공적 객체를 PURE FABRICATION(순수한 가공물)이라 부른다.
어떤 행동이 도메인에 책임질 마땅한 객체가 없다면 순수한 가공물에게 할당하자
이는 표면적 분해보다는 행동적 분해에 의해 생성되는게 일반적이다.

3. 의존성 주입

생성과 사용을 분리하면 사용하는 객체는 외부에서 의존성을 주입받아야 한다.
DIP = 외부의 객체가 인스턴스를 생성해 전달하는 방법
의존성 주입의 3가지 방법
생성자 주입 = 객체 생성 시점에 생성자를 통해 전달
setter 주입 = 객체 생성 후 setter를 통해 전달
단, 객체 전달 전 객체를 사용하려 하면 비정상 동작의 가능성이 있다.
메서드 주입 = 메서드 실행 시점에 인자를 통한 전달

3.1. 숨겨진 의존성은 나쁘다

의존성 주입 외 의존성을 해결하는 방법 중 하나로 service locator 패턴이다.
의존성을 해결할 객체들을 보관하는 일정의 저장소로 객체가 직접 의존성을 요청하는 방식이다.
주입과의 차이점은 받는냐 요청하느냐 주체가 누구인가의 차이이다.
단, 해당 패턴은 의존성을 감추기 때문에 디버깅을 시작할 시점에 원인을 파악하게될 가능성이 높아진다.
즉, 애플리케이션의 신뢰도 하락으로 이어지게 된다.
또한 단위테스트도 어렵게 만든다.

4. 의존성 역전 원칙

4.1. 추상화와 의존성 역전

추상화가 아닌 클래스에 의존하게 된다면 결합도가 높아지고 재사용성과 유연성이 저해된다.
상위 클래스가 하위 클래스에 의존하게 될 경우 변경에 취약해진다.
객체 사이의 협력이 존재할 때 그 협력의 본질을 담고 있는것은 상위 클래스로 비즈니스의 본질을 담고있다.
때문에 어떤 경로로든 상위 클래스가 하위 클래스의 영향을 받아서는 안된다.
상위 클래스의 변경에 의해 하위 수준이 변경되는 것은 납득할 수 있지만 하위 수준의 변경으로 인해 상위 수준이 변경돼서는 곤란다하다.
때문에 상위 클래스는 하위 클래스를 직접 의존하지 말고 추상화에 의존하도록 하자.
추상화에 의존하면 하위 클래스의 변경 영향이 상위 클래스로 전이되지 않는다.
추상화는 유연하고 재사용 가능한 설계를 가능케한다.
구체 클래스는 의존성의 시작점이 되어야 하며 목적이여서는 안된다.
의존성 역전 원칙 (DIP)
상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 된다. 둘 모두 추상화에 의존해야 한다.
추상화는 구체적인 사항에 의존해서는 안 된다. 구체적인 사항은 추상화에 의존해야 한다.
DIP는 전통적인 절차적 방법에 의해 일반적으로 만들어진 의존성 구조에 대한 역전을 뜻한다.

4.2. 의존성 역전 원칙과 패키지

의존성 역전 원칙은 의존성의 방향뿐 아니라 인터페이스의 소유권에도 적용된다.
해당 구조가 추상화에 의존하며 개방-폐쇄 원칙을 지키고 의존성 역전 원칙을 지키기 때문에 유연하고 재사용 가능한 구조라고 생각하기 쉽다.
But. Movie가 DiscountPolicy에 의존하고 있기 때문에 Movie배포를 위해 불필요한 패키지까지 함께 배포돼야 한다.
만약 DiscountPolicy의 구현체가 수정될 경우 Movie클래스의 패키지까지 함께 빌드되어야 하기 때문에 빌드 시간이 가파르게 증가한다.
때문에 추상화 패키지를 별도로 분리시켜 클라이언트가 속한 패키지를 독립적으로 만들어야 한다.
이를 SEPARATED INTERFACE 패턴이라 부른다.
의존성 역전 원칙에 따라 상위 수준의 협력 흐름을 재사용하기 위해서는 추상화가 제공하는 인터페이스의 소유권 역시 역전시켜야 한다.
이는 객체지향 프레임워크의 모듈 구조를 설계하는 데 가장 중요한 핵심 원칙이다.
유연하고 재사용 가능하며 컨텍스트에 독립적인 설계는 전통적인 패러다임이 고수하는 의존성의 방향을 역전시킨다.
훌륭한 객체지향 설계를 위해서는 의존성을 역전시켜야 하며 이를 통해 재사용/유연한 설계를 얻을 수 있다.

4.3. 협력과 책임이 중요하다.

지금까지 클래스를 중심으로 구현 메커니즘 관점에서 의존성을 설명했지만 설계를 유연하게 만들기 위해 협력에 참여하는 객체가 다른 객체ㅔ게 어떤 메시지를 전송하는지가 중요하다.
설계를 유연하게 하기 위해선 역할/책임/협력에 초점을 맞춰야한다.
다양한 컨텍스트에서 협력을 재사용할 필요가 없다면 설꼐를 유연하게 만들 당위성도 함께 사라진다.
초보자가 자주하는 실수로는 객체의 책임과 역할리 자리 잡기전 성급하게 색체 생성에 집중하는 것으로 이리되면 객체 생성과 관련된 불필요한 세부사항에 객체를 결합시킨다.
책임의 불균형이 심화되고 있는 상태에서 생성 책임을 지우는 것은 설계를 하부의 특정한 메커니즘에 종속적으로 만들 확률이 높다.
객채 생성방법의 결정은 모든 책임이 자리를 잡은 후 가장 마지막 시점에 내리는 것이 적절하다.