티스토리 뷰

디자인 패턴

Template Method Pattern

기내식은수박바 2021. 9. 26. 12:38
반응형

템플릿 메서드 패턴?

전체적으로는 동일하면서 부분적으로 다르게 작성된 메서드의 코드 중복을 최소화할 수 있는 디자인 패턴

  • 즉, 동일한 기능을 상위 클래스에서 정의하고, 확장 또는 변화가 필요한 부분만을 서브 클래스에서 구현
  • 상위 클래스에서 동일한 기능을 구현한 클래스를 템플릿 메서드, 하위 클래스에서 세부적인 기능을 구현하도록 오버라이딩하는 메서드를 Primitive 또는 Hook 메서드라고 한다.

 

 

등장 배경

전기를 공급받고 컴퓨터를 키는 과정을 생각해보자. LG 컴퓨터를 사용한다고 했을 때, 아래와 같은 클래스 다이어그램을 정의할 수 있을 것이다.

LGComputer 클래스는 turnOn 메서드를 실행할 때, 전기가 공급되는지 확인할 필요가 있다. 아래와 같이 인터페이스로서 컴퓨터의 상태 (켜짐, 꺼짐), 전원 공급 여부 (공급, 미공급) 를 나타낸다.

  1. LG Computer 클래스의 turnOn 메서드는 getPowerStatus를 통해 전원 공급 상태를 확인한다.
    • 만약 전원이 공급되고 있다면 turnOn 메서드를 종료한다.
    • 만약 전원이 공급되고 있지 않다면 setComputerStatus를 호출해 컴퓨터의 상태를 ON으로 설정하고, executeLGProgram 메서드를 호출해 컴퓨터를 키고 프로그램을 실행한다.
public enum ComputerStatus { ON, OFF }
public enum PowerStatus { ON, OFF }

public class Power {

    private PowerStatus powerStatus;
    
    public Power() {
        this.powerStatus = PowerStatus.OFF;
    }
    
    public PowerStatus getPowerStatus() {
        return this.powerStatus;
    }
    
    public void powerOn() {
        this.powerStatus = PowerStatus.ON;
    }
    
    public void powerOff() {
    	this.powerStatus = PowerStatus.OFF;
    }
}

public class LGComputer {

    private Power power;
    private ComputerStatus computerStatus;
    
    public LGComputer(Power power) {
        this.power = power;
        this.computerStatus = ComputerStatus.OFF;
    }
    
    public void executeLGProgram() {
        // Execute LG Computer Program
    }
    
    public ComputerStatus getComputerStatus() {
        return this.computerStatus;
    }
    
    public void setComputerStatus(ComputerStatus computerStatus) {
        this.computerStatus = computerStatus;
    }
    
    public void turnOn() {
    	ComputerStatus computerStatus = getComputerStatus();
        
        if (computerStatus == ComputerStatus.ON)
            return;
            
        setComputerStatus(ComputerStatus.ON); 
        executeLGProgram();
    }
}

public class Client {
    public static class main(String[] args) {
        Power power = new Power();
        LGComputer lgComputer = new LGComputer(power);
        
        lgComputer.turnOn();
    }
}

 

 

문제점

만약 LG 컴퓨터에서 삼성 컴퓨터처럼 다른 회사의 컴퓨터로 변경되었다면 어떻게 해야할까?

  • 삼성 컴퓨터를 켜고 프로그램을 실행하는 것은 LG 컴퓨터와 완전히 동일하지 않기 때문에 일부분을 수정해야 한다.
public class SamsungComputer {

    private Power power;
    private ComputerStatus computerStatus;
    
    public SamsungComputer(Power power) {
        this.power = power;
        this.computerStatus = ComputerStatus.OFF;
    }
    
    public void executeSamsungProgram() {
        // Execute Samsung Computer Program
    }
    
    public ComputerStatus getComputerStatus() {
        return this.computerStatus;
    }
    
    public void setComputerStatus(ComputerStatus computerStatus) {
        this.computerStatus = computerStatus;
    }
    
    public void turnOn() {
    	ComputerStatus computerStatus = getComputerStatus();
        
        if (computerStatus == ComputerStatus.ON)
            return;
            
        setComputerStatus(ComputerStatus.ON); 
        executeSamsungProgram();
    }
}

그런데, LGComputer 클래스와 SamsungComputer 클래스는 여러 개의 메서드가 동일하게 작성되어 있음을 확인할 수 있다.

  • 코드 중복으로 인해 유지보수성을 악화시킬 것이고, AppleComputer, HPComputer와 같이 다른 회사의 컴퓨터를 사용할 경우에도 동일한 문제가 발생할 것이다.

 

이와 같이 2개 이상의 클래스가 유사한 기능을 제공하면서 중복된 코드가 있을 경우, 상속을 통해 이를 해결할 수 있다.

 

클래스 다이어그램

public abstract class Computer {
    
    protected Power power;
    private ComputerStatus computerStatus;
    
    public Computer(Power power) {
        this.power = power;
        this.computerStatus = ComputerStatus.OFF;
    }
    
    public ComputerStatus getComputerStatus() {
        return this.computerStatus;
    }
    
    public void setComputerStatus(ComputerStatus computerStatus) {
        this.computerStatus = computerStatus;
    }
}

public class LGComputer extends Computer {

    public LGComputer(Power power) {
        super(power);
    }
    
    private void executeLGProgram() {
        // Execute LG Computer Program
    }
    
    public void turnOn() {
    	ComputerStatus computerStatus = getComputerStatus();
        
        if (computerStatus == ComputerStatus.ON)
            return;
            
        setComputerStatus(ComputerStatus.ON); 
        executeLGProgram();
    }
}

public class SamsungComputer extends Computer {

    public SamsungComputer(Power power) {
        super(power);
    }
    
    private void executeSamsungProgram() {
        // Execute Samsung Computer Program
    }
    
    public void turnOn() {
    	ComputerStatus computerStatus = getComputerStatus();
        
        if (computerStatus == ComputerStatus.ON)
            return;
            
        setComputerStatus(ComputerStatus.ON); 
        executeSamsungProgram();
    }
}

Computer클래스를 LGComputer클래스와 SamsungComputer 클래스의 상위 클래스로 정의함으로써 아래와 같은 항목들의 중복을 피할 수 있게됐다.

  • Power클래스와의 연관 관계
  • ComputerStatus 필드
  • getComputerStatus, setComputerStatus 메서드

 

하지만 여전히 LGComputer, SamsungComputer의 turnOn 메서드에서는 코드 중복 문제가 발생하고 있다.

  • execute~Program 메서드를 제외하고는 동일하다.

 

 

해결책

turnOn메서드를 상위 클래스인 Computer 클래스에 이동시키고, executeLGProgram / executeSamsungProgram 메서드의 호출 부분을 하위 클래스에서 오버라이딩하는 방식으로 코드 중복을 최소화할 수 있다.

LGComputer / SamsungComputer 클래스 turnOn메서드에서 다른 부분은 executeProgram호출로 대체했다.

또한, executeProgram 메서드의 구현이 각 클래스 마다 달라야 하므로 추상 메서드로 정의한 후, 하위 클래스에서 적절하게 오버라이딩되도록 한다.

public abstract class Computer {
    
    protected Power power;
    private ComputerStatus computerStatus;
    
    public Computer(Power power) {
        this.power = power;
        this.computerStatus = ComputerStatus.OFF;
    }
    
    public ComputerStatus getComputerStatus() {
        return this.computerStatus;
    }
    
    public void setComputerStatus(ComputerStatus computerStatus) {
        this.computerStatus = computerStatus;
    }
    
    
    // 템플릿 메서드
    public void turnOn() {
    	ComputerStatus computerStatus = getComputerStatus();
        
        if (computerStatus == ComputerStatus.ON)
            return;
            
        setComputerStatus(ComputerStatus.ON); 
        executeProgram(); // Primitive 또는 Hook 메서드
    }
    
    protected abstract void executeProgram();
}

public class LGComputer extends Computer {

    public LGComputer(Power power) {
        super(power);
    }
    
    protected void executeProgram() {
        // Execute LG Computer Program
    }
}

public class SamsungComputer extends Computer {

    public SamsungComputer(Power power) {
        super(power);
    }
    
    protected void executeProgram() {
        // Execute Samsung Computer Program
    }
}

 

 

Reference

  • 도서 'UML과 GoF 디자인 패턴 핵심 10가지로 배우는 JAVA 객체 지향 디자인 패턴'
반응형

'디자인 패턴' 카테고리의 다른 글

Factory Method Pattern  (0) 2021.12.04
Decorator Pattern  (0) 2021.10.11
Singleton Pattern  (0) 2021.10.02
Strategy Pattern  (0) 2021.09.15
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함