본문 바로가기
디자인패턴/행위

[디자인패턴]템플릿 메소드 패턴

by se0nghyun2 2023. 3. 2.
더보기

행위패턴: 객체 간 더 나은 상호작용, 책임 분배 방법 제공하는 패턴

 향후 쉽게 확장할 수 있는 유연성에 대한 솔루션을 제공한다.

 

템플릿 메소드 패턴

우선, 디자인패턴을 배우는 이유 중 하나는 확장에 있어서 유연성을 찾아볼 수 있다.

확장은 보통 상속 및 구현을 통하여 프로그래밍하므로 상속 및 구현이 키포인트다. 

 

힌트 1. 상속 및 구현

힌트 2. 템플릿: 일정한 틀을 가진 변하지 않는 것 

 

이를 종합하여 보면, 

일정한 틀을 가진 변하지 않는 것(템플릿 메서드)를 상위클래스에서 정의하고,

상속이나 구현받을 서브클래스에서 세부 메서드를 구현하는 방식의 패턴이다.

토비 스프링 왈  
'상속을 통해 슈퍼클래스의 기능을 확장할 때 사용하는 가장 대표적인 방법.
변하지 않는 기능은 슈퍼클래스에 만들어두고 자주 변경되며 확장할 기능은 서브클래스에서 만들도록 한다'

 

 

 템플릿 메소드 구조

- AbstractClass : 템플릿메서드를 정의하고, 템플릿 메서드 내에서 실행될 추상 메서드를 선언한다.

- ConcreteClass : 추상클래스를 상속하고 추상메서드를 구체적으로 구현한다.

 

 

 


내가 작성한 클린하지 않은 코드로 예시를 들어본다.

 현재 구성도

플로우는 아래와 같다.

클라이언트가 요청한 Excel , Csv , Txt 파일들을 doFileProcess() 메서드 내에서

for문으로 돌면서 파일명, 파일명에 담긴 특정 값 지역변수 저장하고, 각 파일형식 별 구현한 객체(ex. ??Service) 에서 파일 형식 별 값 읽어들이고, Map으로 변환 작업 진행한다. 그 후 Map을 List로 변환한다.

 

 

클린하지 않은 코드

public interface FileService{
	boolean doFileProcess(MultipartFile[] FileList, List<?> targetList);
}

//엑셀
public class ExcelService implements FileService{
	@override
        public boolean doFileProcess(MultipartFile[] FileList, List<?> targetList){
            for( MultipartFile file: FileList ) {
                    String no_oid = getOid(file); //서비스코드
                    String fileName =file.getOriginalFilename(); //파일명

                    //Excel파일 형식 읽는 로직(파일 형식별로 로직이 다르다) 
                    ...
               }

            Utils.divideListIntoLIMIT(convertMapToList(targetMap),targetList);
            return true;
}

//CSV
public class CsvService implements FileService{
	@override
        public boolean doFileProcess(MultipartFile[] FileList, List<?> targetList){
            for( MultipartFile file: FileList ) {
                    String no_oid = getOid(file); //서비스코드
                    String fileName =file.getOriginalFilename(); //파일명

                    //Csv파일 형식 읽는 로직(파일 형식별로 로직이 다르다) 
                    ...
               }
            Utils.divideListIntoLIMIT(convertMapToList(targetMap),targetList);
            return true;
}

 

인터페이스를 두고 구현해뒀지만 공통된 로직들이 각 구현체 안에 많이 포함되어 있다. 

 

문제) 공통된 로직 내에서 변경이 필요한 경우이다!!!

만약, getOid(file)가 getOther(file)로 변경되어야 한다면 나는 CsvService, ExcelService 모두 변경해주어야 한다.

 

 

 

템플릿 메소드 적용한 코드

public abstract class FileService{
	//템플릿메소드
	boolean doFileProcess(MultipartFile[] fileList, List<?> targetList){
    	for( MultipartFile file: FileList ) {
                    String no_oid = getOid(file); 
                    String fileName =file.getOriginalFilename(); 

                    //Excel,CSV 등 여러 파일 형식 읽는 추상메소드
                    doRealFileProcess(fileList,targetList);    
               }
            Utils.divideListIntoLIMIT(convertMapToList(targetMap),targetList);
            return true;
    }
    
    //각 구현체별 구현될 메소드
    abstract boolean doRealFileProcess(MultipartFile[] fileList, List<?> targetList);
}

//엑셀
public class ExcelService extends FileService{
	@override
        public boolean doRealProcess(MultipartFile[] FileList, List<?> targetList){
          //Excel파일 형식 읽는 로직 
          ..
         }
}
//CSV
public class CsvService extends FileService{
	@override
        public boolean doRealProcess(MultipartFile[] FileList, List<?> targetList){
          //CSv파일 형식 읽는 로직 
          ..
         }
}

중복되는 코드들은 추상클래스에 모아두고 다른 알고리즘으로 로직이 실행되는 부분들만 각 클래스들이 구현하도록 하여 가독성 높이고, 중복성을 제거하였다.

또한 아까 언급했던 공통된 로직 내에서 변경이 필요한 경우의 문제점을 다시 보자.

getOid(file)이 getOhter(file)로 변경 시 모든 상속받은 클래스에서의 변경이 아닌 그 상위 클래스에서의 변경만 하면 된다.

 


템플릿 메소드를 공부하고 사용 시기를 아래와 같이 생각해보았다.

- 공통된 기능들을 상위클래스에 정의하고, 변하거나 확장되어야할 기능들은 하위클래스에서 구현할 때

 -> 공통된 로직과 각 클래스별로 세부적으로 구현해야할 로직이 함께 존재할 떄?