알고리즘 캡슐화
알고리즘을 캡슐화하여 SubClass에서 필요할 때 언제든 가져다 쓸 수 있게 한다.
Coffee.java
public class Coffee {
void prepareReceipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
private void addSugarAndMilk() {
System.out.println("설탕과 우유를 추가하는 중");
}
private void pourInCup() {
System.out.println("컵에 따르는 중");
}
private void brewCoffeeGrinds() {
System.out.println("필터를 통해서 커피를 우려내는 중");
}
private void boilWater() {
System.out.println("물 끓이는 중");
}
}
Tea.java
public class Tea {
void prepareReceipe() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
private void addLemon() {
System.out.println("레몬를 추가하는 중");
}
private void pourInCup() {
System.out.println("컵에 따르는 중");
}
private void steepTeaBag() {
System.out.println("차를 우려내는 중");
}
private void boilWater() {
System.out.println("물 끓이는 중");
}
}
BeverageTest.java
import org.junit.Test;
public class BeverageTest {
@Test
public void 커피_차() {
Tea tea = new Tea();
Coffee coffee = new Coffee();
System.out.println("\nMaking tea...");
tea.prepareReceipe();
System.out.println("\nMaking coffee...");
coffee.prepareReceipe();
}
}
Coffee와 Tea에서 공통 메소드 추출하여 SuperClass(CaffeinBeverage)로 옮긴다.
CaffeinBeverage.java
public abstract class CaffeinBeverage {
final void prepareReceipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
abstract void addCondiments();
abstract void brew();
private void pourInCup() {
System.out.println("컵에 따르는 중");
}
private void boilWater() {
System.out.println("물 끓이는 중");
}
}
prepareReceipe()는 final로 선언하여 서브클래스에서 override해서 쓰지 못하게 했다.
Coffee.java
public class Coffee extends CaffeinBeverage {
@Override
void addCondiments() {
System.out.println("설탕과 우유를 추가하는 중");
}
@Override
void brew() {
System.out.println("필터를 통해서 커피를 우려내는 중");
}
}
Tea.java
public class Tea extends CaffeinBeverage {
@Override
void addCondiments() {
System.out.println("레몬를 추가하는 중");
}
@Override
void brew() {
System.out.println("차를 우려내는 중");
}
}
CaffeinBeverage class에서 prepareReceipe() 메소드가 템플릿 메소드 이다.
| 처음 만들었던 Tea 및 Coffee 클래스 | 템플릿 메소드를 적용한 Tea 및 Coffee 클래스 |
|---|---|
| Coffee와 Tea 에서 각자 알고리즘 수행 | CaffeinBeverage 클래스에서 알고리즘을 독점 |
| 중복된 코드 존재 | Subclass에서 코드 재사용 |
| 알고리즘이 바뀌면 Subclass들을 함께 수정 | 알고리즘이 한군데 모여 있으므로 그 부분만 수정 |
| 새로운 음료를 추가하려면 많은 작업 필요 | 쉽게 추가 가능 |
| 알고리즘에 대한 지식과 구현이 여러 클래스에 분산 | 알고리즘이 하나에 집중되어 있고 일부 구현만 Subclass에 의존 |

후크(hook): 추상메소드에서 선언되는 메소드
기본적인 내용만 구현되어 있거나 아무 코드도 없는 것
CoffeeWithHook.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class CoffeeWithHook extends CaffeinBeverageWithHook {
@Override
void addCondiments() {
System.out.println("우유와 설탕을 추가하는 중");
}
@Override
void brew() {
System.out.println("필터로 커피를 우려내는 중");
}
/*
* 후크 메소드: 서브클래스에서 재정의
*/
public boolean customerWantsCondiments() {
String answer = getUserInput();
if (answer.toLowerCase().startsWith("y"))
return true;
else
return false;
}
private String getUserInput() {
String answer = null;
System.out.print("커피에 우유와 설탕을 넣어 드릴까요? (y/n) ");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
answer = in.readLine();
} catch(IOException ioe) {
System.err.println("IO 오류");
}
if (answer == null) {
return "no";
}
return answer;
}
}
TeaWithHook.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class TeaWithHook extends CaffeinBeverageWithHook {
@Override
public void addCondiments() {
System.out.println("레몬을 추가하는 중");
}
@Override
public void brew() {
System.out.println("차를 우려내는 중");
}
public boolean customerWantsCondiments() {
String answer = getUserInfo();
if (answer.toLowerCase().startsWith("y"))
return true;
else
return false;
}
private String getUserInfo() {
String answer = null;
System.out.print("차에 레몬을 넣어 드릴까요? (y/n) ");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
answer = in.readLine();
} catch(IOException ioe) {
System.err.println("IO 오류");
}
if (answer == null) {
return "no";
}
return answer;
}
}
BeverageTestDrive.java
public class BeverageTestDrive {
/**
* @param args
*/
public static void main(String[] args) {
TeaWithHook teaHook = new TeaWithHook();
CoffeeWithHook coffeeHook = new CoffeeWithHook();
System.out.println("\n차 준비중...");
teaHook.prepareReceipe();
System.out.println("\n커피 준비중...");
coffeeHook.prepareReceipe();
}
}
후크를 사용하려면 SubClass에서 Override해야 한다.
당연히 추상클래스에서 구현한 기본적인 내용을 쓰려면 Override하지 않으면 그만이다.
Q & A
Q. 템플릿을 만들 때 언제 추상 메소드를 쓰고 언제 후크를 써야 하나요?
A. 후크: 알고리즘의 특정 부분이 선택적으로 사용될 때
추상 메소드는 알고리즘의 특정 단계만 제공하고 SubClass에서 구현해야 할 때
할리우드 원칙
먼저 연락하지 마세요. 저희가 연락 드리겠습니다.
public static void sort(Object[] a) {
Object aux[] = (Object[]) a.clone();
mergeSort(aux, a, 0, a.length, 0);
}
private static void mergeSort(Object src[], Object desc[], int low, int high, int off) {
for (int i=low; i<high; i++) {
for (int j=i; j>low && ((Comparable)desc[j-1]).compareTo((Comparable)dest[j]>0;j--) {
swap(desc, j, j-1);
}
}
return;
}
두 객체를 비교해서 그 대소 관계를 판단한 결과를 리턴하는 메소드
Duck.java
public class Duck implements Comparable {
String name;
int weight;
public Duck(String name, int weight) {
this.name = name;
this.weight = weight;
}
public String toString() {
return name + " weight " + weight;
}
public int compareTo(Object object) {
Duck otherDuck = (Duck) object;
if (this.weight < otherDuck.weight)
return -1;
else if (this.weight == otherDuck.weight)
return 0;
else //this.weight > otherDuck.weight
return 1;
}
}
DuckSortTest.java
import java.util.Arrays;
import org.junit.Test;
public class DuckSortTest {
@Test
public void sort테스트() {
Duck[] ducks = {
new Duck("Daffy", 8),
new Duck("Dewey", 2),
new Duck("Howard", 7),
new Duck("Louie", 2),
new Duck("Donald", 10),
new Duck("Huey", 2)
};
System.out.println("Before sorting: ");
display(ducks);
Arrays.sort(ducks);
System.out.println("\nAfter sortingL ");
display(ducks);
}
private void display(Duck[] ducks) {
for (int i = 0; i < ducks.length; i++) {
System.out.println(ducks[i]);
}
}
}