본문 바로가기
JAVA

탬플릿 메서드 패턴 & 팩토리 메서드패턴

by 영카이브 2024. 1. 24.

탬플릿 메서드 패턴

  • 상속을 통해 중복되는 코드는 상위클래스로, 달라지는 부분은 하위클래스로 놓는다.
  • 상위클래스는 추상클래스이다.
  • 탬플릿(공통순서)을 제공하는 메서드(final을 사용해 Override 막음) + 자식클래스가 꼭 구현해야하는 추상메서드 + 선택적 오버라이딩하는 훅 메서드 

탬플릿 메서드 패턴이 필요한 경우

  • 알고리즘의 일관성을 유지하면서 일부분 변경사항 있을 시 사용한다.
  • 즉 여러 클래스가 비슷한 알고리즘 구조를 공유하고 일부만 서브클래스에서 변경한다.

 

사용예시

간단한 음료 제조 과정이다. 음료는 각각 다른 단계의 처리가 필요한데 모든 음료의 제조에는 물을 끓이는 단계, 재료를 추가하는 단계, 음료를 따르는 단계가 포함된다. 그러나 각 음료의 구체적인 단계는 다르다. 커피는 커피 원두를 넣는다는 특별한 단계가 있고, 차는 차 세트를 사용하는 특별한 단계가 있다.

abstract class BeverageTemplate {
    public final void makeBeverage() {
        boilWater();
        addIngredients();
        pourInCup();
        // 추가적인 후처리 단계 (하위 클래스에서 선택적으로 오버라이드 가능)
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    abstract void boilWater();
    abstract void addIngredients();
    abstract void pourInCup();
    abstract void addCondiments();

    // 후처리 단계를 하위 클래스에서 선택적으로 오버라이드할 수 있도록 함
    boolean customerWantsCondiments() {
        return true;
    }
}

class Coffee extends BeverageTemplate {
    @Override
    void boilWater() {
        System.out.println("커피를 위한 물 끓이기");
    }

    @Override
    void addIngredients() {
        System.out.println("커피 원두 추가");
    }

    @Override
    void pourInCup() {
        System.out.println("커피를 컵에 따르기");
    }

    @Override
    void addCondiments() {
        System.out.println("설탕과 우유 추가");
    }
}

class Tea extends BeverageTemplate {
    @Override
    void boilWater() {
        System.out.println("차를 위한 물 끓이기");
    }

    @Override
    void addIngredients() {
        System.out.println("찻잎 추가");
    }

    @Override
    void pourInCup() {
        System.out.println("차를 컵에 따르기");
    }

    @Override
    void addCondiments() {
        System.out.println("레몬 추가");
    }

    // 차에선 후처리 단계를 생략
    @Override
    boolean customerWantsCondiments() {
        return false;
    }
}

public class Client {
    public static void main(String[] args) {
        System.out.println("커피 만들기");
        Coffee coffee = new Coffee();
        coffee.makeBeverage();

        System.out.println("차 만들기");
        Tea tea = new Tea();
        tea.makeBeverage();
    }
}

 

커피 만들기
커피를 위한 물 끓이기
커피 원두 추가
커피를 컵에 따르기
설탕과 우유 추가

차 만들기
차를 위한 물 끓이기
찻잎 추가
차를 컵에 따르기

 

 

 

팩토리 메서드 패턴

  • 팩토리는 객체를 생성한다.
  • 팩토리 메서드는 객체를 생성 반환 하는 메서드다.
  • 팩토리 메서드 패턴은 하위클래스에서 팩토리 메서드를 Override해서 객체를 반환한다.
  • 상위 클래스에서 인스턴스 생성을 처리하는 메서드(팩토리 메서드)를 선언하고 이를 하위 클래스에서 구현하여 실제 인스턴스를 생성한다.

팩토리 메서드 패턴이 필요한 경우

  • 객체 생성의 변동이 있고 유연성이 필요한 경우 사용한다.
  • 아직 클라이언트에서 어떤 구체적인 클래스의 인스턴스를 만들지를 모를 때 사용한다.
  • 즉 객체 생성 방법이 여러개이고 이 로직을 별도의 클래스로 분리함으로써 새로운 객체 추가나 변경이 용이하다.

 

사용예시

각 음료에 대한 팩토리를 만들어두고, 클라이언트에서는 필요한 음료의 팩토리를 선택하여 해당 팩토리로부터 음료를 생성할 수 있다. 이는 음료에 대한 확장이나 변경이 필요할 때 수정이 용이하도록 만들어진 구조다.

팩토리 메서드는 createBeverage 메서드이다. 이 메서드는 Beverage 객체를 생성하는 역할을 한다.

CoffeeFactory와 TeaFactory 클래스에서 이 메서드를 Override하여 각각의 음료에 맞는 Beverage 객체를 생성한다.

따라서 CoffeeFactory에서는 createBeverage 메서드가 Coffee 객체를 생성하고, TeaFactory에서는 Tea 객체를 생성한다.

클라이언트 코드에서는 어떤 음료를 만들지를 선택하고, 해당 음료를 생성할 수 있는 팩토리를 선택하여 createBeverage 메서드를 호출하면, 그에 맞는 음료 객체가 생성되어 반환된다.

// 음료 추상 클래스
abstract class Beverage {
    abstract void prepare();
}

// 커피 클래스
class Coffee extends Beverage {
    @Override
    void prepare() {
        System.out.println("커피 가루 우려내기");
        System.out.println("설탕과 우유 추가");
    }
}

// 차 클래스
class Tea extends Beverage {
    @Override
    void prepare() {
        System.out.println("차잎 우려내기");
        System.out.println("레몬 추가");
    }

    // 차에선 후처리 단계를 생략
    @Override
    boolean customerWantsCondiments() {
        return false;
    }
}

// 음료를 만드는 팩토리 인터페이스
interface BeverageFactory {
    Beverage createBeverage();
}

// 커피를 만드는 팩토리 클래스
class CoffeeFactory implements BeverageFactory {
    @Override
    public Beverage createBeverage() {
        return new Coffee();
    }
}

// 차를 만드는 팩토리 클래스
class TeaFactory implements BeverageFactory {
    @Override
    public Beverage createBeverage() {
        return new Tea();
    }
}

// 클라이언트 코드
public class Client {
    public static void main(String[] args) {
        // 커피를 만드는 팩토리 선택
        BeverageFactory coffeeFactory = new CoffeeFactory();
        // 커피 생성
        Beverage coffee = coffeeFactory.createBeverage();
        // 커피 제조
        System.out.println("커피 만들기");
        coffee.prepare();

        System.out.println();

        // 차를 만드는 팩토리 선택
        BeverageFactory teaFactory = new TeaFactory();
        // 차 생성
        Beverage tea = teaFactory.createBeverage();
        // 차 제조
        System.out.println("차 만들기");
        tea.prepare();
    }
}

 

커피 만들기
커피 가루 우려내기
설탕과 우유 추가

차 만들기
차잎 우려내기
레몬 추가

 

참고!

Beverage coffee = coffeeFactory.createBeverage();

BeverageFactory 인터페이스를 구현한 CoffeeFactory 클래스에서 createBeverage 메서드가 return new Coffee();를 반환하고 있다. 이때 반환된 객체는 Coffee 타입의 객체다. 그러나 메서드의 반환 타입이 Beverage로 선언되어 있기 때문에 실제로는 Coffee 객체가 Beverage로 자동으로 형변환되어 반환된다. 따라서 Coffee coffee가 아닌 Beverage coffee로 받는다. Beverage Coffee의 상위 클래스이기 때문에, Beverage 타입으로 선언하면 새로운 종류의 음료를 추가하거나 변경할 때, 해당 음료를 만드는 팩토리만 새로 구현하면 되므로 클라이언트 코드의 수정이 최소화된다.

 

 

두 패턴을 함께 사용

 

// 음료 추상 클래스
abstract class Beverage {
    // 팩토리 메서드
    public final void makeBeverage() {
        boilWater();
        addIngredients();
        pourInCup();
        // 추가적인 후처리 단계 (하위 클래스에서 선택적으로 오버라이드 가능)
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    abstract void boilWater();
    abstract void addIngredients();
    abstract void pourInCup();
    abstract void addCondiments();

    // 후처리 단계를 하위 클래스에서 선택적으로 오버라이드할 수 있도록 함
    boolean customerWantsCondiments() {
        return true;
    }
}

// 커피 클래스
class Coffee extends Beverage {
    @Override
    void boilWater() {
        System.out.println("커피를 위한 물 끓이기");
    }

    @Override
    void addIngredients() {
        System.out.println("커피 원두 추가");
    }

    @Override
    void pourInCup() {
        System.out.println("커피를 컵에 따르기");
    }

    @Override
    void addCondiments() {
        System.out.println("설탕과 우유 추가");
    }
}

// 차 클래스
class Tea extends Beverage {
    @Override
    void boilWater() {
        System.out.println("차를 위한 물 끓이기");
    }

    @Override
    void addIngredients() {
        System.out.println("찻잎 추가");
    }

    @Override
    void pourInCup() {
        System.out.println("차를 컵에 따르기");
    }

    @Override
    void addCondiments() {
        System.out.println("레몬 추가");
    }

    // 차에선 후처리 단계를 생략
    @Override
    boolean customerWantsCondiments() {
        return false;
    }
}

// 음료를 만드는 팩토리 인터페이스
interface BeverageFactory {
    Beverage createBeverage();
}

// 커피를 만드는 팩토리 클래스
class CoffeeFactory implements BeverageFactory {
    @Override
    public Beverage createBeverage() {
        return new Coffee();
    }
}

// 차를 만드는 팩토리 클래스
class TeaFactory implements BeverageFactory {
    @Override
    public Beverage createBeverage() {
        return new Tea();
    }
}

// 클라이언트 코드
public class Client {
    public static void main(String[] args) {
        // 커피를 만드는 팩토리 선택
        BeverageFactory coffeeFactory = new CoffeeFactory();
        // 커피 생성
        Beverage coffee = coffeeFactory.createBeverage();
        // 커피 제조
        System.out.println("커피 만들기");
        coffee.makeBeverage();

        System.out.println();

        // 차를 만드는 팩토리 선택
        BeverageFactory teaFactory = new TeaFactory();
        // 차 생성
        Beverage tea = teaFactory.createBeverage();
        // 차 제조
        System.out.println("차 만들기");
        tea.makeBeverage();
    }
}

'JAVA' 카테고리의 다른 글

인터페이스  (0) 2024.01.28
다형성과 형변환  (0) 2024.01.27
메서드 동적 바인딩  (0) 2024.01.19
참조형식과 호출되는 메소드 관계  (0) 2024.01.18
IS A 상속  (0) 2024.01.16