본문 바로가기
OOP/<헤드 퍼스트 디자인 패턴>, 에릭 프리먼 외

Simple Factory, Factory Method, Abstract Factory

by 민휘 2023. 2. 24.

Simple Factory

핵심 의도

객체 생성의 책임을 분리하자.

 

적용 상황

요구사항 변경에 의해 객체 생성에 필요한 코드가 자꾸 바뀌는 경우에 사용할 수 있다. 객체 생성에 필요한 과정을 템플릿처럼 정해서 다른 책임들로부터 분리할 수 있다.

 

솔루션의 구조와 각 요소의 역할

객체를 생성하는 부분이 요구사항에 따라 바뀌는 부분이므로, 객체 생성 책임을 별도의 객체인 Factory에 할당한다.

 

적용 예시

요구사항

피자 가게에서 주문 요청을 받으면 피자를 만들어 굽고 잘라서 박스에 넣으려고 한다. 이때 피자의 종류는 세 가지이고, 피자의 종류는 문자열로 구분한다.

 

설계

피자 가게인 PizzaStore이 피자 생성과 주문 메시지를 받을 수 있다. 이때 SimplePizzaFactory는 문자열에 따라 피자를 생성하는 책임을 구현한다.

 

코드

Pizza

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

	public String getName() {
		return name;
	}

	public void prepare() {
		System.out.println("Preparing " + name);
	}

	public void bake() {
		System.out.println("Baking " + name);
	}

	public void cut() {
		System.out.println("Cutting " + name);
	}

	public void box() {
		System.out.println("Boxing " + name);
	}

}

CheesePizza

public class CheesePizza extends Pizza {
	public CheesePizza() {
		name = "Cheese Pizza";
		dough = "Regular Crust";
		sauce = "Marinara Pizza Sauce";
		toppings.add("Fresh Mozzarella");
		toppings.add("Parmesan");
	}
}

PizzaStore

public class PizzaStore {
	SimplePizzaFactory factory;

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

	public Pizza orderPizza(String type) {
		Pizza pizza;

		**pizza = factory.createPizza(type);**

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

		return pizza;
	}
}

SimplePizzaFactory

public class SimplePizzaFactory {

	public Pizza createPizza(String type) {
		Pizza pizza = null;

		if (type.equals("cheese")) {
			pizza = new CheesePizza();
		} else if (type.equals("pepperoni")) {
			pizza = new PepperoniPizza();
		} else if (type.equals("clam")) {
			pizza = new ClamPizza();
		} else if (type.equals("veggie")) {
			pizza = new VeggiePizza();
		}
		return pizza;
	}
}

PizzaTest

public static void main(String[] args) {
	SimplePizzaFactory factory = new SimplePizzaFactory();
	PizzaStore store = new PizzaStore(factory);

	Pizza pizza = store.orderPizza("cheese");
	System.out.println("We ordered a " + pizza.getName() + "\\\\n");
	System.out.println(pizza);

	pizza = store.orderPizza("veggie");
	System.out.println("We ordered a " + pizza.getName() + "\\\\n");
	System.out.println(pizza);
}

한계

여전히 OCP 원칙을 위반한다. 위의 피자 예시에서 하와이안 피자를 추가해달라는 요구사항이 생기면, SimpleFactory의 createPizza()의 분기 처리 코드를 수정해야 한다.

 

Factory Method

핵심 의도

팩토리 메소드 패턴은 객체를 생성할 때 필요한 인터페이스를 만든다. 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정한다. 팩토리 메소드 패턴을 사용하면 클래스 인스턴스 만드는 일을 서브클래스에게 맡긴다.

 

적용 상황

팩토리로 생성해야 하는 객체가 여러 개로 늘어날 경우 사용한다. Simple Factory를 적용했을 때 지킬 수 없던 OCP를 지키면서 객체를 확장할 수 있다.

 

솔루션의 구조와 각 요소의 역할

객체에게 책임을 분할하기

제품을 사용하는 책임을 Creator에 할당한다. Creator는 내부에서 제품을 생성해야 하는데, 이 생성 부분은 바뀌는 부분이므로 책임을 분리하여 ConcreteCreator에게 할당한다. Creator는 제품 생성이 필요할 때마다 ConcreteCreator에게 메시지를 보낸다.

 

구현 포인트

팩토리 메소드에서 매개변수를 받아 ConcreteCreator에서 분기 처리하여 객체를 생성할 수 있다. 하지만 매개변수를 쓰지 않고 그냥 한 가지 객체만 만드는 경우도 많다.

객체를 new로 생성하지 않고는 자바 프로그램을 만들 수 없다. 그래도 생성 코드를 한 곳에 모아 놓고 체계적으로 관리하면, 객체 생성 코드를 보호하고 관리할 수 있다.

 

적용 예시

 

요구사항

뉴욕 스타일 피자를 파는 지점과 시카고 스타일 피자를 파는 지점을 추가했다. 각 지점은 동일하게 주문을 받지만, 만드는 피자의 종류가 다르다. NyStyleCheesePizza, NyStyleVeggiePizza, ChicagoStyleCheesePizza, ChicagoStyleClamPizza 등이 있다. 이때 피자를 생성하는 부분을 팩토리 메소드 패턴으로 분리하려고 한다.

 

설계

피자 객체를 사용하는 책임을 가진 PizzaStore 객체를 만든다. 피자를 사용하는 메소드는 orderPizza() 뿐인데, 피자 생성 책임을 외부에서 요청한다.

뉴욕 스타일 피자의 주문을 받는 NyPizzaStore과 시카고 스타일 피자의 주문을 받는 ChicagoPizzaStore를 만들었다. 각 지점의 피자가게는 피자 생성 요청을 수신하는 책임을 가진다.

 

코드

PizzaStore

public abstract class PizzaStore {

	abstract Pizza createPizza(String item);

	public Pizza orderPizza(String type) {
		Pizza pizza = createPizza(type);
		System.out.println("--- Making a " + pizza.getName() + " ---");
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}
}

NyStylePizzaStore

public class NYPizzaStore extends PizzaStore {

	Pizza createPizza(String item) {
		if (item.equals("cheese")) {
			return new NYStyleCheesePizza();
		} else if (item.equals("veggie")) {
			return new NYStyleVeggiePizza();
		} else if (item.equals("clam")) {
			return new NYStyleClamPizza();
		} else if (item.equals("pepperoni")) {
			return new NYStylePepperoniPizza();
		} else return null;
	}
}

PizzaTest

public class PizzaTestDrive {

	public static void main(String[] args) {
		PizzaStore nyStore = new NYPizzaStore();
		PizzaStore chicagoStore = new ChicagoPizzaStore();

		Pizza pizza = nyStore.orderPizza("cheese");
		System.out.println("Ethan ordered a " + pizza.getName() + "\\\\n");

		pizza = chicagoStore.orderPizza("cheese");
		System.out.println("Joel ordered a " + pizza.getName() + "\\\\n");

		pizza = nyStore.orderPizza("clam");
		System.out.println("Ethan ordered a " + pizza.getName() + "\\\\n");

		pizza = chicagoStore.orderPizza("clam");
		System.out.println("Joel ordered a " + pizza.getName() + "\\\\n");

		pizza = nyStore.orderPizza("pepperoni");
		System.out.println("Ethan ordered a " + pizza.getName() + "\\\\n");

		pizza = chicagoStore.orderPizza("pepperoni");
		System.out.println("Joel ordered a " + pizza.getName() + "\\\\n");

		pizza = nyStore.orderPizza("veggie");
		System.out.println("Ethan ordered a " + pizza.getName() + "\\\\n");

		pizza = chicagoStore.orderPizza("veggie");
		System.out.println("Joel ordered a " + pizza.getName() + "\\\\n");
	}
}

 

Abstract Factory

핵심 의도

추상 팩토리 패턴은 구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스를 제공한다. 구상 클래스는 서브클래스에서 만든다.

 

적용 상황

연관된 객체들을 한번에 생성하는 책임을 캡슐화할 때 사용한다.

 

솔루션의 구조와 각 요소의 역할

 

객체에게 책임을 분할하기

제품군을 생성하는 책임과 사용하는 책임을 할당해야 한다. 여기서 제품군 생성은 ConcreateFactory가, 제품군의 사용은 Client가 한다.

 

구현 포인트

추상 팩토리 패턴은 객체의 구성 방식, 팩토리 메소드 패턴은 상속으로 객체를 생성한다.

 

적용 예시

요구사항

지점별 피자가게에서 특정 지역별 재료만을 사용할 수 있도록 피자를 생성한다. 이때 재료들을 제품군으로 볼 수 있다.

 

설계

우선 제품군을 인터페이스와 구현체로 분리하여 생성한다. 그리고 제품군을 생성하는 책임을 PizzaIngredientFactory에 할당하고, NYPizzaIngredientFactory와 ChicagoPizzaIngredientFactory에서 재료들을 선택한다. 그리고 이렇게 생성된 재료들을 NYPizzaStore과 ChicagoPizzaStore에서 사용한다.

 

코드

PizzaIngredientFactory

public interface PizzaIngredientFactory {

	public Dough createDough();
	public Sauce createSauce();
	public Cheese createCheese();
	public Veggies[] createVeggies();
	public Pepperoni createPepperoni();
	public Clams createClam();

}

NYPizzaIngredientFactory

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {

	public Dough createDough() {
		return new ThinCrustDough();
	}

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

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

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

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

	public Clams createClam() {
		return new FreshClams();
	}
}

NYPizzaStore

public class NYPizzaStore extends PizzaStore {

	protected Pizza createPizza(String item) {
		Pizza pizza = null;
		PizzaIngredientFactory ingredientFactory =
			new NYPizzaIngredientFactory();

		if (item.equals("cheese")) {

			pizza = new CheesePizza(ingredientFactory);
			pizza.setName("New York Style Cheese Pizza");

		} else if (item.equals("veggie")) {

			pizza = new VeggiePizza(ingredientFactory);
			pizza.setName("New York Style Veggie Pizza");

		} else if (item.equals("clam")) {

			pizza = new ClamPizza(ingredientFactory);
			pizza.setName("New York Style Clam Pizza");

		} else if (item.equals("pepperoni")) {

			pizza = new PepperoniPizza(ingredientFactory);
			pizza.setName("New York Style Pepperoni Pizza");

		}
		return pizza;
	}
}

PizzaTest

public static void main(String[] args) {
	PizzaStore nyStore = new NYPizzaStore();
	PizzaStore chicagoStore = new ChicagoPizzaStore();

	Pizza pizza = nyStore.orderPizza("cheese");
	System.out.println("Ethan ordered a " + pizza + "\\\\n");
 }

'OOP > <헤드 퍼스트 디자인 패턴>, 에릭 프리먼 외' 카테고리의 다른 글

Command  (0) 2023.02.24
Singleton  (0) 2023.02.24
Decorator  (0) 2023.02.24
Observer  (0) 2023.02.24
Strategy  (0) 2023.02.24