인프런 김영한 강사님 강의를 듣고 정리한 내용입니다.
여러 개의 메서드에서 공통된 부분이 발생될 때 그 부분을 메서드로 뽑아낸다. 하지만 중간에 비즈니스 로직이 끼어있고 시작과 끝부분이 같다면 이를 메서드로 뽑아내기 곤란하다. 이때 사용할 수 있는 여러개의 디자인 패턴에 대해서 소개한다.
이 디자인 패턴들의 공통된 특징은 메서드를 핵심 기능(중간)과 부가 기능(시작과 끝)으로 분리한다는 것이다. 그리고 여러개의 메서드에서 중복되어 발생하는 부가 기능을 위한 메서드를 만든다.
웹에서 헤더와 메인 그리고 푸터로 분리하는 것과 비슷하다.
핵심 기능은 메인과 비슷하고, 부가 기능은 헤더, 푸터와 비슷하다.
해당 객체가 제공하는 고유의 기능
핵심 기능을 보조하기 위해 제공하는 기능이다.
좋은 설계를 위한 가장 중요한 관점은 변하는 것과 변하지 않는 것을 분리하는 것이다. 메서드마다 핵심 기능은 변할 수 있다. 하지만 부가 기능은 변하지 않는다.
템플릿 메서드 패턴은 템플릿을 사용하는 방식이다. 템플릿 클래스 내부에 부가 기능과 관련된 메서드가 있고, 그 메서드 내부에는 핵심 기능에 대한 메서드가 실행된다.
핵심 기능에 관한 메서드는 추상 메서드로 오버라이딩해야한다.
템플릿 클래스의 부가 기능을 사용하는 로직들은 템플릿 클래스를 상속하여 핵심 기능을 오버라이딩한다.
그림에서 하나의 AbstractTemplate 내부에 두 메서드가 존재한다. execute() 메서드에는 부가 기능과 관련된 내용이 구현되어있고, call() 메서드는 추상 메서드이다.
그리고 SubClassLogic1과 SubClassLogic2는 AbstractTemplate의 부가 기능을 사용하기 위해 해당 템플릿을 상속하고 있다. 각각의 클래스 내부에서는 call() 메서드를 오버라이딩하여 각각의 핵심 기능을 구현한다.
execute() 메서드의 실행은 각각의 상속받은 구현체에서 하게 된다.
상속을 사용하기 때문에 상속의 단점을 모두 안고 간다. 자식 클래스가 부모 클래스와 컴파일 시점에 강하게 결합되는 문제가 있다. 자식 클래스가 부모 클래스를 강하게 의존하고 있지만, 자식 클래스는 부모 클래스의 기능을 전혀 사용하지 않고 있다.
이러한 설계의 문제점은 부모 클래스를 수정할 때 자식 클래스에도 영향을 줄 수 있다는 것이다.
전략 패턴은 템플릿 메서드의 단점인 상속을 제거하지만 비슷한 역할을 수행한다. 전략 패턴에서는 상속 대신 위임을 사용한다.
역시 핵심 기능과 부가 기능으로 분리한다.
핵심 기능을 위해서는 인터페이스를 생성하고, 부가 기능을 위해서는 클래스를 만든다.
이 때 부가 기능을 위한 부분을 Context라고 부르고, 핵심 기능을 위한 부분을 Strategy라고 부른다.
Context에서는 내부에 Strategy를 주입받는다.
그리고 주입 받은 Strategy의 핵심 기능 메서드를 Context의 부가 기능을 위한 메서드 내부에서 실행하게 된다.
템플릿 메서드 패턴과 달리 execute() 메서드의 실행은 Context에서 하게 된다.
전략 패턴의 핵심은 Context는 Strategy 인터페이스에만 의존한다는 점이다. 덕분에 Strategy의 구현체를 변경하거나 새로 만들어도 Context 코드에는 영향을 주지 않는다.
- 필드 주입
Context 클래스 내부에 Strategy 필드를 만들어서 생성자, setter 등으로 주입 받는다.
조립 후에 실행하는 방식으로 싱글톤 Context를 사용하면 동시성 문제 등으로 Strategy의 실시간 변경이 어렵다.- 매개 변수 주입
execute() 메서드 실행 시에 매개 변수로 Strategy를 주입 받는다.
매개 변수 주입 방식이 실행 시점에 유연하게 전략을 변경할 수 있기 때문에 더 적합하다.
GoF 패턴은 아니지만 스프링에서 자주 사용되는 방식이다.
전략 패턴에서 매개 변수로로 전략을 주입하는 방식을 템플릿과 콜백을 강조하여 템플릿 콜백 패턴이라고 부른다.
전략 패턴과의 용어 차이는 다음과 같다.
프로그래밍에서 콜백 또는 콜애프터 함수는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다. 콜백을 넘겨받는 코드는 이 콜백을 필요에 따라 즉시 실행할 수도 있고, 아니면 나중에 실행할 수도 있다.
콜백은 코드가 호출은 되지만, 코드를 넘겨준 곳의 뒤에서 실행되는 것이다.
단순히 코드의 길이를 줄일 뿐만 아니라 좋은 설계를 할 수 있다. 좋은 설계는 변경이 일어날 때 자연스럽게 드러난다. 만약 부가 기능과 핵심 기능을 분리하지 않았다면, 부가 기능과 관련된 코드의 수정이 일어날 때 문제가 발생한다. 템플릿이 없다면 각각의 코드를 일일이 수정해야한다.
이는 단일 책임의 원칙과 관련이 있다.
객체지향 SOLID 5원칙 중 하나이다.
하나의 코드는 하나의 책임만을 져야한다는 원칙이다. 중복 제거 시에 역할이 분리되고, 변경 지점을 하나로 모아서 변경에 쉽게 대처할 수 있게 된다.