Open j03y14 opened 6 months ago
특정 구현을 바탕으로 프로그래밍 하지 않아야한다면, new 를 쓸 때 마다 결국 특정 구현을 사용하게 되는 것
아닌가요?
Duck duck;
if(picnic) {
duck = new MallardDuck();
} else if(hunting) {
duck = new DecoyDuck();
} else if(inBathTub){
duck = new RubberDuck();
}
구상 클래스의 인스턴스는 여러 개 있으며, 그 인스턴스의 형식은 실행 시 주어진 조건에 따라 결정되는 상황이다.
컴파일 전까지는 어떤 것의 인스턴스를 만들 지 알 수 없다. 이런 코드를 변경하거나 확장 할 때는 코드를 다시 확인하고 새로운 코드를 추가하거나 기존 코드를 제거해야한다. 즉, 관리와 갱신이 어려워지고, 오류가 생길 가능성도 높아진다.
‘new’ 키워드는 변화하는 무언가 때문에 문제가 있을 수 있다.
구상클래스를 많이 사용하면 새로운 구상 클래스가 추가될 때마다 코드를 고쳐야한다. 따라서 새로운 구상 형식을 써서 확장이 필요할 때 다시 열 수 있게 해줘야한다.
바뀌는 부분과 바뀌지 않는 부분을 찾아서, 분리해야한다.
function orderPizza(type: string) {
let pizza: Pizza;
if(type === "cheese"){
pizza = new CheesePizza();
} else if(type === "greek"){
pizza = new GreekPizza();
} else if(type === "pepperoni"){
pizza = new PepperoniPizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
// 메뉴가 추가 , 삭제 되는 경우 함수가 변경된다.
function orderPizza(type: string) {
let pizza: Pizza;
if(type === "cheese"){
pizza = new CheesePizza();
} else if(type === "pepperoni"){
pizza = new PepperoniPizza();
} else if(type === "clam"){
pizza = new ClamPizza();
} else if(type === "Veggie"){
pizza = new VeggiePizza();
}
// 아래 부분은 변경되지 않는다.
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
문제가 생기는 부분은 인스턴스를 만드는 구상 클래스를 선택하는 부분이다.
객체 생성을 처리하는 부분 캡슐화하고, 이를 팩토리 라고 부릅니다.
class SimplePizzaFactory {
public createPizza(type: string): Pizza {
let pizza = null;
if(type === "cheese"){
pizza = new CheesePizza();
} else if(type === "pepperoni"){
pizza = new PepperoniPizza();
} else if(type === "clam"){
pizza = new ClamPizza();
} else if(type === "Veggie"){
pizza = new VeggiePizza();
}
return pizza;
}
}
피자 생성 부분(변하는 부분)을 캡슐화 하는 것 만으로 재사용 성이 높아지고 변화가 일어났을 때 변경해야하는 부분이 좁아집니다.
class PizzaStore {
factory: SimplePizzaFactory
constructor(factory: SimplePizzaFactory){
this.factory = factory;
}
orderPizza(type: string):Pizza{
const pizza = factory.createPizza(type);
pizza.prepre();
// ...
return pizza;
}
}
...
→ 객체 구성을 활용하면, 행동을 실행할 때 구현된 객체를 동적으로 바꿀 수 있다. 이를 어떻게 활용할까!
지금 한게 간단한 팩토리라고 말한건가…?
class PizzaStore() {
orderPizza(type: string) {
const pizza = createPizza(type);
pizza.prepare();
pizza.bake();
//...
return pizza;
}
createPizza(type: string)() {
throw new Error('it is abstract method');
}
}
class NYPizzaStore extends PizzaStore() {
createPizza(type: string){
if(type === 'cheese'{}
//...
}
}
class Pizza {
name: string;
dough: string;
sauce: string;
toppings: string[];
constructor() {
}
prepare() {
console.log('');
}
//...
cut() {
console.log('');
}
//...
}
class NYStylePizza() extends Pizza {
constructor() {
this.name = 'new york style pizza';
// ...
}
}
nyPizzaStore = new NYPizzaStore();
nypizzaStore.orderPizza('cheese');
객체를 생성할 때 필요한 인터페이스를 만듭니다. 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정합니다. 팩토리 메소드 패턴을 사용하면 클래스 인스턴스 만드는 일을 서브클래스에게 맡기게 됩니다.
사용하는 서브클래스에 따라 생산되는 객체 인스턴스가 결정됩니다.
디자인 원칙: 추상화 된것에 의존하게 만들고, 구상 클래스에 의존하지 않게 만든다.
“구현보다 인터페이스에 맞춰서 프로그래밍한다”와 비슷하지만 추상화를 더 강조합니다.
나쁜 예시에서 PizzaStore는 구상 피자 클래스가 변경되면 바뀌어야 하므로 구상 클래스에 의존합니다.
팩토리 메소드 패턴을 사용하면 PizzaStore는 추상화 된 Pizza 클래스에 의존하고, 나머지 구상 피자 클래스들도 Pizza 클래스에 의존하게 됩니다.
구상클래스에 거의 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스를 제공합니다.
어디서 어떻게 구성을 쓴거지…?
심플 팩토리는 객체 생성 부분만 떼어내서 캡슐화하고, 이를 팩토리라고 부른다. 팩토리 메소드 패턴은 객체를 생성하는 "팩토리 메소드"를 자녀 클래스에서 구현하는 방식(상속) 추상 팩토리 패턴은 다수의 팩토리 메소드 묶음을 " 추상 팩토리 클래스"로 표현하는 방식
new를 사용하면 구상 클래스의 인스턴스가 만들어진다.
인터페이스에 맞춰서 코딩하면 변경에 닫혀있고 확장 가능한 코드를 작성할 수 있다.
바뀌는 부분을 찾아서 바뀌지 않는 부분과 분리해야 한다는 원칙을 생각해보자.
가장 문제점은 인스턴스를 만드는 구상 클래스를 선택하는 부분이다. 객체 생성 부분을 orderPizza에서 분리하자.
이 객체 생성을 담당하는 부분을 팩토리라고 한다.
이런 방식을 간단한 팩토리라고 한다.
여러 스타일의 피자를 만들고 싶다. 그리고 지점마다 굽는 방식, 자르는 방식, 포장하는 방식이 다르다.
팩토리 메서드: 팩토리 메서드로 객체 생성을 서브클래스에게 위임. 슈퍼 클래스와 클라이언트 코드를 분리할 수 있다.
팩토리 메서드 패턴
서브 클래스에서 어떤 클래스를 만들지 결정해서 객체 생성을 캡슐화 한다. 생잔자 클래스는 실제 생산될 product에 대해서 전혀 모르고 서브클래스에서 결정된다.
의존성 뒤집기 원칙
구상 클래스에 대한 의존성을 줄이면 좋다.
생각 뒤집기.
추상 팩토리를 도입해서 피자 종류에 맞는 원재료군을 생산하는 방법을 구축.
추상 팩토리 패턴
구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스를 제공한다.
추상 팩토리 패턴에 팩토리 메서드 패턴이 있는 것 아닌가? 맞다.
단지 팩토리 메서드 패턴에서는 하나를 만드는데 여기는 여러개를 만드니 인터페이스를 만든 것 같다.