본문 바로가기

Languages/Java

(1) 템플릿 메서드 패턴 활용 - 간단한 CRUD

 

저번 글 "클래스 활용 " 에서 추가해보면 좋을 점을 들었습니다.  

 

1. 템플릿메서드 패턴 적용
2. MVC 패턴 적용

 

이번 글에서는 템플렛메서드 패턴 적용한 과정을 작성합니다 .

 

 

 

템플릿 메서드란 ?

템플릿 메서드(Template Method)는 디자인 패턴(Design Pattern) 중 하나로,
“어떤 작업을 처리하는 전체 흐름(알고리즘의 골격) 은 상위 클래스에서 정의하고,
그 과정 중 일부 단계는 하위 클래스에서 상속 후 재정의(override) 하도록 하는 패턴”이다.

 

 

23개나 되는 디자인 패턴 중 하나입니다 .. 

 

상위클래스에서 정의를 하고 하위 클래스에서 재정의를 한다는 말이 글로보면 이해하기 어렵더라구요

제가 짠 코드의 패키지 구조를 보면서 설명해드리겠습니다

 

전체 코드는 다음 글에서 다룹니다

 

패키지 구조

 

 

흐름 :  View → Controller → Service → Repo → Model

 

이 중 View 안에서 메뉴마다 반복되는 흐름을 발견하였습니다 

 

중복되는 view

 

 

“회원 메뉴에서 검색/수정/삭제 등 메뉴가 여러 개 있을 때,
각 메뉴마다 반복되는 흐름(run → print → 입력 → 실행) 이 계속 중복되고 있습니다
이런 중복을 줄이고, 공통 흐름을 묶기 위해 템플릿 메서드를 적용합니다

 

 

템플릿 메서드 적용 전

 

템플릿메서드 적용 후

 

제가 생각한 적용 전과 후의 그림입니다 확연히 줄어든 게 보이시나요 !

그럼 이제 코드를 소개해보겠습니다

 

주요 키워드 
interface , implements 
class , extends 
abstract
<>

 

 

abstract class Template<T> {
	T method;
	boolean inRunning = true;
	
	// 생성자를 통해 Search 타입 MemberMenu
	// 이 클래스에서 사용되는 T 타입의 객체를 Template 클래스 method에 저장한다. 
	Template(T method) { 
		this.method = method;
	}
	
	// 객체 생성 후 실제 실행되는 흐름 
	void run (Scanner sc) {
		while(inRunning) {
		print();
		System.out.print("#사용자 : ");
		switch(sc.nextInt()) {
		case 1: firstCase(method); break;
		case 2: secondCase(method); break;
		case 3: thirdCase(method); break;
		case 4: fourthCase(method); break;
		case 5: fifthCase(method); break;
		case 9: inRunning = false; break;
		default: System.out.println("잘못 입력하셨습니다"); break;
		}
	}
	}
	// 추상 메서드
	abstract void print();
	// T method는 변환된 T 인터페이스의 객체를 얘기한다.
	abstract void firstCase(T method);
	abstract void secondCase(T method);
	abstract void thirdCase(T method);
	// 4~5번은 없는 경우있어서 오버라이딩으로 진행
	void fourthCase(T method) {
		System.out.println("비어있습니다.");
	};
	void fifthCase(T method) {
		System.out.println("비어있습니다.");
	};
}

 

 

이렇게 Template.run()에서
전체 알고리즘(반복, 메뉴 출력, 입력 처리, case)을 한번에 처리함으로써
하위 클래스에는 실제 기능만 구현하면 됩니다.

 

 

 

Template<T> 해석 ㅡ <T> 가 무엇인가 ?

 

abstract class Template <T> 는

T라는 ‘아직 정해지지 않은 타입’을 받아서 사용하는 추상 클래스

를 뜻합니다

 

 

제네릭이란 ?
제네릭(Generic)은 클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해 지정되는 것을 의미한다

 

 

 

Template 클래스는
어떤 타입의 기능을 실행해야 할지 모르는 상태입니다.

검색 기능일 수도 있고,
수정 기능일 수도 있고,
삭제 기능일 수도 있기 때문이죠.

그래서 제네릭(T) 를 사용하여 타입을 외부에서 지정하게 합니다.

 (상속받는 순간 T가 무엇인지 결정된다.)

 

예를 들어 하위 클래스 중 검색 클래스의 코드를 보겠습니다

 

 

class SearchMember extends Template<Search> {

	/*
	 * SearchMember 클래스는 Search 인터페이스의 Template 클래스를 상속받는다
	 * 
	 * 
	 * 생성자에는 MemberMenu 객체 (this) 가 들어오게됨.
	 * MemberMenu는 Search 인터페이스를 implements 했기에 자동으로
	 * Search 타입으로 변환하여 Search s에 넣어준다.
	*/
	
	public SearchMember(Search s) {
		super(s);
	}
	
	void print(){
		System.out.println("1. 아이디로 검색하기");
		System.out.println("2. 이름으로 검색하기");
		System.out.println("3. 이메일로 검색하기");
		System.out.println("9. 메인으로 돌아가기");
		
	}
	
	/*
	 * 구현부 
	 * 
	 * 각 메서드들은 부모클래스 Template 의 run() 메서드에서 호출된다 
	 * 호출 시 파라미터로 Search 타입의 method라는 객체를 받게되며 
	 * 이 method는 현재 Search 인터페이스를 가진 MemberMenu 객체가 들어온다
	 * 실제로는 MemberMenu에 구현된 Search 관련 메서드들을 사용가능하게한다
	 */
	@Override
	void firstCase(Search method) {
		method.searchId();
	}
	@Override
	void secondCase(Search method) {
		method.searchName();
	}
	@Override
	void thirdCase(Search method) {
		method.searchEmail();
	}
}

 

검색 클래스가 상속받음

 

 <Search> — Template<T> 의 T를 Search로 지정

 

SearchMember는 Template 를 상속하므로
Template 내부의 모든 T는 Search로 확정됩니다.

 

SearchMember라는 클래스를 생성하고 Template 클래스를 상속받는 것까지는 이해가 되실겁니다

근데 <Search> 는 도대체 무엇인가에 대해 보겠습니다

 

//인터페이스 
interface Search {
	void searchId ();
	void searchName ();
	void searchEmail ();
}

interface Update {
	void updatePassword();
	void updateName();
	void updateEmail();
}
interface Delete {
	void deleteOne();
	void deleteAll();
}

 

 

Search Class 에서는 

상속받은 Template 클래스의 인터페이스로

미리 선언해둔 인터페이스를 사용하겠다 ~ 라는 뜻이 됩니다. 

 

 

 

 

 Search Class에서는 부모 클래스는 Search 인터페이스를 가지게 되고 ,

Update  Class에서는 부모 클래스는 Update 인터페이스를 가지게 되고 ,

Delete  Class에서는 부모 클래스는 Delete 인터페이스를 가지게 됩니다.

 

 

Template class 의 생성자를 보면 T 타입의 method라는 변수를 생성하였습니다

그렇다면 상속받은 어떠한 하위 클래스가 부모클래스를 실행하느냐에 따라

여기 T 타입은 하위 클래스를 따라갈 겁니다

 

그리고 생성자를 통해 T 타입의 method를 전달 받아야하는데 

 

 

 

위 코드를 보게되면 선언해둔 인터페이스들을 MemberMenu 가 가지고 있고

입력값에 맞게 각 하위 클래스들을 생성후 실행을 하는 코드입니다 .

 

부모 클래스 생성자에 들어오는 T method 는 this 로 들어오게 되는데

여기서의 this 는 Search , Update , Delete 라는 인터페이스들의 묶음이 들어가게 됩니다

 

case 2 를 예를 들어서 확인해보겠습니다. 

 

interface Search {
	void searchId ();
	void searchName ();
	void searchEmail ();
}

 

new SearchMember(this).run(sc);
  • S,U,D 를 가지고 있는 MemberMenu 자기 자신을 생성자 파라미터로 보내준다
class SearchMember extends Template<Search> {

	/*
	 * SearchMember 클래스는 Search 인터페이스의 Template 클래스를 상속받는다
	 * 
	 * 
	 * 생성자에는 MemberMenu 객체 (this) 가 들어오게됨.
	 * MemberMenu는 Search 인터페이스를 implements 했기에 자동으로
	 * Search 타입으로 변환하여 Search s에 넣어준다.
	*/
	
	public SearchMember(Search s) {
		super(s);
	}
  • Search 클래스 생성자로 들어온 this 는 Search 타입으로 건네받게 되어 결국 Search 만 들어오고 이 타입은
    s라는 이름으로 부모 생성자로 이동한다
  • 상속 받을 때에 제네릭 <T> 자리에 <Search>를 넣음으로 Serach 타입을 가지라는 의미로 변한다
abstract class Template<T> {
	T method;
	boolean inRunning = true;
	
	// 생성자를 통해 Search 타입 MemberMenu
	// 이 클래스에서 사용되는 T 타입의 객체를 Template 클래스 method에 저장한다. 
	Template(T method) { 
		this.method = method;
	}
  • 전달받은 Search 타입의 s 는 부모 클래스 내부에 <T> 타입의 method 라는 변수에 저장된다 
    곧 Serach타입의 객체를 사용하겠다 라는 얘기가 된다 
// 추상 메서드
	abstract void print();
	// T method는 변환된 T 인터페이스의 객체를 얘기한다.
	abstract void firstCase(T method);
	abstract void secondCase(T method);
	abstract void thirdCase(T method);
	// 4~5번은 없는 경우있어서 오버라이딩으로 진행
	void fourthCase(T method) {
		System.out.println("비어있습니다.");
	};
	void fifthCase(T method) {
		System.out.println("비어있습니다.");
	};
  • 이 부분은 부분으로 현재 부모 클래스가 가지고 있는 T 타입의 method는 Search 타입을 얘기하며 
  • 하위 클래스인 Search 클래스에서 재정의 된 Case 메서드를 통해 실제 실행된다 
	@Override
	void firstCase(Search method) {
		method.searchId();
	}
	@Override
	void secondCase(Search method) {
		method.searchName();
	}
	@Override
	void thirdCase(Search method) {
		method.searchEmail();
	}
  • Search 클래스의 구현부 
  • 파라미터로 Search 타입의 객체가 들어오면 그 객체 내부에 있는 메서드를 호출한다

 

 

 

그림을 그리자면 이렇게 될 것 같네요

 

정리

  1. 템플릿 메서드는 중복을 최소화하기 위해 사용한다
  2. 추상클래스와 구현클래스로 나누어 사용한다
  3. 큰 흐름은 추상 클래스에서 구현 
  4. 세부적인 메서드는 구현 클래스에서 구현
  5. 제네릭을 사용할 때는 각각의 타입을 잘 넘겨주기