해당 포스팅은 한빛 미디어 헤드퍼스트 디자인 패턴(에릭 프리먼, 엘리자베스 롭슨 저)를 통해 공부한 내용을 정리한 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
좋은 프로그래머는 자기 두뇌를 사용한다. 그러나 좋은 가이드라인은 모든 케이스를 고려해야만 하는 노력을 줄여준다.
(Francis Glassborow, 개발자)
디자인 패턴의 설계
느슨한 결합으로 객체지향 디자인
- 객체의 인스턴스는 항상 공개 되어야 하는 것이 아니며 모든 것을 공개 했다가는 문제가 생길 수도 있음
- new 연산자가 구상을 생각!!
- new를 사용하면 구상 클래스의 인스턴스가 만들어짐
- 이는 구상 클래스를 바탕으로 코드를 수정해야 할 가능성이 커지고 유연성이 떨어짐.
Duck duck = new MallardDuck()
new의 문제?
- 구상 클래스 간의 변화되는 부분이 문제!
- 인터페이스에 맞춰 코딩하면 변화에 대응이 가능함. ⇒ 다형성
- 변경에는 닫혀있고 확장에는 열려있어야한다.
피자 코드 설계
public class Pizza {
public Pizza() {
}
Pizza orderPizza(String type) {
Pizza pizza;
if (type.equals("cheese")) {
pizza = new ChesePizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
- pizza는 type에 따라 구상 클래스의 인스턴스를 생성.
- 사라지는 피자와 새로 생기는 피자가 생기면 코드 지우고 코드 새로 추가해야됨.
객체 생성 부분 캡슐화(변화가 생기는 부분)
- 객체 생성을 처리하는 클래스를 팩토리라고 함.
- SimplePizzaFactory가 OrderPizza()메소드가 새로 만든 객체의 클라이언트 생성해줌.
public class SimplePizzaFactory {
Pizza create(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}
return pizza;
}
}
- 이러한 방법은 static factory라고 부름
- 객체 생성 메소드를 실행하려고 객체의 인스턴스를 만들지 않아도 되기 때문.
Client Code 수정하기
객체의 팩토리 인스턴스 저장하기
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
public class SimplePizzaFactory {
public SimplePizzaFactory() {
}
Pizza create(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}
return pizza;
}
}
- 각각의 지점들은 굽는 방식이 달라지거나 피자의 토핑이 달라질 수 있음.
- PizzaStore를 만들어 각각 스타일대로 구현
다양한 팩토리 만들기
다양한 스타일의 피자 가게가 있음.
public abstract class PizzaStore {
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;
}
}
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;
}
}
public class ChicagoPizzaStore extends PizzaStore {
Pizza createPizza(String item) {
if (item.equals("cheese")) {
return new ChicagoStyleCheesePizza();
} else if (item.equals("veggie")) {
return new ChicagoStyleVeggiePizza();
} else if (item.equals("clam")) {
return new ChicagoStyleClamPizza();
} else if (item.equals("pepperoni")) {
return new ChicagoStylePepperoniPizza();
} else return null;
}
}
// 뉴욕식 치즈피자
PizzaStore nyStore = new NYPizzaStore();
nyStore.orderPizza("cheese");
// 시카고식 치즈피자
PizzaStore chicagoStore = new ChicagoPizzaStore();
chicagoStore.orderPizza("cheese");
팩토리 메소드 패턴
- 팩토리 패턴은 객체 생성(Pizza)을 캡슐화
- 팩토리 메서드 패턴은 서브클래스(NYPizzaStore, ChicagoPizzaStore)에서 어떤 클래스를 만들지 정하므로, 객체 생성을 캡슐화
- 제품 클래스(Pizza) 와 생산자 클래스(PizzaStore)를 분리함으로써 구체적인 구현은 구상 클래스가 책임 지도록.
- 특징
- 캡슐화: 객체 생성 로직을 팩토리 클래스나 메서드로 분리하여 외부에서 직접 객체를 생성하지 않도록 함.
- 유연성: 객체 생성 방식을 변경할 때 클라이언트 코드를 수정할 필요 없이 팩토리 클래스만 수정하면 됨.
- 확장성: 새로운 종류의 객체를 추가할 때도 기존 클라이언트 코드에 영향을 주지 않고 팩토리 클래스만 확장하면 됨.
- OCP: 하나의 클래스에 추가적인 기능을 넣어도 다른클래스의 변경을 하지 않아도된다.
💡 팩토리 메서드 패턴
객체를 생성할 때 필요한 인터페이스(PizzaStore)를 만들고 어떤 클래스의 인터페이스를 만들지는
서브 클래스(NYPizzaStore, ChicagoPizzaStore)에서 결정하도록 함.
💡 Simple Factory VS Factory Method
팩토리 메소드 패턴이 간단한 팩토리와 상당히 비슷하지만 간단한 팩토리는 일회용 처방에 불과한 반면, 팩토리 메소드 패턴을 사용하면 여러번 재사용이 가능한 프레임워크를 만들 수 있습니다. 예를 들어, 팩토리 메소드 패턴의 orderPizza( ) 메소드는 피자를 만드는 일반적인 프레임워크를 제공합니다. 그 프레임워크는 팩토리 메소드 피자 생성 구상 클래스를 만들었죠. PizzaStore 클래스의 서브 클래스를 만들 때, 어떤 구상 제품 클래스에서 리턴할 피자를 만들지를 결정합니다. 이 프레임워크를 간단한 팩토리와 한번 비교해 보세요. 간단한 팩토리는 객체 생성을 캡슐화하는 방법을 사용하긴 하지만 팩토리 메소드만큼 유연하지는 않습니다. 생성하는 제품을 마음대로 변경할 수 없기 때문입니당. => (SimpleFactory라는 인스턴스를 가지고 있기 때문)
의존성 뒤집기 원칙(Dependency Inversion)
- 구상 클래스의 의존성을 떨어뜨리기
- 즉, 고수준 구성요소는 저수준 구성요소에 의해 정의되는 행동이 들어있는 구성요소
- PizzaStore는 고수준 구성요소
- Pizza 클래스는 저수준 구성요소.
💡 추상화된 것에 의존하게 만들고, 구상 클래스에 의존하지 않게 만든다.
- DI를 지키는 방법
- 변수에 구상 클래스의 레퍼런스를 저장하지 않는다.
- 구상 클래스에서 유도된 클래스를 만들지 말자.
- 베이스 클래스에 이미 구현되어 있는 메소드를 오버라이드하지 말자.
추상 팩토리 패턴
💡 구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스 제공.
팩토리 메소드 패턴 VS 추상 팩토리 패턴
비교\패턴 | 팩토리 메소드 패턴 | 추상 팩토리 패턴 |
공통점 | 애플리케이션을 특정 구현으로부터 분리. | |
제품 생성 | 클래스를 사용 | 객체를 사용 |
제품 생성 방식 | 상속 | 구성 |
제품 생성 방법 | 객체를 생성하고 클래스를 확장할 때에는 팩토리 메소드를 오버라이드하여 서브 클래스에서 객체를 생성 | 제품군을 만드는 추상 형식을 제공. 인스턴스를 만든 다음 추상 형식을 써서 만든 코드에 전달. |
'기타 > 디자인패턴' 카테고리의 다른 글
[Design Pattern] Command Pattern (2) | 2024.06.03 |
---|---|
[Design Pattern] Singleton Pattern (0) | 2024.05.31 |
[Design Pattern] Decorator Pattern (0) | 2024.05.28 |
[Design Pattern] Observer Pattern (0) | 2024.05.27 |
[Design Pattern] Strategy Pattern (0) | 2024.05.27 |