Java를 공부하다 보니 상속을 통한 확장성에 대한 얘기를 많이 접해볼 수 있었다. 그중에서도 지금 공부하고 있는 스프링 프레임워크에서 많이 사용하고 있는 디자인 패턴인 "템플릿 메서드 패턴"에 대해서 짧게 적어보려고 한다.
먼저 디자인 패턴이란 무엇인가? 소프트웨어 설계를 할 때 자주 만나는 문제들이 존재한다. 반복되는 문제들을 해결할 수 있는 해결방안이 정해져 있으면 좋지 않을까? 그래서 있는 존재하는 것이 디자인 패턴입니다.
즉, 디자인 패턴이란 소프트웨어 설계 시 특정 상황에서 자주 만나게 되는 문제를 해결하기 위해 사용할 수 있는 재사용이 가능한 솔루션을 의미한다.
이제 템플릿 메서드 패턴에 대해서 알아봅시다. 템플릿 메서드의 특징은 다음과 같습니다.
1. 추상 메서드, 구현된 메서드를 이용하여 코드의 흐름을 정의하는 메소드
2. 메서드를 final로 선언하여 하위 클래스에서 재정의할 수 없게 함
3. 프레임워크에서 많이 사용되는 설계 패턴
4. 추상 클래스로 선언되어 있는 상위 클래스에서 템플릿 메서드를 사용하여 전체적인 흐름을 정의하고, 하위 클래스에서 다르게 구현되어야 할 부분은 추상 메서드로 선언하여 하위 클래스에서 정의하도록 함
쉽게 풀어보면 상위 클래스에서 실행되어야 할 코드의 흐름(시나리오)을 정의하고 있는 final 메서드를 선언합니다. 이 메서드를 템플릿 메서드라고 합니다.
그리고 해당 추상 클래스를 상속받은 하위 클래스에서는 상위 클래스의 추상 메서드를 오버라이딩하여 정의하여 사용합니다. 하위 클래스는 상위 클래스의 템플릿 메서드를 재정의할 수 없습니다. 왜냐하면 final 키워드가 붙어있기 때문입니다.
코드로 된 예시를 하나 살펴보면서 설명해 보겠습니다.
TV를 예시로 들어봅시다. TV는 다음과 같은 고정된 시나리오로 코드가 실행된다고 해봅시다.
TV를 켜고 -> (TV가 추천 채널을 찾는다) -> TV가 채널을 선택 -> 해당 채널이 재생 -> TV가 꺼짐
추천 채널을 찾는 부분은 ()를 쳐놓은 이유는 구현을 해도 되고, 안 해도 되는 메서드입니다. 그럼 이 메서드는 추상 메서드인가? 아닙니다. 추상 메서드가 아닌 구현이 되어있지만 비어있는 메서드입니다. 이런 메서드를 훅 메서드라고 합니다.
이제 TV를 상속받는 SmartTV와 ManualTV를 구현해 봅시다.
# 추상 클래스인 TV를 생성
TV는 코드 흐름을 가지고 있는 run()이라는 템플릿 메서드를 가지고 있습니다.
public abstract class TV {
public abstract void turnOn();
public abstract void turnOff();
public abstract void selectChannel();
// 훅 메서드
public void findChannel() {};
public void display() {
System.out.println("채널이 재생됩니다.");
};
// 템플릿 메서드
public final void run() {
turnOn(); // TV가 켜지고
findChannel(); // 채널을 찾고
selectChannel(); // 채널을 선택하고
display(); // 해당 채널이 재생된다.
turnOff(); // TV를 끈다.
}
}
템플릿 메서드인 run()을 보면 final로 선언되어 있고, 코드의 흐름이 정의되어 있습니다. 여기서 훅 메서드인 findChannel()을 확인해 보면 추상 메서드가 아니고, 구현되어 있는 메서드입니다. 하지만 내용이 비어있습니다.
즉, 하위 클래스에서 재정의해주면 이 기능을 사용할 수 있고, 재정의해주지 않으면 해당 기능은 그냥 아무것도 실행되지 않고 지나갑니다.
# SmartTV 클래스를 생성
스마트 티비는 자동이라는 기능으로 모든 기능을 정의할 것이고, ManualTV와는 다르게 훅 메서드인 findChannel()도 재정의해서 사용할 것입니다.
public class SmartTV extends TV {
@Override
public void turnOn() {
System.out.println("Smart TV가 자동으로 켜집니다.");
}
@Override
public void turnOff() {
System.out.println("Smart TV가 자동으로 꺼집니다.");
}
@Override
public void findChannel() {
System.out.println("Smart TV가 추천 채널을 탐색합니다.");
}
@Override
public void selectChannel() {
System.out.println("Smart TV가 추천 채널을 자동으로 선택합니다.");
}
}
# ManualTV 클래스 생성
매뉴얼 티비는 수동으로 작동하기 때문에 리모컨으로 작동되는 내용으로 모든 기능을 정의할 것이고, 자동으로 채널을 찾는 기능인 findChannel()은 구현하지 않을 것입니다.
public class ManualTV extends TV {
@Override
public void turnOn() {
System.out.println("리모컨으로 TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("리모컨으로 TV를 끕니다.");
}
@Override
public void selectChannel() {
System.out.println("리모컨으로 보고싶은 채널을 선택합니다.");
}
}
# 각 TV에 있는 템플릿 메서드를 실행
SmartTV와 ManualTV의 각 run() 메서드를 실행시켜 봅니다.
public class TVTest {
public static void main(String[] args) {
TV smartTV = new SmartTV();
TV manualTV = new ManualTV();
System.out.println("스마트 티비를 실행");
smartTV.run();
System.out.println("매뉴얼 티비를 실행");
manualTV.run();
}
}
출력 결과는 아래와 같습니다.
스마트 티비를 실행
Smart TV가 자동으로 켜집니다.
Smart TV가 추천 채널을 탐색합니다.
Smart TV가 추천 채널을 자동으로 선택합니다.
채널이 재생됩니다.
Smart TV가 자동으로 꺼집니다.
매뉴얼 티비를 실행
리모컨으로 TV를 켭니다.
리모컨으로 보고싶은 채널을 선택합니다.
채널이 재생됩니다.
리모컨으로 TV를 끕니다.
스마트 티비에서는 훅 메서드인 findChannel()이 작동되었고, 매뉴얼 티비에서는 작동되지 않은 결과를 볼 수 있습니다.
그리고 2개의 TV는 템플릿 메서드로 정해진 코드 흐름(시나리오)대로 실행됩니다. 이 템플릿 메서드는 final 메서드이기 때문에 SmartTV, ManualTV에서 재정의할 수 없습니다.
여기서 final에 대해서 간단하게 확인하고 넘어가 봅시다.
# final이 변수에 붙었을 때
private static final int ZERO = 0;
private static final int ONE = 1;
변수에 final이 붙은 경우에는 상수로서 사용됩니다. 상수는 값을 변경할 수 없습니다.
우리가 자주 사용하는 상수에는 Integer 클래스의 MAX_VALUE가 있습니다.
# fina이 메서드에 붙었을 때
public final void templateMethod() {
// 코드 구현
}
메서드에 final이 붙은 경우에는 해당 메서드는 재정의될 수 없습니다. 위에서 살펴봤던 템플릿 메서드가 이 경우에 해당합니다.
# final이 클래스에 붙었을 때
public final class TV {
// 코드 구현
}
클래스에 final이 붙은 경우에는 해당 클래스는 상속할 수 없습니다. 즉, extends TV가 불가능합니다.
'🧑🏻💻 Dev > Java' 카테고리의 다른 글
[Java] 입력을 받는 BufferedReader와 Scanner (0) | 2023.02.02 |
---|---|
[디자인 패턴] 싱글톤 패턴 (0) | 2023.01.29 |
[자바 자료구조] 스택(Stack)과 큐(Queue) (0) | 2023.01.17 |
[자바 알고리즘] Greedy Algorithm (그리디 알고리즘) (0) | 2023.01.17 |
[자바 알고리즘] DFS와 BFS (0) | 2023.01.17 |