문법/Java
[Java] Template Method/Factory Method/Strategy/Template Callback 패턴
jungmin.park
2023. 11. 6. 02:05
Template Method 패턴
" 하위 클래스에서 구체적으로 처리해라 " |
- 상위클래스 : 템플릿에 해당하는 메소드가 정의, 정의 안에는 추상 메소드가 사용되고 있다.
- 추상 메소드의 정의만 알 수 있다. -> 정의부/ 처리의 뼈대 결정
- 하위클래스 : 추상 메소드를 실제로 구현하는 것
- 추상 메소드 구현으로 구체적은 처리가 결정된다. -> 구현부
- 서로 다른 하위 클래스가 서로 다른 구현을 실행하면 서로 다른 처리가 실행 가능하다.
AbstractDisplay |
open close display |
< 상위 클래스 >
CharDisplay | StringDisplay |
open close |
open close printLine |
< 하위 클래스 >
- 이때, open, print, close(abstract method)로 하위클래스에서 구현을 해줄것
- display는 상위에서 구현을 함
public abstract class AbstractDisplay { public abstract void open(); public abstract void print(); public abstract void close(); public final void display(){ open(); for (int i = 0; i < 5; i++) { print(); } close(); } } |
public class CharDisplay extends AbstractDisplay{ private char ch; public CharDisplay(char ch) { this.ch = ch; } @Override public void open() { System.out.print("<<"); } @Override public void print() { System.out.print(ch); } @Override public void close() { System.out.println(">>"); } } |
public class StringDisplay extends AbstractDisplay{ private String string; private int width; public StringDisplay(String string) { this.string = string; this.width = string.getBytes().length; } @Override public void open() { printLine(); } @Override public void print() { System.out.println("|" + string + "|"); } @Override public void close() { printLine(); } public void printLine(){ System.out.print("+"); for (int i = 0; i < width; i++) { System.out.print("-"); } System.out.println("+"); } } |
public class Main {
public static void main(String[] args) {
AbstractDisplay d1 = new CharDisplay('H');
AbstractDisplay d2 = new StringDisplay("Hello, world.");
AbstractDisplay d3 = new StringDisplay("안녕하세요!");
d1.display();
d2.display();
d3.display();
}
}
- 큰 틀은 상위클래스인 AbstractDisplay
- d1, d2는 각각 자신의 클래스에서 구현한 open, close, print 함수로 호출된다.
<<HHHHH>> +-------------+ |Hello, world.| |Hello, world.| |Hello, world.| |Hello, world.| |Hello, world.| +-------------+ +----------------+ |안녕하세요!| |안녕하세요!| |안녕하세요!| |안녕하세요!| |안녕하세요!| +----------------+ |
- runtime시 타입객체가 아닌 실제 들어있는(CharDisplay/StringDisplay) 객체 호출 -> 하위클래스는 상위클래스형이 될 수 있다.
정리
AbstractClass(추상 클래스의 역할) | ConcreteClass(구현 클래스의 역할) |
- 템플릿 메소드를 만든다. - 추상 메소드가 있는 곳 - 추상 메소드는 하위클래스에서 구현 |
- 추상 메소드를 구체적으로 구현 - 상위 클래스의 견본 메소드에서 하위 클래스가 오버라이딩한 메소드를 호출 |
Factory Method 패턴
" 오버라이드된 메소드가 객체를 반환하는 패턴" |
인스턴스를 만드는 방법은 상위 클래스에서 결정하지만 구체적인 내용은 하위 클래스 측에서 수행한다.
-> 인스턴스 생성을 위한 골격과 실제의 인스턴스 생성의 클래스를 분리해서 생각할 수 있다.
Factory | Product |
create - 템플릿 메소드 createProduct registerProduct |
use |
- Factory 클래스에서 Product 객체를 생성해주고 있다.
IDCardFactory | IDCard |
owners - 변수 createProduct registerProduct getOwners |
owner - 변수 use getOwner |
- IDCardFactory 클래스에서 IDCard 객체 생성하고 있다.
public abstract class Factory { public final Product create(String owner){ Product p = createProduct(owner); registerProduct(p); return p; } protected abstract Product createProduct(String owner); protected abstract void registerProduct(Product product); } |
public abstract class Product { public abstract void use(); } |
public class IDCardFactory extends Factory { private List owners = new ArrayList<>(); @Override protected Product createProduct(String owner) { return new IDCard(owner); } @Override protected void registerProduct(Product product) { owners.add(((IDCard)product).getOwner()); } public List getOwners(){ return owners; } } |
public class IDCard extends Product { private String owner; IDCard(String owner){ System.out.println(owner + "의 카드를 만듭니다."); this.owner = owner; } @Override public void use() { System.out.println(owner + "의 카드를 사용합니다."); } public String getOwner() { return owner; } } |
public class Main { public static void main(String[] args) { Factory factory = new IDCardFactory(); Product card1 = factory.create("홍길동"); card1.use(); } } |
- Factory create 실행
Product p = createProduct(owner);
registerProduct(p);
return p; - createProduct 하위클래스에서 구현되어있으므로 IDCardFactory createProduct/registerProduct 실행.
- protected Product createProduct(String owner) {
return new IDCard(owner);
} - protected void registerProduct(Product product) {
owners.add(((IDCard)product).getOwner());
} - IDCard 객체 생성 -> 생성자 호출
- IDCard(String owner){
System.out.println(owner + "의 카드를 만듭니다.");
this.owner = owner;
}
- IDCard(String owner){
각 클래스의 역할
Factory | Product |
Product 역할과 인스턴스 생성 | 인스턴스가 가져야 할 인터페이스(API)를 결정 -> 추상클래스 |
IDCardFactory | IDCard |
구체적인 제품을 결정 IDCard 클래스 객체 호출 | 구체적인 제품을 만듬 IDCard 생성자 만들어지고 use 메소드호출 |
정리
팩토리 메소드 패턴은 의존 역전 원칙(DIP)를 활용하고 있음을 알 수 있다. |
예를 들어 TV Product 클래스와 TV공장 TVFactory 만든다고 할 때 상위 클래스의 내용을 수정하지 않아도 된다. 상위클래스에는 TV Product, TVFactory 구체적인 클래스 이름이 들어있지 않음 -> 상위클래스가 포함된 패키지에 의존하고 있지 않다. 하위클래스만 필요에 따라 수정을 해주면 된다. |
Strategy Pattern 패턴
"같은 문제를 다른 방법으로 해결 가능하다." |
- 알고리즘을 구현한 부분을 모두 교환할 수 있다.
- 전략 메소드를 가진 전략 객체
- 전략 객체를 사용하는 컨텍스트(전략 객체의 사용자/소비자)
- 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트(제3자, 전략 객체의 공급자)
예시) 보급장교가 군인에게 무기를 보급, 군인은 무기 사용
- 전략 객체 : 무기
- 컨텍스트 : 군인
- 클라이언트 : 보급장교
Strategy |
runStrategy |
StrategyGun | StrategySword | StrategyBow |
runStrategy 실행 시 : 탕, 타당, 타다당 | runStrategy 실행 시 : "챙.. 채쟁챙 챙챙" | runStrategy 실행 시 : 슝.. 쐐액.. 쉑, 최종병기 |
Soldier |
runContext |
public static void main(String[] args) {
Strategy strategy = null;
Soldier rambo = new Soldier();
strategy = new StrategyGun();
rambo.runContext(strategy);
System.out.println();
strategy = new StrategySword();
rambo.runContext(strategy);
System.out.println();
strategy = new StrategyBow();
rambo.runContext(strategy);
}
전투 시작
탕, 타당, 타다당
전투 종료
전투 시작
챙.. 채쟁챙 챙챙
전투 종료
전투 시작
슝.. 쐐액.. 쉑, 최종병기
일부러 Strategy 역할을 만들 필요가 있을까?
개방 폐쇄 원칙(OCP)과 의존 역전 원칙(DIP)이 적용된 패턴 |
- 템플릿 메서드와 차이
- 같은 문제의 해결책으로 상속을 사용하는 템플릿 메서드
- 객체 주입을 통한 전략 패턴 중에서 선택/적용 가능
- 단일 상속만 가능한 자바에서 상속이라는 제한이 있는 템플릿 메서드 패턴보다 전략 패턴을 더 많이 쓴다.
- 수정과 교체가 용이하다.
- 상위는 수정없이 하위만 수정을 하면 된다. 위임이라는 느슨한 연결을 사용하고 있어 알고리즘을 용이하게 바꿀 수 있다.
- 실행 중 전략패턴의 하위 클래스를 바꿀 수 있다.
- 메모리가 적은 환경에서는 SlowbutLessMemoryStrategy(속도는 느리지만 메모리를 절약하는 전략) 을 사용하고
- 메모리가 많은 환경에서는 FastButMoreMemoryStrategy(속도는 빠르지만 메모리를 많이 사용하는 전략)
- 검산에 사용 할 수 있다.
- 한쪽의 알고리즘을 다른 쪽 알고리즘의 검산에 이용할 수 있다.
- 표 계산 소프트웨어의 디버그 판에서 복잡한 계산을 실행할 때
- 버그가 있을지도 모르는 고속의 알고리즘과 / 저속이지만 확실한 계산을 실행하는 알고리즘을 준비해 검산
- 표 계산 소프트웨어의 디버그 판에서 복잡한 계산을 실행할 때
- 한쪽의 알고리즘을 다른 쪽 알고리즘의 검산에 이용할 수 있다.
Template Callback Pattern
- DI에서 사용하는 특별한 형태의 전략 패턴의 변형
- 전략 패턴과 모두 동일하지만 전략을 익명 내부 클래스로 정의해서 사용
rambo.runContext(strategy);
rambo.runContext(new Strategy(){
@Override
public void runStrategy(){
System.out.printlnl("총! 총총총총!");
}
});
리팩터링된 코드
public class Soldier {
void runContext(Strategy weaponSound){
System.out.println("전투 시작");
executeWeapon(weaponSound).runStrategy();
System.out.println("전투 종료");
}
private Strategy executeWeapon(final String weaponSound){
return new Strategy(){
@Override
public void runStrategy(){
System.out.println(weaponSound);
}
};
}
}