https://github.com/lhoju0158

개발 끄적끄적

[디자인 패턴] 추상 팩토리 패턴, 팩토리 메서드 패턴

lhoju0158 2025. 10. 25. 02:06

new를 사용하는 것은 구상 클래스의 인스턴스를 만든는 것이다.
만일 휴대폰이라는 인터페이스가 있고, 아이폰, 갤럭시를 원한다면

Phone phone;
phone = new IPhone(); // 아이폰이면
phone = new Galaxy(); // 갤럭시면

 

그리고 IPhoneGalaxyPhone을 구현한다.

피자 가게 오픈

피자가게에서 치즈, 그릭, 페페로니 피자를 만들고 싶다 하자.

Pizza orderPizza(String type) {
       Pizza pizza;

       if(type.equals("cheese")) pizza = new CheesePizza();
       else if(type.equals("greek")) pizza = new GreekPizza();
       else if(type.equals("pepperoni")) pizza = new PepperoniPizza();       

       pizza.prepare();
       pizza.bake();
       pizza.cut();
       pizza.box();

       return pizza;
 }

 

이렇게 선언 해서 만들면 된다. 여기서 새로운 인스턴스를 할당하는 부분을 다른 객체, 팩토리로 변경해보자

public class SimplePizzaFactory {
    public Pizza createPizza(String type){ 
        Pizza pizza = null;
        if(type.equals("cheese")) pizza = new CheesePizza();
        if(type.equals("pepper")) pizza = new PepperoniPizza();
        if(type.equals("clam")) pizza = new ClamPizza();
        if(type.equals("veggie")) pizza = new VeggiePizza();

        return pizza;
    }
 }

 

이를 활용하면 간단한 팩토리가 가능하다.

public class PizzaStore{
    SimplePizzaFactory simplePizzaFactory;

    public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
        this.simplePizzaFactory = simplePizzaFactory;
    }

    public Pizza orderPizza(String type){
        Pizza pizza;
        pizza = simplePizzaFactory.createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

 

factory에게 type을 주면, 이것에 맞는 pizza가 오는 구조이다. 이는 디자인 패턴이라 할 수 없다.

 

여기서 만약!

 

장사가 되게 되게 잘되서 분점을 내려한다. 서울 지점 말고 부산 지점을 또 낸다. 현재 서울에선 4가지 스타일의 피자를 파는데.. 이걸 좀 다르게 부산 지점에 만들거다. 부산 사람들은 치즈를 좋아한다. (이재모 피자 때문인 듯) 그리고 맵게 만드는 걸 좋아한다. 

 

그러면 여기서 요구사항이...

 

4가지 스타일의 피자 -> 고정 사항

하지만 맛 + 방식은 좀 다르게 -> 변경 사항

 

그리고 두가지 분점은 동일한 순서로 피자를 만든다 (피자 생성 -> 준비 -> 제작 -> 포장)

 

이를 구현해보자. 

팩토리 메소드 패턴

public abstract class PizzaStore{
    public Pizza orderPizza(String type){
        Pizza pizza;
        pizza = createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
    abstract Pizza createPizza(String type);
}

 

이렇게 피자 가게를 크게 만들고 이를 서울 지점, 부산 지점이 상속 받는다. 여기서 피자를 주문하는 방식은 동일하지만, 피자를 만드는 과정은 모두 다르다! 서울부산createPizza()을 자기들 방식대로 구현하면 된다.

public class 서울PizzaStore extends PizzaStore{
	@Override
	public Pizza createPizza(String type){
		Pizza pizza = null;
		if(type.equals("cheese")) pizza = new 서울StyleCheesePizza();
		if(type.equals("peper")) pizza = new 서울StylePepperoniPizza();
		if(type.equals("clam")) pizza = new 서울StyleClamPizza();
		if(type.equals("veggie")) pizza = new 서울StyleVeggiePizza();
		return pizza;
	}
}

 

서울과 부산 지점 모두 치즈, 후추, 크림, 배지 피자를 판다. 근데 이게 서로 다른 스타일이다. 

 

그럼 서울과 인천 지점이 만들어내는 피자는 크게 피자라는 틀 안에 있지만 세세한건 다르다. 만들어내는 지점 + 맛의 조합으로 여러가지 바리에이션이 가능하다.

 

그럼 일단 큰 피자라는 틀을 만들자.

public abstract class Pizza{
	String name;
	String dough;
	String sauce;
	ArrayList<String> toppings = new ArrayList<>();

	public void prepare(){
		System.out.println("Preparing : "+name);
		System.out.println("Tossing dough...");
		System.out.println("Adding source");
		System.out.println("Adding toppings");
		for (String topping : toppings) {
			System.out.println("\ttopping : "+topping);
		}
	}

	public void bake(){
		System.out.println("Bake for 25 minutes at 350");
	}
    
	public void cut(){
		System.out.println("Cutting the pizza into diagonal slices");
	}
	
	public void box(){
		System.out.println("Place pizza in official PizzaStore box");
	}
	
	public String getname(){
		return this.name;
	}
}

 

서울 지점은 본점이라 기본 스타일을 따른다

public class 서울StyleCheesePizza extends Pizza{
	public 서울StyleCheesePizza() {
		this.name = "서울 Style CheesePizza";
		this.dough = "Thin Crust Dough";
		this.sauce = "Marinara Sauce";
		this.toppings.add("Grated Reggiano Cheese");
	}
}

 

부산은 커팅 방법이 다르다. 그러면 기본 방식을 따르지 않고 오버라이딩 해주면 된다.

public class 부산StyleCheesePizza extends Pizza{
	public 부산StyleCheesePizza() {
		this.name = "부산 Style CheesePizza";
		this.dough = "Extra Thick Crust Dough";
		this.sauce = "Plum Tomato Sauce";
		this.toppings.add("Shredded mozzarella Cheese");
	}

	@Override
	public void cut() {
		System.out.println("Cutting the pizza into square slices");
	}
}

 

그럼 이걸 사용해보자. 나는 서울 지점의 치즈 피자와 인천 지점의 후추 피자를 사고 싶다. 

public class PizzaTestDrive {
	public static void main(String[] args) {
		PizzaStore 서울Store = new 서울PizzaStore();
		PizzaStore 부산Store = new 부산PizzaStore();

		Pizza 서울SytpePizza = nyStore.orderPizza("cheese");
		System.out.println(서울SytpePizza.getname());

		Pizza 부산StypePizza = 부산Store.orderPizza("peper");
		System.out.println(부산StypePizza.getname());
	}
}
 

이렇게 각 스토어에서 주문을 하면 된다. 

 

여기서 의존성 역전 원칙이 사용된다.

 

기존에는

피자가게 1번 지점(추상)은 서울 치즈 피자(구체)를 만든다.

피자가게 2번 지점(추상)은  부산 후추 피자(구체)를 만든다.

 

그런데 이제는

 

피자 가게(추상)는 피자(추상)를 만들어낸다.

피자(추상)의 종류에는 서울 치즈 피자(구체)가 있고, 부산 후추 피자(구체) 등등이 있다.

 

이렇게 의존 관계가 역전된다. 

 

여기서 의존성이란? 파라미터 값 또는 지역 변수 등으로 다른 객체를 참조하는 것

 

쉽게 말하면 내가 만들어지기 위한 재료라 생각하자. 나는 재료에 의존한다! 

예시로.. 김치찌개를 위해선 김치, 돼지고기가 필요하다. 김치찌개는 의존 하는 것, 김치는 의존 받는 것. 이를 김치찌개 -> 김치 이렇게 표현할 수 있다. 

 

피자 가게에는 피자가 필요하다. 피자 가게(추상) -> 피자(추상) 이렇게 추상적인 것에 의존하게 된다. 

 

팩토리 메소드 패턴을 적용하고 나면 고수준 구성요소(PizzaStore)와 저수준 구성요소 (서울StyleCheesePizza, 부산StylePizza, ..) 들이 모두 추상 클래스인 Pizza에 의존하게 된다. 

 

 

여기서 갑자기 부산 땅값이 비싸졌다. 그래서 부산 지점이 갑자기 싼 재료로 원가 절감을 하고 있다. 본사에서 이제 재료도 관리를 한다! 두둥. 단, 앞으로 부산 지점은 본사에서 재료 공급을 하지 않고, 현지에서 재료를 공수한다. 

 

추상 팩토리 패턴

이제 원재료 공장을 만들자.

 

요구사항

- 지역별 원재료 공장이 필요하다.

- 팩토리에서 사용할 원재료 클래스가 있다.

- 이를 pizzaStore 코드에서 사용하도록 하나의 공장이 필요하다.

 

기본 원재료 공장

public interface PizzaIngredientFactory {
    public Dough createDough();
    public Sauce createSauce();
    public Cheese createCheese();
    public Veggies[] createVeggies();
    public Pepperoni createPepperoni();
    public Clams createClams();
}

 

이를 구현하는 부산재료 공장, 구현은 오버라이딩한다. 

public class 부산PizzaingredientFactory implements PizzaIngredientFactory{
	@Override
	public Dough createDough() {
		return new ThinCrustdough();
	}

	@Override
	public Sauce createSauce() {
		return new MarinaraSauce();
	}

	@Override
	public Cheese createCheese() {
		return new ReggianoCheese();
	}

	@Override
	public Veggies[] createVeggies() {
		Veggies veggies[] = { new Farlic(), new Onion(), new Mushroom(), new RedPepper() };
		return veggies;
	}

	@Override
	public Pepperoni createPepperoni() {
		return new SlicedPepperoni();
	}

	@Override
	public Clams createClams() {
		return new Freshclams();
	}
}

 

그럼 이제 재료 공장이 바뀌었으니 피자 준비 과정이 변경된다. 

 

public void prepare(){
    System.out.println("Preparing : "+name);
    System.out.println("Tossing dough...");
    System.out.println("Adding source");
    System.out.println("Adding toppings");
    for (String topping : toppings) {
        System.out.println("\ttopping : "+topping);
    }
}

 

이렇게 되어있는걸 이제 피자 공장에서 받아오자. 

 

public abstract class Pizza{
    String name;
    Dough dough;
    Sauce sauce;
    Veggies veggies[];
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clams;
    
    public abstract void prepare(); // 추상 메소드로 변경
    
    public void bake(){
        System.out.println("Bake for 25 minutes at 350");
    }
    
    public void cut(){
        System.out.println("Cutting the pizza into diagonal slices");
    }
    
    public void box(){
        System.out.println("Place pizza in official PizzaStore box");
    }
    
    public String getname(){
        return this.name;
    }
}

 

이렇게 변경하고 이를 상속받는 클래스에서 구현하면 된다.

 

public class ClamPizza extends Pizza{
    PizzaIngredientFactory ingredientFactory;

    public ClamPizza(PizzaIngredientFactory ingredientFactory) {
        this.ingredientFactory = ingredientFactory;
    }

    @Override
    public void prepare() {
        this.dough = ingredientFactory.createDough();
        this.sauce = ingredientFactory.createSauce();
        this.cheese = ingredientFactory.createCheese();
        this.clams = ingredientFactory.createClams();
    }
}

 

이제 이를 사용해보자.

public class 부산PizzaStore extends PizzaStore{
    @Override
    public Pizza createPizza(String type){
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new 부산PizzaingredientFactory();
        if(type.equals("cheese")){
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName(ingredientFactory.부산_STYLE+" Cheese Pizza");
        } else if(type.equals("peper")){
            pizza = new PepperoniPizza(ingredientFactory);
            pizza.setName(ingredientFactory.부산_STYLE+" Pepperoni Pizza");
        } else if(type.equals("clam")){
            pizza = new ClamPizza(ingredientFactory);
            pizza.setName(ingredientFactory.부산_STYLE+" Clam Pizza");
        } else if(type.equals("veggie")){
            pizza = new VeggiePizza(ingredientFactory);
            pizza.setName(ingredientFactory.부산_STYLE+" Veggie Pizza");
        }
        return pizza;
    }
}

 

흐름을 정리하면

 

1. 부산 피자가게를 만든다.

     - PizzaStore 부산PizzaStore = new 부산PizzaStore();

2. 치즈 피자를 주문을 한다.

     - 부산PizzaStore.orderPizza("cheese");

3. orderPizza 메소드에서는 우선 createPizza() 메소드를 호출한다

     - Pizza pizza = createPizza("cheese");

4. createPizza() 메소드가 호출되면 원재료 공장이 돌아가기 시작한다.

     - Pizza pizza = new CheesePizza(부산IngredientFactory); // 의존성 주입

5. 피자를 준비하는 prepare()메소드가 호출되면 팩토리에 원재료 주문이 들어간다.

     - void prepare(){

            dough = 부산IngredientFactory.createDough();

            sauce = 부산IngredientFactory.createSauce();

            cheese = 부산IngredientFactory.createCheese();

       }

6. 준비단계가 끝나고 orderPizza() 메소드에서는 피자를 굽고, 자르고, 포장한다.

 

 

추상 팩토리 패턴: 여러 제품을 이루는 제품군을 찍어내는 공장. 인터페이스를 정의해주는 공장

팩토리 메서트 패턴: 단일 제품 생성 방식을 구체화 해주는 메서드. 메서드를 정의해준다. 

 

후기

팩토리 패턴을 정확하게 사용하고 싶어서 정리했다. 디자인패턴.. 알수록 어렵당..

 

참고 자료

https://bcp0109.tistory.com/368

 

Factory 패턴 (3/3) - Abstract Factory (추상 팩토리) 패턴

1. Overview Factory 패턴 시리즈의 마지막인 추상 팩토리 패턴입니다. 추상 팩토리는 얼핏 보면 팩토리 메서드 패턴과 비슷하다고 느낄 수도 있습니다. 가장 큰 차이점은 팩토리 메서드 패턴은 어떤

bcp0109.tistory.com

https://jusungpark.tistory.com/14

 

디자인패턴 - 팩토리 패턴 (factory pattern)

팩토리 패턴 (factory pattern) 팩토리 메소드 패턴 : 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만든다. 즉 팩토리 메소드 패턴

jusungpark.tistory.com