팩토리메서드
정의- 객체 생성을 처리하며, 팩토리 메서드를 이용하면 객체를 생성하는 작업을 서브클래스에 캡슐화시킬수 있다.
수퍼클래스에 있는 클라이언트 코드와 서브클래스에 있는 객체 생성 코드를 분리시킬수 있다.
팩토리 메서드 선언
abstract Product factoryMethod(String type)
abstract
- 팩토리 메서드는 추상 메서드로 선언하여 서브 클래스에서 객체 생성을 책임지도록
Product
- 팩토리 메서드에서 특정제품(객체)를 리턴하며, 그 객체를 보통 수퍼클래스에서 정의한 메서드 내에서 쓰임
factoryMethod
- 팩토리 메서드는 클라이언트(수퍼클래스에 있는 orderPizza() 같은 코드)에서 실제로 생성되는 구상 객체가 무엇인지 알수 없게 만드는 역할
String type
- 팩토리 메서드를 만들 때에 매개변수를 써서 만들어낼 객체 종류를 선택할 수 도 있다.
정적팩토리메서드(static factory)
-> 객체를 생성하기 위해 메소드를 실행시키기 위해서 객체의 인스턴스를 만들지 않아도 된다.
대신 서브 클래스를 만들어서 객체 생성 메서드의 행동을 변경시킬 수 없다.
인터페이스를 구현의 의미는
implements 키워드를 써서 구현하는 클래스를 만든다고 생각하면안된다.
일반적으로 어떤 상위형식(클래스 or 인터페이스)에 있는 메서드를 구현하는 구상 클래스는
그 상위 형식의 인터페이스를 구현하는 클래스라고 생각하면된다.
팩토리 메서드 패턴
-> 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만든다.
클래스의 인스턴스를 만드는 일을 서브 클래스에 맡기는 것.
장점)
객체 생성코드를 전부 한 객체 또는 메서드에 집어넣으면 코드에서 중복된 내용을 제거할 수 있고, 나중에 관리할때도 한 군데에만 신경쓰면된다.
클라이언트 입장에서는 객체 인스턴스를 만들 때 필요한 구상 클래스가 아닌 인터페이스만 필요로 하게된다.
구현이 아닌 인터페이스를 바탕으로 프로그래밍하여 유연성과 확장성에 용이하다.
Dependency Inversion Principle
의존성역전의 원칙
추상화된 것에 의존하도록 만들어라.
구상클래스에 의존하도록 만들지 않도록 한다.
예)
고수준 구성요소 (PizzaStore)
저수준 구성요소 (Pizaa)
PizzaStore 클래스는 구상 Pizza 클래스에 의존함
따라서 구상클래스처럼 구체적인것이 아닌 추상클래스나 인터페이스와 같이 추상적인 것에 의존하는 코드를 만들어야한다.
고수준 모듈과 저수준 모듈에 모두 적용될수 있다.
팩토리 메서드 패턴을 이용하면 인스턴스를 만드는 부분을 뽑아낼 수 있다.
PizzaStore -> Pizza -> NYStyleCheesePizza, ChicagoCheesePizza
고수준 구성요소인 PizzaStore와 저수준 구성요소인 피자 객체들이 모두 추상클래스인 Pizza에 의존하게 된다.
가이드라인
어떤 변수에도 구상 클래스에 대한 레퍼런스를 저장하지 않는다.
-> new 연산자를 사용하면 구상클래스에 대한 레퍼런스를 사용하게 된다. 팩토리를 써서 구상 클래스에 대한 레퍼런스를 변수에 저장하는 일을 미리 방지한다.
구상클래스에서 유도된 클래스를 만들지 않는다.
-> 구상클래스에서 유도된 클래스를 만들면 특정 구상클래스에 의존하게 된다. 인터페이스나 추상 클래스처럼 추상화된 것으로부터 클래스를 만들어야 한다.
베이스 클래스에 이미 구현되어 있던 메서드를 오버라이드하지 않는다.
->이미 구현되어 있는 메서드를 오버라이드한다는 것은 애초부터 베이스 클래스가 제대로 추상화된것이 아니였다고 볼수 있다. 베이스 클래스에서 메서드를 정의할 때는 모든 서브 클래스에 공유할 수 있는 것만 정의해야 한다.
예제)
public class PizzaStore {
public Pizza orderPizza() {
Pizza pizza = new Pizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
피자주문에서 피자
public Pizza orderPizza(String type) {
Pizza pizza = null;
// 객체 생성부분을 캡슐화
if("cheese".equals(type)) {
pizza = new CheesePizza();
} else if ("greek".equals(type)) {
pizza = new GreekPizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
여러개의 피자클래스가 필요
public Pizza orderPizza(String type) {
Pizza pizza = null;
pizza = simplePizzaFactory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
public class SimplePizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza = null;
if("cheese".equals(type)) {
pizza = new CheesePizza();
} else if ("greek".equals(type)) {
pizza = new GreekPizza();
}
return pizza;
}
}
심플 팩토리 만듬
심플팩토리는 디자인 패턴이 아니다.
//추상 클래스변경
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza = null;
//팩토리객체가 아닌 메서드로 바꿈
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
//팩토리 메서드가 추상메서드로 바뀜
protected abstract Pizza createPizza(String type);
}
팩토리 메서드 선언
public class NYPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
Pizza pizza = null;
if("cheese".equals(type)) {
pizza = new NYStyleCheesePizza();
} else if ("greek".equals(type)) {
pizza = new NYStyleGreekPizza();
}
return pizza;
}
}
하위클래스에서 객체생성
피자클래스
public abstract class Pizza {
String name;
String dough;
String sauce;
List<String> toppings = new ArrayList<>();
public void prepare() {
System.out.println("준비중 " + name);
System.out.println("반죽..." );
System.out.println("소스..." );
System.out.println("토핑..." );
for (String topping: toppings) {
System.out.println(" " + topping);
}
}
public void bake() {
System.out.println("25분");
}
public void cut() {
System.out.println("대각선 커팅");
}
public void box() {
System.out.println("포장");
}
public String getName() {
return name;
}
}
public class CheesePizza extends Pizza {
public CheesePizza() {
name = "치즈피자";
dough = "얇은 크러스트 도우";
sauce = "마리나라 소스";
toppings.add("모짜렐라 치즈");
}
}
public class GreekPizza extends Pizza {
public GreekPizza() {
name = "갈릭피자";
dough = "두꺼운 크러스트 도우";
sauce = "토마토 소스";
toppings.add("올리브");
}
void cut() {
System.out.println("네모모양 커팅" );
}
}
public class PizzTestDrive {
public static void main(String[] args) {
PizzaStore nyPizzaStore = new NYPizzaStore();
nyPizzaStore.orderPizza("cheese");
}
}
추상팩토리 Abstract Factory
정의 - 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있다.
설명)
제품군(family)을 생성하기 위한 인터페이스를 제공할 수 있다.
이 인터페이스를 사용하는 코드를 만들면 코드를 제품을 생성하는 실제 팩토리와 분리시킬 수 있다.
지역, 운영체체, 룩앤필 등 서로 다른 상황별로 적당한 제품을 생산할 수 있는 다양한 팩토리를 구현할 수 있다.
예제)
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperonic createPepperonic();
public Clams createClames();
}
피자의 제품군을 인터페이스
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
@Override
public Dough createDough() {
return new ThinCrustDough();
}
@Override
public Sauce createSauce() {
return new MarinaraSauce();
}
@Override
public Cheese createCheese() {
return new RegginaoCheese();
}
@Override
public Veggies[] createVeggies() {
Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
return veggies;
}
@Override
public Pepperonic createPepperonic() {
return new SlicedPepeeroni();
}
@Override
public Clams createClames() {
return new FreshClams();
}
}
각 지점마다 구상클래스
public abstract class Pizza {
String name;
Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
Pepperonic pepperonic;
Clams clams;
// 추상메서드
abstract void prepare();
public void bake() {
System.out.println("25분");
}
public void cut() {
System.out.println("대각선 커팅");
}
public void box() {
System.out.println("포장");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
피자클래스 추상메서드로 변경
public class CheesePizza extends Pizza {
private PizzaIngredientFactory pizzaIngredientFactory;
public CheesePizza (PizzaIngredientFactory pizzaIngredientFactory) {
this.pizzaIngredientFactory = pizzaIngredientFactory;
}
@Override
void prepare() {
System.out.println("준비중 " + name);
dough = pizzaIngredientFactory.createDough();
sauce = pizzaIngredientFactory.createSauce();
cheese = pizzaIngredientFactory.createCheese();
}
}
설명 sauce = pizzaIngredientFactory.createSauce();
sauce
pizza에 있는 인스턴스 변수에 이 피자에서 사용할 특정 소스에 대한 레퍼런스를 대입
pizzaIngredientFactory
원재료 팩토리, pizza 클래스에서는 원재로 팩토리가 맞기만 하면 어떤 팩토리를 쓰든 상관안함
createSauce
해당 지역에서 쓰이는 소스를 리턴
public class GreekPizza extends Pizza {
private PizzaIngredientFactory pizzaIngredientFactory;
public GreekPizza (PizzaIngredientFactory pizzaIngredientFactory) {
this.pizzaIngredientFactory = pizzaIngredientFactory;
}
@Override
void prepare() {
System.out.println("준비중 " + name);
dough = pizzaIngredientFactory.createDough();
sauce = pizzaIngredientFactory.createSauce();
cheese = pizzaIngredientFactory.createCheese();
clams = pizzaIngredientFactory.createClames();
}
}
생성자를 통해서 팩토리를 전달
public class NYPizzaStore extends PizzaStoreAbstract {
@Override
protected Pizza createPizza(String type) {
//추상팩토리
//뉴욕피자 원재료 공장을 전달
PizzaIngredientFactory pizzaIngredientFactory = new NYPizzaIngredientFactory();
Pizza pizza = null;
if("cheese".equals(type)) {
pizza = new CheesePizza(pizzaIngredientFactory);
pizza.setName("뉴욕스타일 치즈 피자");
} else if ("greek".equals(type)) {
pizza = new GreekPizza(pizzaIngredientFactory);
}
return pizza;
}
}
public class PizzTestDrive {
public static void main(String[] args) {
PizzaStore nyPizzaStore = new NYPizzaStore();
nyPizzaStore.orderPizza("cheese");
}
}
비교)
팩토리 메서드 패턴
- 클래스를 써서 제품을 만듬
- 상속을 통해 객체를 만듬
- 클래스를 확장하고 팩토리 메서드를 오버라이드
- 서브 클래스를 통해서 객체를 만듬
- 클라이언트 코드와 인스턴스를 만들어야 할 구상 클래스를 분리시켜야할때
-> 객체를 생성하기 위한 인터페이스를 만든다.
어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하도록 한다.
인스턴스를 만드는 일을 서브클래스로 미룰 수 있다.
추상팩토리 패턴
- 객체를 써서 제품을 만듬
- 객체구성(Composition)을 통해 만듬
- 제품군을 만들기 위한 추상 형석을 제공
- 서브클래스에서 정의함
- 클라이언틍에서 서로 연관딘 일련의 제품을 만들어야 할때, 즉, 제품군을 만들어야 할때
-> 서로 연관된 또는 의존적인 객체들로 이루어진 제품군을 생성하기 위한 인터페이스를 제공한다
구상 클래스는 서브 클래스에 의해 만들어진다.
핵심정리
- 팩토리를 쓰면 객체 생성을 캡슐화 할수 있다.
- 간단한 팩토리는 엄밀하게 말해서 디자인패턴은 아니지만, 클라이언트와 구상클래스를 분리시키기 위한 간단한 기법으로 활용할 수 있다.
- 팩토리 메서드 패턴에서는 상속을 활용한다. 객체 생성이 서브클래스에게 위임된다. 서브클래스에서는 팩토리 메서드를 구현하여 객체를 생산한다.
- 추상팩토리 패턴에서는 객체 구성을 활용한다. 객체 생성이 팩토리 인터페이스에서 선언한 메서들에서 구현된다.
- 모든 팩토리 패턴에서는 애플리케이션의 구상 클래스에 대한 의존성을 줄여줌으로써 느슨한 결합을 도와준다.
- 팩토리 메서드 패턴에서는 어떤 클래스에서 인스턴스를 만드는 일을 서브클래스한테 넘긴다.
- 추상팩토리 패턴은 구상클래스에 직접 의존하지 않고도 서로 관련된 객체들로 이루어진 제품군을 만들기 위한 용도로 쓰인다.
- 의존성 뒤집기 원칙을 따르면 구상 형식에 대한 의존을 피하고 추상화를 지향할 수 있다.
- 팩토리는 구상 클래스가 아닌 추상클래스/인터페이스에 맞춰서 코딩할 수 있게 해주는 강력한 기법이다.
추상팩토리 또다른 예제
예)
비행기를 조정하고 미사일을 발사해서 적을 미사일로 쏴 맞춰 잡는 슈팅 게임
여러 종류의 적이 출현하고 한 단계의 끝에 다다르면 그 단계의 보스가 출현하고, 이 보스를 맞춰 잡으면 다음 단계로 넘어가는 방식을 취한다.
또한 중간중간에 공격을 하지 않지만, 부딪히면 안되는 장애물이 출현하기도 한다.
보스 적기, 작은 적기 그리고 장애물을 단계마다 다른 종류가 출현한다.
특별 공격으로 작은 분신을 만들어내는 보스와 강력한 미사일을 발사하는 보스의 두가지 종류가 있을 수 있고, 적기에도 미사일을 발사하는 적기와 자폭하는 적기가 있을 수도 있다.
또한 각 단계마다 적들의 공격력이나 방어력이 달라질 수 있다.
장애물에도 돌처럼 부딪히고 지나가는 장애물과 부딪히면 폭파되는 장애물이 있을 수 있다.
이런 보스, 적기, 장애물을 구현하기 위해 Boss, SmallFlight, Obstacle 클래스 및 하위 클래스
EnemyFlight
Boss
CloningBoss
StrongAttackBoss
SmallFlight
MissileSmallFlight
DashSmallFlight
Obstacle
RockObstacle
BombObstacle
실제 게임 플레이를 진행하는 Stage 클래스는 몇 단계인지에 따라 서로 다른 적기, 장애물 또는 보스를 생성해야 한다.
public class Stage {
private void createEnemies() {
for ( int i=0; i<ENEMY_COUNT; i++) {
if(stageLevel == 1) {
enemies[i] = new DashSmallFlight(1, 1); //공격 1, 수비력 1
}else if (stageLevel == 2) {
enemies[i] = new MissileSmallFlight(1, 1);
}
}
if(stageLevel == 1) {
boss = new StrongAttackBoss(1, 10);
} else if (stageLevel == 2) {
boss = new CloningBoss(5, 20);
}
}
private void createObstacle() {
for ( int i=0; i<OBSTACLE_COUNT; i++) {
if(stageLevel == 1) {
obstacles[i] = new RockObstacle();
}else if (stageLevel == 2) {
obstacles[i] = new BombObstacle();
}
}
}
}
위 코드의 문제는 단계별로 적기, 보스, 장애물을 생성하는 규칙이 Stage 클래스에 포함되어 있다는 점이다.
새로운 적 클래스가 추가되거나 각 단계의 보스의 종류가 바뀔 때 Stage 클래스를 함께 수정해주어야 하고, 각 단계별로 생성 규칙이 달라질경우에도 Stage 클래스를 수정해 주어야 한다.
또한 중첩되거나 연속된 조건문으로 인해 코드가 복잡해지기 쉽고 이는 코드 수정을 어렵게 만드는 원인이 된다.
적과 장애물 객체의 생성을 Stage 클래스에서 직접 수행하면서 앞서 언급한 문제들이 발생하기 때문에, Stage 클래스로부터 객체 생성 책임을 분리함으로써 이 문제를 해소할 수 있다.
추상팩토리 패턴이다.
추상팩토리 패턴에서는 관련된 객체 군을 생성하는 책임을 갖는 타입을 별도로 분리한다.
SmallFlight, Boss, Obstacle 객체를 생성해주는 책임을 갖는 EnemyFactory 타입을 추가할 수 있다.
EnemyFactory
+getFactory(Stage : int)
+createSmallFlight()
+createBoss()
+createObstacle()
EasyStageEnemyFactory, HardEnemyFactory
EnemyFactory 클래스는 Boss, SmallFlight, Obstacle 객체를 생성해주는 메서드를 정의하고 있다.
여기서 EnemyFactory 클래스는 객체 생성 메서드를 선언하는 추상 타입으로서 팩토리에 해당되며, 팩토리가 생성하는 대상인 Boss, SmallFlight, Obstacle은 제품 타입이 된다.
EnemyFactory.getFactory() 메서드는 정적메서드로서 파라미터로 전달받은 레벨에 따라 알맞는 EnemyFactory 객체를 리턴하도록 정의했다.
EnemyFactory 클래스
public abstract class EnemyFactoy {
public static EnemyFactory getFactory(int level) {
if(level ==1) {
return EasyStageEnemyFactory();
} else {
return HardEnemyFactory();
}
}
//객체 생성을 위한 팩토리 메서드
public abstract Boss createBoss();
public abstract SmallFlight createSmallFlight();
public abstract Obstacle createObstacle();
}
팩토리인 EnemyFactory를 구현한 콘크리트 팩토리 클래스
public class EasyStageEnemyFactory extends EnemyFactory {
public Boss createBoss() {
return new StrongAttackBoss();
}
public SmallFlight createSmallFlight() {
return new DashSmallFlight();
}
public Obstacle createObstacle() {
return new RockObstacle();
}
}
public class HardEnemyFactory extends EnemyFactory {
public Boss createBoss() {
return new CloningBoss();
}
public SmallFlight createSmallFlight() {
return new MissileSmallFlight();
}
public Obstacle createObstacle() {
return new BombObstacle();
}
}
Stage 클래스는 객체 생성이 필요한 경우 직접 생성하기보다는 추상 팩토리타입인 EnemyFactory를 이용해서 객체를 생성한다.
public class Stage {
private EnemyFactory enemyFactory;
public class Stage(int level) {
enemyFactory = EnemyFactory.getFactory(level);
}
private void createEnemies() {
for(int i=0; i<=ENEMY_COUNT; i++) {
enemies[i] = enemyFactory.createSmallFlight();
}
boss = enemyFactory.createBoss();
}
private void createObstacle() {
for(int i=0; i<=ENEMY_COUNT; i++) {
obstacles[i] = enemyFactory.createObstacle();
}
}
}
변경된 Stage 클래스의 코드를 보면, Stage클래스는 더이상 StrongAttackBoss 클래스와 DashSmallFlight클래스와 같은 콘크리트 제품 클래스를 사용하지 않는다.
단지 추상타입인 Boss, SmallFlight, Obstacle만 사용할 뿐이다.
각 콘크리트 제품 클래스를 사용해서 객체를 생성하는 코드는 EnemyFactory 팩토리의 하위 타입인 EasyStageEnemyFactory, HardEnemyFactory 클래스로 옮겨졌다.
Stage 클래스는 EnemyFactory 클래스의 정적메서드인 getFactory() 메서드를 이용해서 사용할 EnemyFactory 클래스를 구하고 있다.
private EnemyFactory enemyFactory;
public class Stage(int level) {
//EnemyFactory 객체를 구함
enemyFactory = EnemyFactory.getFactory(level);
}
따라서 레벨1에서 사용되는 객체는 완전히 다른 타입으로 변경하고 싶다면 Stage 클래스를 변경할 필요없이, 새로운 EnemyFactory 구현 클래스를 만들고 EnemyFactory.getFactory() 메서드에서 이 클래스의 객체를 리턴하도록 수정해주면된다.
public abtract class EnemyFactory {
public static EnemyFactory getFactory(int level) {
if(level ==1) {
//새로운 팩토리 클래스
return SomethingNewEnemyFactory();
} else {
return HardEnemyFactory();
}
}
EnemyFactory 객체를 구하는 기능을 EnemyFactory 클래스에서 정의했는데 DI를 사용해도 된다.
DI를 사용하면 생성자나 설정 메서드를 통해서 EnemyFactory 객체를 전달받게 되므로, EnemyFactory 클래스에 getFactory() 메서드를 정의할 필요가 없어진다. 따라서 EnemyFactory 추상 클래스를 인터페이스로 변환할 수 있다.
public class Stage {
private EnemyFactory enemyFactory;
//DI를 적용하면 팩토리 구하는 기능을 EnemyFactory에 구현할 필요가 없음
public Stage(int level, EnemyFactory enemyFactory) {
this.level = level;
this.enemyFactory = enemyFactory;
}
}
추상 팩토리 패턴을 사용할 때의 장점은 클라이언트에 영향을 주지 않으면서 사용할 제품(객체)군을 교체할 수 있다는 것이다.
Stage클래스는 Boss, SmallFlight, Obstacle 타입의 객체를 사용하는데, CloningBoss, MissileSmallFlight, BombObstacle 객체를 사용하다가 StrongAttackBoss, DashSmallFlight, RockObstacle 객체를 사용하도록 변경하더라도 Stage 클래스는 전혀 영향을 받지 않는다.
오직 Stage 클래스는 전혀 영향을 받지 않는다.
오직 Stage 클래가 사용ㅇ할 콘크리트 팩토리 객체만 변경해주면된다.
사용할 콘크리트 객체를 변경하는 작업 역시 Stage 클래스에는 영향을 주지 않기 때문에 제품군을 쉽게 변경할 수 있다.
만약 팩토리가 생성하는 객체가 늘 동일한 상태를 갖는다면, 프로토타입 방식으로 팩토리를 구현할 수 있다.
프로토타입 방식은 생성할 객체의 원형 객체를 등록하고, 객체 생성 요청이 있으면 원형 객체를 복제해서 생성한다.
public class Factory {
private ProductA productA;
private ProductB productB;
public Factory(ProductA productA, ProductB productB ){
this.productA = productA;
this.productB = productB;
}
public ProductA createA() {
return (ProductA) productAProto.clone();
}
public ProductB createB() {
return (ProductB) productBProto.clone();
}
}
프로토타입 방식의 팩토리를 사용하면, 객체군마다 팩토리 클래스를 작성할 필요없이 객체 군마다 팩토리 객체를 생성해주면된다.
객체 군 1을 위한 팩토리 객체
Factory family1Factory = new Factory(new HighProductA(), new HighProductB());
ProductA a = family1Factory.creatA(); //HighProductA 객체 복제본 생성
객체 군 2을 위한 팩토리 객체
Factory family2Factory = new Factory(new LowProductA(), new LowProductB());
proudctB b = family2Factory.createB(); //LowProductB 객체 복제본 생성
프로토타입 방식을 추상 팩토리 타입과 콘크리트 팩토리 클래스를 따로 만들 필요가 없어 구현이 쉽지만, 반면에 제품 객체의 생성 규칙이 복잡할 경우 적용할 수 없는 한계가 잇다.
추상팩토리 적용한 대표적인 예가 자바의 JDBC API이다
Client ---> <<interface>> Connection
Client ---> <<interface>> Statement
Client ---> <<interface>> PreparedStatement
<<interface>> PreparedStatement ---> <<interface>> Statement
ConnectionImpl ---> <<inteface>> MySQLConnection
ConnectionImpl ---> <<inteface>> Connection
StatementImpl ---> <<interface>> Statement
PreparedStatementImpl ---> <<interface>> PreparedStatement
PreparedStatementImpl ---> StatementImpl
ConnectionImpl ---> instantlate StatementImpl
ConnectionImpl ---> instantlate PreparedStatementImpl
클라이언트는 Connection을 이용해서 Statement 객체와 PreparedStatement 객체를 생성한다.
즉 Connection이 팩토리에 해당하고 Statement와 PreparedStatement가 제품에 해당한다.
DBMS 별로 알맞는 Statement 콘크리트 클래스와 PreparedStatement 콘크리트 클래스를 제공하며, 이들 콘크리트 클래스의 객체를 생성해주는 Connection 구현클래스를 제공한다.
따라서 클라이언트가 사용할 DBMS를 변경해야 할경우, 클라이언트 수정없이 팩토리에 해당하는 Connection 객체만 교체해주면된다.
출처 - Head First Design Patterns 저자- 에릭 프리먼, 엘리자베스 프리먼, 케이시 시에라, 버트 베이츠
출처 - 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴 저자 - 최범균