문법/Java

[Java] Template Method/Factory Method/Strategy/Template Callback 패턴

jungmin.park 2023. 11. 6. 02:05

Template Method 패턴

" 하위 클래스에서 구체적으로 처리해라 " 
  • 상위클래스 : 템플릿에 해당하는 메소드가 정의, 정의 안에는 추상 메소드가 사용되고 있다.
    • 추상 메소드의 정의만 알 수 있다. -> 정의부/ 처리의 뼈대 결정
  • 하위클래스 : 추상 메소드를 실제로 구현하는 것
    • 추상 메소드 구현으로 구체적은 처리가 결정된다. -> 구현부
  • 서로 다른 하위 클래스가 서로 다른 구현을 실행하면 서로 다른 처리가 실행 가능하다.

 

AbstractDisplay
open
print
close
display

< 상위 클래스 >

CharDisplay StringDisplay
open
print
close
open
print
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;
          }
          

각 클래스의 역할

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);
            }
        };
    }
}