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