Goal
- 간단한 팩토리에 대해 알아본다
- 샘플 코드를 통한 객체의 캡슐화를 간단한 팩토리로 변경해본다
- 다양한 팩토리 만들어보기
- new 연산자에 대한 고찰
간단한 팩토리 (Simple Factory)
디자인 패턴이라기 보다는 프로그래밍에서 자주쓰이는 관용구에 가깝다.
왜 or 언제 간단한 팩토리 사용하는가?
객체 생성을 처리하는 부분을 Factory라고 부르며, 해당 부분을 캡슐화하여 분리할 수 있기 때문에 코드 가독성이나 관리 측면에서 용이하다
다이어그램
최첨단 피자 코드 만들기
as - is 피자 주문 코드
public Pizza orderPizza(String type) {
Pizza pizza = new Pizza();
// 바뀌는 코드
if(type.equals("cheese")){
pizza = new CheesePizza();
}else if(type.equals("greek")){
pizza = new GreekPizza();
}else if(type.equals("peperoni")){
pizza = new PeperoniPizza();
}
// 바뀌지 않는 코드
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
=> 피자 종류를 바탕으로 type을 받아 구상 클래스의 인스턴스를 만들고 pizza 인스턴스 변수에 그 인스턴스를 대입한다.
문제점 : 피자 종류가 추가 또는 변경될 때마다 바뀌는 코드를 계속 수정해야함.
to-be 피자 주문 코드
객체 생성 부분 캡슐화
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
pizza = factory.createPizza(type);
orderPizza()에서 가장 문제가 되는 부분인 인스턴스를 만드는 구상 클래스를 선택하는 부분으로, 바뀌는 코드를 캡슐화한다.
객체 생성을 처리하는 클래스 Factory - SimplePizzaFactory
public class SimplePizzaFactory {
public Pizza createPizza(String type){
Pizza pizza = null;
// orderPizza()에서 뽑아온 코드
// 전달 받은 매개변수로 피자 종류를 결정
if(type.equals("cheese")){
pizza = new CheesePizza();
}else if(type.equals("peperoni")){
pizza = new PeperoniPizza();
}else if(type.equals("clam")){
pizza = new ClamPizza();
}else if(type.equals("veggie")){
pizza = new VeggiePizza();
}else {
pizza = null;
}
return pizza;
}
}
- 이 클래스에서 하는 일은 클라이언트가 받을 피자만 만드는 일만한다.
- SimplePizzaFactory를 만들고 나면 orderPizza() 메소드는 새로 만든 객체의 클라이언트가 된다.
- 즉, 새로운 만든 객체를 호출하게 되는것 => 피자가 필요할 때마다 피자 공장에 피자 만들어 달라고 부탁하면 된다.
- orderPizza() 메소드에서 어떤 피자를 만들지 고민하지 않아도 됨
클라이언트 코드
public class PizzaStore {
// PizzaStore에 SimplePizzaFactory의 레퍼런스를 저장
SimplePizzaFactory factory;
// PizzaStore의 생성자에 팩토리 객체가 저장됨
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
// orderPizza() 메소드는 팩토리로 피자 객체를 만들고, 주문받은 형식을 이 쪽으로 전달하면 됨
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
- new 연산자 대신 팩토리 객체에 있는 create 메소드를 사용하여, 더 이상 구상 클래스의 인스턴스를 만들 필요가 없다
다양한 팩토리 만들기
상황
위의 피자가게가 대박이나서 뉴욕, 시카고, 캘리포니에서도 지점을 오픈하고자한다.
요구 사항
각 지점마다 그 지역의 특성과 입맛(뉴욕, 시카고, 캘리포니아 스타일) 을 반영한 다양한 스타일의 피자를 만들어야 한다.
해결 방안
SimpleFactory를 삭제하고 3가지의 서로 다른 다양한 팩토리(뉴욕 피자 팩토리, 시카고 피자 팩토리, 캘리포니아피자 팩토리)를 만들어 PizzaStore에서 적당한 팩토리를 사용한다.
SimplePizzaFactory 대신, 지역 별 PizzaFactory만들기
PizzaStore nyStore = new NYPizzaStore(); // 뉴욕 지점
PizzaStore chicagoStore = new ChicagoPizzaStore(); // 시카고 지점
하지만 지점들을 제대로 관리하기 위해, PizzaStore와 피자 제작 코드 전체를 하나로 묶어주는 프레임을 만들어야 한다.
- 굽는 방식이 달라진다거나, 이상하게 생긴 피자 상자를 쓴다거나 컷팅을 까먹는 일 발생
- SimplePizzaFactory를 만들기 전에 썼던 코드에는 피자를 만드는 코드가 PizzaStore와 직접 연결되었지만, 유연성 x
=> 팩토리 메소드 패턴을 사용하여 극복한다.
new 연산자
new를 사용하면 구상 클래스의 인스턴스가 만들어진다. 인터페이스가 아닌, 특정 구현을 사용하는 것이다.
new 권장 코드
구상 클래스를 바탕으로 코딩하면 나중에 코드를 수정해야 할 가능 성이 커지고, 유연성이 떨어지므로 인터페이스를 사용한다.
Duck duck = new MallardDuck(); // 권장 o
MallardDuck duck = new MallardDuck(); // 권장 x
new 의 문제
new의 문제가 아닌 '변화'의 문제로, 변화하는 무언가 때문에 new를 조심해서 사용해야한다.
- 인터페이스에 맞춰 코딩하면 시스템의 여러 변화에 대응할 수 있다
- 인터페이스 바탕 코드는 어떤 클래스든 특정 인터페이스만 구현하면 사용할 수 있기 때문 => 다형성
- 구상 클래스를 많이 사용하면 새로운 구상 클래스가 추가될 때마다 코드를 고쳐야함
- 변경에 닫혀있는 코드가 됨. 새로운 구상 형식을 써서 확장 해야할 때는 다시 열 수 있게 해야함 => OCP 개방폐쇄의 원칙
참고
new로 객체를 사용하면 RDMS에서 not null인 필드의 경우도 null로 생성이 되는 문제가 발생한다.
그래서 이 객체를 생성하는 대부분의 경우에는 빌더패턴을 사용하는 것을 권장한다.
참고
헤드퍼스트 디자인 패턴
'JAVA > 디자인 패턴' 카테고리의 다른 글
[Factory Pattern] 팩토리 패턴 - 추상 팩토리 (0) | 2023.07.10 |
---|---|
[Factory Pattern] 팩토리 패턴 - 팩토리 메소드 (0) | 2023.07.09 |
[Strategy Pattern] 전략 패턴 (1) | 2023.07.06 |
[Decorator Pattern] 데코레이터 패턴 (0) | 2023.07.04 |
[Proxy Pattern] 프록시 패턴 - 보호 프록시 (0) | 2023.07.04 |