[4장] Factory Pattern

ss0510s·2022년 7월 10일
0

Factory Pattern

객체 생성 부분을 추상화한 패턴으로, 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정한다.

OO 원칙

  • 추상화된 것에 의존하게 만들고, 구상 클래스에 의존하지 않게 만든다.

Dependency Inversion Principle(의존성 역전 원칙)

  • 변수에 구상클래스의 레퍼런스를 저장하지 않는다.
  • 구상클래스에서 유도된 클래스를 만들지 않는다.
  • 베이스 클래스에 이미 구현되어 있는 클래스를 오버라이드 하지 않는다.

피자 가게 운영 코드

피자 종류에 따라 객체를 생성하고, 그에 맞게 피자를 만드는 코드

기존 코드

Pizza orderPizza(String type) {
	Pizza pizza;
    
    if(type.equals("cheese") {
    	pizza = new CheesePizza();
    } else if(type.equlas("greek") {
    	pizza = new GreekPizza(); 
    } ....
    
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}
  • PizzaStore은 Pizza 객체에 직접 의존한다. -> Pizza 객체가 수정되면 PizzaStore까지 수정해야할 수 있다.
  • 구상클래스를 많이 사용하면 클래스가 추가될 때마다 코드를 수정해줘야 하므로 OCP 원칙을 위반한다. 따라서 인터페이스를 바탕으로 구현한다. (다형성)

Simple Factory

  • 바뀌는 부분: 피자 종류 - 객체 생성 부분을 캡슐화한다.
    => 객체 생성 코드를 팩토리 클래스에서 구현한다.

Simple Factory

  • 구상 Pizza 클래스를 직접 참조한다.
public class SimplePizzaFactory {
	public Pizza createPizza(String type){
    	Pizza pizza = null;
        
        // 객체 생성 - 인스턴스에 따라 객체 생성
        if(type.equals("cheese") {
    		pizza = new CheesePizza();
        } else if(type.equlas("greek") {
            pizza = new GreekPizza(); 
        } ....
        
        return pizza;
    }

}

Client

  • 팩토리를 사용하는 클라이언트로, Factory로 부터 피자 인스턴스를 받는다.
public class PizzaStore {
	SimplePizzaFactory factory; //팩토리 레퍼런스 저장
    
    public PizzaStore(SimplePizzaFactory factory){
    	this.factory = factory; // 팩토리 지정
    }
    public Pizza orderPizza(String type) {
    	Pizza pizza; // 피자 객체 생성
        
        pizza = factory.createPizza(type); // type에 따라 객체 생성
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
    	
        return pizza;
    }

}
  • Client에서 Pizza 구상 클래스에 직접 의존하지 않기 때문에 확장성이 좋지만, 여전히 OCP 원칙을 위배한다.

Factory Method Pattern

  • 객체를 생성할 때 필요한 인터페이스를 만들고, 어떤 클래스의 인스턴스를 만들지 서브클래스에서 결정한다.
  • 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정한다.
  • 사용하는 서브클래스에 따라 생성되는 객체 인스턴스가 결정된다.

Abstract Factory

  • 팩토리 메서드를 정의한다.
  • 추상 메서드를 통해 구체적인 인스턴스 생성을 서브 클래스로 넘겨준다.
  • 어떤 클래스가 생성되는지 알 수 없다.
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 item); // 추상 클래스로 지정, 지점마다 다름
}

Concrete Factory

  • 어떤 클래스의 인스턴스를 만들지 결정한다.
  • 각 서브 클래스마다 팩토리메서드를 원하는대로 구현할 수 있다.(다형성)
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;
	}
}

Product

  • 추상 클래스로 구현하여 구상 클래스에서 재정의한다.
public abstract class Pizza {
	String name;
	String dough;
	String sauce;
	ArrayList<String> toppings = new ArrayList<String>();
 
	void prepare() {
		System.out.println("Prepare " + name);
		System.out.println("Tossing dough...");
		System.out.println("Adding sauce...");
		System.out.println("Adding toppings: ");
		for (String topping : toppings) {
			System.out.println("   " + topping);
		}
	}
  
	void bake() {
		System.out.println("Bake for 25 minutes at 350");
	}
 
	void cut() {
		System.out.println("Cut the pizza into diagonal slices");
	}
  
	void box() {
		System.out.println("Place pizza in official PizzaStore box");
	}
 
	public String getName() {
		return name;
	}

	public String toString() {
		StringBuffer display = new StringBuffer();
		display.append("---- " + name + " ----\n");
		display.append(dough + "\n");
		display.append(sauce + "\n");
		for (String topping : toppings) {
			display.append(topping + "\n");
		}
		return display.toString();
	}
}

Concrete Product

public class NYStyleCheesePizza extends Pizza {

	public NYStyleCheesePizza() { 
		name = "NY Style Sauce and Cheese Pizza";
		dough = "Thin Crust Dough";
		sauce = "Marinara Sauce";
 
		toppings.add("Grated Reggiano Cheese");
	}
}

Test.java

public class PizzaTestDrive {
 
	public static void main(String[] args) {
		PizzaStore nyStore = new NYPizzaStore(); // Factory 객체 생성 - 다형성
 
 			
		Pizza pizza = nyStore.orderPizza("cheese"); // Product 객체 생성 - Factory에서 생성한 객체를 넘겨준다.
		System.out.println("Ethan ordered a " + pizza.getName() + "\n");
        
        pizza = nyStore.orderPizza("clam");
		System.out.println("Ethan ordered a " + pizza.getName() + "\n");
	}
}

고수준 구성요소인 PizzaStore와 저수준 구성요소인 Pizza 객체들이 모두 추상클래스인 Pizza 의존하게 된다. => 의존성 역전 원칙

** 고수준 구성요소는 다른 저수준 구성요소에 의해 정의되는 행동이 들어있는 구성요소를 뜻한다
ex) PizzaStore의 행동은 Pizza에 의해 결정된다.

추상 팩토리 패턴

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

  • 가게마다 원재료가 다를 경우 지역마다 구성요소를 다르게 구성해야 하므로 원재료를 생산하는 팩토리를 생성한다.

Abstract Factory

  • 서로 관련된 제품군을 생성하는 추상 인터페이스이다.
// 원재료를 생산하는 팩토리 interface
public interface PizzaIngredientFactory {
	public Dough createDough();
	public Sauce createSauce();
	public Cheese createCheese();
	public Veggies[] createVeggies();
	public Pepperoni createPepperoni();
	public Clams createClam();
}

Concrete Factory

  • 구상 팩토리는 서로 다른 제품군을 구현하고, 클라이언트는 제품이 필요하면 팩토리 가운데 필요한 것을 사용한다.
// 가게마다 원재료를 생산하는 팩토리 생성
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();
	}
}

// 원재료 interface
public interface Dough {
	public String toString();
}

Abstarct Class

public abstract class PizzaStore {
 
	protected 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;
	}
}

Client

  • 지역별 피자 가게 팩토리 생성
  • 추상 팩토리의 클라이언트는 PizzaStore의 인스턴스인 NYPizzaStore이다.
public class NYPizzaStore{
	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;
 	}
}

Abstract Product

public abstract class Pizza {
	String name;
	//원재료들
	Dough dough;
	Sauce sauce;
	Veggies veggies[];
	Cheese cheese;
	Pepperoni pepperoni;
	Clams clam;

	abstract void prepare();

	void bake() {
		System.out.println("Bake for 25 minutes at 350");
	}

	void cut() {
		System.out.println("Cutting the pizza into diagonal slices");
	}

	void box() {
		System.out.println("Place pizza in official PizzaStore box");
	}

	void setName(String name) {
		this.name = name;
	}

	String getName() {
		return name;
	}

	public String toString() {
		StringBuffer result = new StringBuffer();
		result.append("---- " + name + " ----\n");
		if (dough != null) {
			result.append(dough);
			result.append("\n");
		}
		if (sauce != null) {
			result.append(sauce);
			result.append("\n");
		}
		if (cheese != null) {
			result.append(cheese);
			result.append("\n");
		}
		if (veggies != null) {
			for (int i = 0; i < veggies.length; i++) {
				result.append(veggies[i]);
				if (i < veggies.length-1) {
					result.append(", ");
				}
			}
			result.append("\n");
		}
		if (clam != null) {
			result.append(clam);
			result.append("\n");
		}
		if (pepperoni != null) {
			result.append(pepperoni);
			result.append("\n");
		}
		return result.toString();
	}
}

Concrete Product

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

Test.java

public class PizzaTestDrive {
 
	public static void main(String[] args) {
		PizzaStore nyStore = new NYPizzaStore(); // pizza store 객체 생성
 
		Pizza pizza = nyStore.orderPizza("cheese");
		System.out.println("Ethan ordered a " + pizza + "\n");
		pizza = nyStore.orderPizza("pepperoni");
		System.out.println("Ethan ordered a " + pizza + "\n");
	}
}

장점

  • 클래스 생성과 사용의 로직을 분리하여 결합도를 낮추고, 정보 은닉이 가능하다.
  • 확장성이 높다.
  • 객체 생성 코드를 하나로 이동하여 단일책임원칙(ISP)를 만족한다.
  • 기존 코드를 수정하지 않고, 새로운 객체를 추가할 수 있기 때문에 개방-폐쇄원칙(OCP)를 만족한다.

단점

  • 제품마다 팩토리 클래스를 모두 구현해야하기 때문에 제품이 늘어날 때마다 서브 클래스 수가 증가한다.
  • 코드의 복잡성이 증가한다.

팩토리 메서드 패턴과 추상 팩토리 패턴

  • 팩토리메서드 패턴

    • 인자에 따라 객체의 종류가 결정된다.
    • 구체적인 객체 생성 과정을 하위 또는 구상 클래스에 옮기는 것이 목적이다.
    • 한 개의 메서드로 여러 개의 객체를 만들고, create 메서드가 Factory 클래스에 1개만 있다.
    • 메소드 레벨에서 포커스를 맞춘다.
    • 상속으로 객체를 생성한다. -> 클래스를 확장하고 메서드를 오버라이드해야 한다.
  • 추상팩토리 패턴

    • 한 팩토리에서 서로 연관된 여러 종류를 모두 지원한다.
    • 관련 있는 여러 객체를 구체적인 클래스에 의존하지 않게 하는 것이 목적이다.
    • 구상클래스에 의존하지 않고 여러 개의 관련된 객체를 하나의 팩토리로 묶는다.
    • 인자에 따라 관련된 객체들을 생성하는 팩토리 종류가 결정된다.
    • 클래스 레벨에서 포커스를 맞춘다.
    • 객체 Composition으로 생성한다.
    • 제품군에 제품을 추가해야하면 인터페이스를 수정해야한다.
profile
개발자가 되기 위해 성장하는 중입니다.

0개의 댓글