북극곰의 개발일기

추상 팩토리 패턴(abstract Factory Pattern)





posted by purplebeen on Wed Jan 31 2018 00:53:16 GMT+0900 (KST) in JAVA


지난번에 다루었던 팩토리 메소드 패턴에 이어서 이번에는 추상 팩토리 패턴을 다뤄 볼려고 한다.

팩토리패턴

추상 팩토리 패턴 : 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있게 한다.

객체지향 디자인 원칙

  1. 바뀌는 부분을 캡슐화한다.
  2. 상속보다는 객체 구성을 활용한다.
  3. 구현이 아닌 인터페이스에 맞춰서 프로그래밍한다.
  4. 서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 활용해야 한다.
  5. 클래스는 확장에 대해서는 열려있지만 변경에 대해서는 닫혀 있어야 한다.(OCP)
  6. 추상화된 것에 의존하라. 구상 클래스에 의존하지 않도록 한다. (의존성 뒤집기 원칙, Dependency Inversion Principle)

의존성 뒤집기 : 객체지향 디자인을 할때 일반적으로 생각하는방법과는 반대로 뒤집어서 생각한다는 것. 고수준 모듈과저수준 모듈이둘 다 하나의 추상 클래스에 의존함.

의존성 뒤집기 원칙을 지키는 데 도움이 될만한 가이드라인

  • 어떤 변수에도 구상 클래스에 대한 레퍼런스를 저장하지 말자.
  • 구상 클래스에서 유도된 클래스를 만들지 말자.
  • 베이스 클래스에 이미 구현되어 있던 메소드를 오버라이드 하지 말자.

피자가게의 지점이 여러 곳으로 늘어남에 따라서 원재료도 관리를 해야 하기 시작했다.

뉴욕 지점에서 쓰는 원재료가 다를 거고, 시카고 지점에서 쓰는 원재료가 다를 것이다. 이를 관리하기 위해서 먼저 원재료 공장을 만들어 보자.

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

재료를 생산하는 인터페이스를 만들었다.

이제 그러면 이를 뉴욕 지점에 맞는 팩토리를 만들어서 구현해보자

public class NYPizzaIngredientFactory Implements PizzaIngredientFactory {
	public Dough createDough() {
		return new ThinCrustDough();
	}

	public Sauce createSauce() {
		return new MarinaraSauce();
	}
	public Cheese createCheese() {
		return new ReggianCheese();
	}
	public Veggies[] createVeggies() {
		Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom(),
 new RedPepper() };
		return veggies;
	}
	public Pepperoni createPepporoni() {
		return new SlicedPepporoni();
	}
	public Clams createClam() {
		return new FreshClams();
	}
}

이번에는 시카고 지점의 재료 팩토리를 만들어보자.

public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactroy {
	public Dough createDough() {
		return new ThickCreateDough();
	}
	public Sauce creteSauce() {
		return new PlumTomatoSauce();
	}
	public Cheese createCheese() {
		return new Mozzarella();
	}
	public Pepperoni createPepperoni() {
		return new SlicedPepperoni();
	} 
	public Clam createClam() {
		return new FrozenClam();
	}
	public Veggies[] createVeggies() {
		Veggies veggies = { new Eggplants(), new Spinach(), new BlackOlives() );
		return veggies;
	}
	
}

각 재료 인터페이스를 만들어 보자.

public Interface Dough() {
	public String toString();
}
public Interface Sauce() {
	public String toString();
}
public Interface Cheese() {
	public String toString();
}
public interface Veggies {
	public String toString();
}
public Interface Clam() {
	public String toString();
}

각 재료군들의 하위클래스를 만들어 보자.

public class ThinCrustDough implements Dough{
	@Override
	public String toString() {
		return "ThinCrustDough";
	}
}
public class ThickCrustDough implements Dough{
	@Override
	public String toString() {
		return "ThickCrustDough";
	}
}
public class MarinaraSauce implements Sauce {
	@Override
	public String toString() {
		return "MarinaraSauce"
	}
}
public class PlumTomatoSauce implements Sauce {
	@Override
	public String toString() {
		return "PlumTomatoSauce"
	}
}
public class ReggianoCheese implements Cheese {
	@Override
	public String toString() {
		return "ReggianoCheese"
	}
}
public class Mozzarella implements Cheese {
	@Override
	public String toString() {
		return "ReggianoCheese"
	}
}
public class SlicedPepporoni implements Pepperoni {
	@Override
	public String toString() {
		return "SlicedPepporoni"
	}
}
public class FreshClams implements Clam {
	@Override
	public String toString() {
		return "FreshClams"
	}
}
public class FrozenClams implements Clam {
	@Override
	public String toString() {
		return "FrozenClams"
	}
}
public class Garlic implements Veggies {
	@Override
	public String toString() {
		return "Garlic"
	}
}
public class Onion implements Veggies {
	@Override
	public String toString() {
		return "Onion"
	}
}
public class Mushroom implements Veggies {
	@Override
	public String toString() {
		return "Mushroom"
	}
}
public class RedPepper implements Veggies {
	@Override
	public String toString() {
		return "RedPepper"
	}
}
public class Eggplant implements Veggies {
	@Override
	public String toString() {
		return "Eggplant"
	}
}
public class Spinach implements Veggies {
	@Override
	public String toString() {
		return "Spinach"
	}
}
public class BlackOlives implements Veggies {
	@Override
	public String toString() {
		return "BlackOlives"
	}
}

자 이제 그러면 이에 맞게 Pizza Class를 수정해보자.

package FactoryPattern;

import java.util.ArrayList;

public abstract class Pizza {
	String name;
	Dough dough;
	Sauce sauce;
	Veggies veggies[];
	Cheese cheese;
	Pepperoni pepperoni;
	Clams clam;
	ArrayList toppings = new ArrayList();
	
	public void prepare() {
		System.out.println("Preparing " + name);
		System.out.println("Tossing dough....");
		System.out.println("Adding Sauce....");
		System.out.println("Adding toppings...");
		for(int i = 0; i < toppings.size(); i++) {
			System.out.println("   " + toppings.get(i));
		}
	}
	
	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 void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	
	public String toString() {
		String str = "The cheese pizza includes " + dough.toString() + " dough and " + cheese.toString() + " cheeses and " + sauce.toString() + " sauces";
		System.out.println(str);
		return str;
		
	}
}

자 이제 그러면 CheesePizza를 수정해보자.

package FactoryPattern;

public class CheesePizza extends Pizza {
	PizzaIngredientFactory ingredientFactory;
	public CheesePizza(PizzaIngredientFactory ingredientFactory) {
		this.ingredientFactory = ingredientFactory;
	}
	void prepare() {
		System.out.println("Preparing" + name);
		dough = ingredientFactory.createDough();
		sauce = ingredientFactory.createSauce();
		cheese = ingredientFactory.createCheese();
	}

}

Ingredient Factory 객체를 받아와서 그 객체를 통해서 dough, sauce, cheese를 설정할 수 있게 변화 시켰다.

이렇게 되면 각 지점에 따라 재료가 바뀌더라도 객체 구성을 사용하였기 때문에 변경에 유연하게 대처할 수 있다.

각 재료들을 설명해준느 toString() 메소드가 작동할 수 있도록 PizzaStore를 수정해주자.

package FactoryPattern;

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

이제 NYPizzaStore를 수정시켜보자.

package FactoryPattern;

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("NewYork Style Cheese Pizza");
		}
		return pizza;
	}
	

}

PizzaIngredientFactory 형태의 객체 NYPizzaIngredientFactory를 생성해서 item이 cheese일 경우

새로운 CheesePizza객체를 생성하며 생성자에 인자값으로 ingredientFactory를 넘겨주고 있다.

자 이제그러면 피자가 제대로 생성 되는지 테스트를 해보자.

package FactoryPattern;

public class PizzaTestdrive {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		PizzaStore nyStore = new NYPizzaStore();
		nyStore.orderPizza("cheese");
	}

}

NYPizzaStore 객첼르 생성한 뒤 orderPizza()메소드에 cheese라는 인자값을 넘겨 주어

뉴욕 치즈 피자가 만들어지도록 설정하였다. 실행시켜보면,

다음과 같이 각 재료들이 잘 조합되어 나오는것을 볼 수 있다.