"헤드 퍼스트 디자인 패턴(개정판)"을 읽고 정리한 내용입니다.
4. 객체지향 빵 굽기
팩토리(Factory)
객체 생성을 처리하는 클래스를 팩토리(Factory)라고 부른다.
팩토리 메소드 패턴
팩토리 메소드 패턴(Factory Method Pattern)은 객체를 만들기 위한 인터페이스를 정의하지만, 어떤 클래스의 인스턴스를 만들지는 서브 클래스들이 결정하도록 하는 방법이다. 즉, 클래스의 인스턴스를 만드는 일을 서브 클래스에게 맡긴다.
디자인 원칙
- 애플리케이션에서 달라지는 부분을 식별하고 불변으로부터 그것들을 분리해라. (Identify the aspects of your application that vary and separate them from what stays the same.)
- 달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 캡슐화(encapsulate)한다. 그러면 나중에 바뀌지 않는 부분에는 영향을 미치지 않고 그 부분만 고치거나 확장할 수 있다.
- 구현이 아니라 인터페이스에 프로그래밍해라. (Program to an interface, not an implementation.)
- 특정 행동만을 목적으로 하는 클래스의 집합을 만들고, 이 클래스에서 구현한다. 그러면 슈퍼 클래스에서 구체적으로 구현하거나 서브 클래스 자체에서 별도로 구현하지 않아서 특정 구현에 의존하지 않는다.
- 상속보다는 구성이 더 낫다. (Favor composition over inheritance.)
- 여기서 구성(composition)은 행동을 상속받는 것 대신에, 올바른 행동 객체로 구성되어 행동을 부여받는 것을 말한다.
- 구성을 사용하면 유연성을 향상 시킬 수 있다. 알고리즘군을 별도의 클래스로 캡슐화 할 수 있을 뿐만 아니라, 구성 요소로 사용하는 객체에서 올바르게 행동 인터페이스를 구현하기만 한다면 실행 중에(runtime) 동적으로 행동을 바꿀 수도 있다.
- 상호작용하는 객체들 사이에는 느슨한 결합을 사용하도록 노력해라. (Strive for loosely coupled designs between objects that interact.)
- 느슨한 결합(Loose Coupling)은 객체들 사이의 종속성이 적다는 뜻이다. 즉, 객체들 사이의 유연성이 좋아진다. 대표적인 예로 "옵저버 패턴"이 있다.
- 주제(Subject)는 옵저버가 특정 인터페이스(Observer 인터페이스)를 구현한다는 사실만 알고있다. 즉, 주제는 옵저버가 무엇을 하는지 알 필요가 없다.
- 옵저버는 언제든지 새로 추가할 수 있다. (제거도 마찬가지이다.)
- 새로운 타입의 옵저버를 추가할 때도 주제를 수정할 필요가 없다.
- 주제와 옵저버는 서로 독립적으로 재사용할 수 있다.
- 주제나 옵저버가 달라져도 서로에게 영향을 미치지 않는다.
- 느슨한 결합(Loose Coupling)은 객체들 사이의 종속성이 적다는 뜻이다. 즉, 객체들 사이의 유연성이 좋아진다. 대표적인 예로 "옵저버 패턴"이 있다.
- 클래스는 확장에는 열려있어야 하지만, 변경에는 닫혀 있어야 한다. (Classes should be open for extension, but closed for modification.)
- 이 원칙은 OCP(Open-Closed Principle, 개방-폐쇄 원칙)이다.
- 기존 코드를 변경하지 않고 확장으로 새로운 행동을 추가해야 한다는 말이다.
- 추상화에 의존해라. 구상 클래스에 의존하지 마라.(Depend upon abstractions. Do not depend upon concrete classes.)
- 이 원칙은 DIP(Dependency Inversion Principle, 의존성 뒤집기 원칙)이다.
- 이 원칙을 지키기 위한 가이드 라인은 아래와 같다.
- 변수에 구상 클래스의 레퍼런스를 저장하지 마라.
- 구상 클래스에서 유도된 클래스를 만들지 마라.
- 베이스 클래스에 이미 구현되어 있는 메소드를 오버라이드하지 마라.
추상 팩토리 패턴
추상 팩토리 패턴(Abstract Factory Pattern)은 구체적인 구상 클래스를 명시하지 않고, 관련있거나 의존적인 객체의 군을 만들기 위한 인터페이스를 제공하는 방법이다.
예시
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
protected abstract Pizza createPizza(String type);
}
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("NYStyleCheesePizza");
} else if (item.equals("veggie")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("NYStyleVeggiePizza");
} else if (item.equals("clam")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("NYStyleClamPizza");
} else if (item.equals("pepperoni")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("NYStylePepperoniPizza");
}
return pizza;
}
}
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();
}
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
@Override
public Dough createDough() {
return new ThinCrustDough();
}
@Override
public Sauce createSauce() {
return MarinaraSauce();
}
@Override
public Cheese createCheese() {
return new ReggianoCheese();
}
@Override
public Veggies[] createVeggies() {
Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPerrer() };
return veggies;
}
@Override
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
@Override
public Clams createClam() {
return new FreshClams();
}
}
import java.util.ArrayList;
import java.util.List;
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("Cut the pizza into diagonal slices");
}
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 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();
}
}
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");
}
}