스프링 프레임워크(Spring Framework) 톺아보기 - 템플릿 메서드 패턴과 전략 패턴, 그리고 콜백 패턴

Janek·2023년 2월 13일
0

Spring 톺아보기

목록 보기
7/10
post-thumbnail

해당 포스팅은 인프런에서 제공하는 김영한 님의 '스프링 핵심원리 고급편'을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.

핵심 기능과 부가 기능

핵심 기능은 해당 객체가 제공하는 고유의 기능을 말하며, 부가 기능핵심 기능을 보조하기 위해 제공되는 기능을 말한다. 필요에 따라 부가 기능이 늘어나게 되면서 객체 내에 핵심 기능 제공을 위한 코드보다 부가 기능을 위한 코드가 많아지는 경우가 생기게 된다. 또한 여러 객체에서 비슷한 부가 기능을 위한 중복되는 코드가 많아지면서 리소스를 낭비하고, 유지/보수가 어렵게 만든다.

그렇기에 핵심 기능과 부가 기능을, 변하는 것과 변하지 않는 것을 분리하는 것이 좋은 객체 지향 설계이지만, 막상 여러 기능이 혼재한 상황에서 단순하게 기능별로 메서드를 추출하는 것은 어려울 때가 많다.

이 때 변하는 부가 기능과 변하지 않는 핵심 기능을 분리하여 모듈화하여 이러한 문제를 해결 하기 위해 사용되는 디자인 패턴이 템플릿 메서드 패턴(Template Method Pattern)이다.

템플릿 메서드 패턴(Template Method Pattern)


템플릿 메서드 패턴은 이름 그대로 변하지 않는 부분을 템플릿이라는 틀에 몰아두고, 일부 변하는 부분을 별도로 호출해서 해결하는 방식을 이야기한다.

public abstract class AbstractClass {
	
    public void templateMethod() {
    	// 변하지 않는 기능
       ...
       // 변하는 기능
    	primitiveOperation1();
    	primitiveOperation2();
    }
    
    protected abstract primitiveOperation1();
    
    protected abstract primitiveOperation2();
    
}

public class ConcreteClass extends AbstractClass {
	
    @Override
    protected void primitiveOperation1() {
    	// 변하는 기능
    }
    
    @Override
    protected void primitiveOperation2() {
    	// 변하는 기능
    }
    
}

위의 코드는 처음 그림을 코드로 작성한 예제이다. 변하지 않는 기능을 부모 클래스에 몰아 넣고 하나의 템플릿으로 만든다. 그리고 변하는 부분을 템플릿 안에서 호출하도록 처리한 뒤 자식 클래스에서 상속오버라이딩을 통해 구현한다.

즉 클라이언트에서 템플릿 메서드를 호출하면 템플릿을 포함한 부모 클래스가 그 구현체인 자식 클래스를 호출하여 오버라이딩 된 부가 기능 메서드를 호출하는 것이다.

템플릿 메서드 패턴의 단점과 해결

템플릿 메서드 패턴은 부가 기능을 위해 자식 객체를 계속 만들어야 한다는 단점이 존재한다. 그러나 아래 예제와 같이 익명 내부 클래스를 사용하여 이러한 단점을 해결할 수 있다.

AbstractClass template = new AbstractClass() {
	@Override
    protected void primitiveOperation1() {
    	...
    }
    
	@Override
    protected void primitiveOperation2() {
    	...
    }
}

template.templateMethod();

템플릿 메서드 패턴은 부모 클래스에서 알고리즘의 골격인 템플릿을 정의하고 변경되는 로직을 자식 클래스에서 정의하는 방식이다. 다형성을 통해 자식 클래스가 알고리즘 전체 구조를 변경하지 않은채 특정 부분만을 재정의함으로 단일 책임 원칙을 지킬 수 있게 해준다.

그러나 상속을 이용한다는 점에서 부모 클래스와 자식 클래스가 컴파일 시점에 강하게 결합되며 부모 클래스의 기능을 전혀 사용하지 않음에도 부모 클래스를 강하게 의존하게 된다. 그렇기에 부모 클래스의 기능 수정이 자식 클래스에도 영향을 끼칠 수 있다. 이러한 문제점들이 존재하기에 이를 개선한 디자인 패턴이 등장했으며, 바로 전략 패턴(Stragy Pattern)이다.

전략 패턴(Strategy Pattern)

템플릿 메서드 패턴의 단점들은 대부분 부모 클래스를 상속해서 사용한다는데서 온다. 이러한 단점을 극복하기 위해서는 변하는 부분을 상속이 아닌 다른 방법으로 해결해야 하는데, 전략 패턴은 이를 위임으로 해결한다.

전략 패턴은 변하지 않는 부분을 Context에 두고, 변하는 부분을 Stratgy라는 인터페이스를 만들고 이를 구현하도록 해서 문제를 해결한다.

위의 예제를 코드로 구현해보면 아래와 같다.

public interface Strategy {
	void execute();
}

public class StrategyA implements Strategy {
	@Override
    public void call() {
    	//...
    }
}

public class StrategyB implements Strategy {
	@Override
    public void call() {
    	//...
    }
}

public class SomeClass {	// Context
	
    private Strategy strategy;
    
    public SomeClass(strategy) {
    	this.strategy = strategy;
    }
    
    public void execute() {
    	//...
        strategy.
    }
}

전략 패턴의 핵심은 ContextStrategy 인터페이스에만 의존한다는 점이다. Strategy의 구현체를 변경하거나 새로 만들지라도 Context에는 영향을 주지 않는다. 스프링에서 사용되는 의존관계 주입 방식이 바로 전략 패턴이다.

선조립 후 실행

전략 패턴은 Context 내부 필드에 Strategy를 두고 사용한다. 이 방식을 통해 ContextStrategy를 실행 전에 원하는 모양으로 조립해두고, 실행 시점에 Context만 실행하면 된다.

스프링도 이처럼 어플리케이션 로딩 시점에 의존관계 주입을 통해 필요한 의존관계를 모두 맺어두고 실제 요청을 처리한다.

콜백 패턴(Callback Pattern)

전략 패턴의 단점은 ContextStrategy를 조립한 이후에 전략을 변경하기가 번거롭다는 점이다. 특히 Context를 싱글톤으로 사용할 때 동시성 이슈 등 고려할 점이 많다.

이를 좀 더 유연하게 만들기 위해서는 Strategy를 실행 시점에 직접 파라미터로 전달해서 사용하면 된다. 그리고 이러한 방법을 콜백 패턴이라고 한다.

콜백

프로그래밍에서 콜백(callback) 또는 콜 애프터 함수(call-after-function)는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다. 콜백을 넘겨받는 코드는 이 콜백을 즉시/나중에 실행할 수 있다.

자바에서 콜백은 보통 하나의 메서드를 가진 인터페이스를 구현하고, 이를 익명 내부 클래스로 구현하여 사용했다. 자바8 이후로는 주로 람다를 사용한다.

템플릿 콜백 패턴

스프링에서 Strategy를 파라미터로 받는 전략 패턴을 템플릿 콜백 패턴이라고 한다. Context가 템플릿 역할을, Strategy 부분이 콜백으로 넘어온다. 스프링에서 이름에 ...Template가 붙어 있다면 보통 템플릿 콜백 패턴으로 만들어져 있다고 볼 수 있다.

profile
만들고 나누며, 세상을 이롭게 하고 싶습니다.

0개의 댓글