Factory Method 패턴은 객체의 생성과 관련된 패턴이다.

Factory라는 것은 생산품을 생산하는 생산자의 의미로 사용되는 단어이고, 객체지향 언어에서는 객체를 생성하는 생산자를 의미한다.

Method는 본래 Template Method 패턴에서 차용한 단어이다. Factory Method 패턴에서는 객체의 생성 직후에 해야 할 일을 순서대로 정의한 메소드를 제공한다. 그리고 구체적인 생성은 구체 클래스들에 위임한다.

이 Factory Method 패턴을 통해 얻을 수 있는 이익은 다음과 같다.

1. 객체의 생성 후 공통으로 할 일을 수행한다.

객체가 생성된 직후에 어떤 작업을 수행해야 하는 경우는 자주 나타난다. 특히 문제가 되는 경우는 객체가 이벤트를 수신하는 경우이다. 만약 객체를 이벤트 수신자로 등록하는 코드를 생성자 내부에 넣어 두게 되면 생성이 완료 되기 이전에 이벤트를 받게 되어 오류가 발생하는 경우가 생긴다. 이런 일들은 객체 생성 이후에 해주어야 하는데, Factory Method 패턴을 이용하면 이 문제를 해결할 수 있다.

2. 생성되는 객체의 구체적인 타입을 감춘다.

Factory Method 패턴은 그 구조상 생성된 객체의 타입을 구체적인 타입이 아닌 추상 타입(상위 타입)으로 리턴한다. 이를 통해서 객체를 사용하는 측에서는 구체적인 타입의 존재 조차 모르도록 할 수 있다.

3. 구체적인 Factory 클래스가 생성할 객체를 결정하도록 한다.

구체 Factory 클래스는 생성 메소드 내부의 구현을 책임진다. 구체 생성 메소드 내부에서는 필요한 동작을 자유롭게 구현할 수 있는데, 특히 인자를 받거나 상태에 따라서 생성할 객체를 바꿀 수도 있다. 이렇게 하면 좀 더 다양한 기능을 수행하거나 수정에 용이한 구조를 만들어 낼 수 있다.


본격적인 구현 예제에 앞서서, 매우 간단한 형태의 Simple Factory Method를 구현해 보고자 한다. 이 구현은 사실 Gof가 의도한 Factory Method 패턴과는 관계가 없을 수 있다. 생성을 담당하는 메소드이기 때문에 Factory Method라고 불리긴 하지만 Factory Method 패턴은 아니다. 하지만 위에서 언급한 1번의 경우를 구현한 것이기 때문에 봐 둘 필요는 있다.


Factory Method ( Factory Method 패턴이 아님 )

class Wheel{

    private Wheel(){}// 생성자를 감춤.

    public static Wheel create(){

        Wheel wheel = new Wheel();

        wheel.init();// 생성 후 꼭 해줘야 할 일

        return wheel;

    }

    public void init(){}

}

위의 클래스에서 create() 메소드가 이에 해당한다. create() 메소드는 객체가 생성된 직 후 해주어야 할 일(위에서는 init() 메소드를 호출하는 일)을 수행한다. 위에서 이야기 한 것처럼 어떤 작업은 객체의 생성자 내부에서 하지 못하는 일이 있을 수 있다. 그런 일들은 객체가 생성된 이후에 해주어야 하는데, 이런 Factory Method를 제공해 주지 않으면 객체를 생성 받은 쪽에서 그 일을 해야 한다. 만약 객체를 생성하는 곳이 여러 곳이라면 이런 작업을 여러 곳에서 해야 하고, 또 이를 수정해야 할 경우에는 여러 곳에서 이를 수정해야 한다. 이런 문제를 방지할 수 있도록 해주는 것이 Factory Method 이다.


Factory Method 패턴은 좀 더 많은 문제에 관여하는 패턴이다. 다음의 예제를 보자.


Factory Method 패턴 클래스 다이어그램

Factory Method 패턴의 구현

abstract class ShapeFactory{

    public final Shape create(Color color){

        Shape shape = createShape();

        shape.setColor(color);       // 1. 생성 후 공통으로 할 일

        return shape;                // 2. 구체적인 타입을 알 수 없음

    }

   

    // protected이기 때문에 외부에 노출 안됨.

    // 3. 매개 변수를 받아서 생성할 객체를 결정하도록 할 수 있음.

    abstract protected Shape createShape();

}

class RectangleFactory extends ShapeFactory{

    @Override

    protected Shape createShape() {

        return new Rectangle();

    }

}

class CircleFactory extends ShapeFactory{

    @Override

    protected Shape createShape() {

        return new Circle();

    }

}

interface Shape

{

    public void setColor(Color color);

    public void draw();

}

class Rectangle implements Shape

{

    Color color;

    public void setColor(Color color){ this.color = color; }

    public void draw() { System.out.println("rect draw"); }

}

class Circle implements Shape

{

    Color color;

    public void setColor(Color color){ this.color = color; }

    public void draw() { System.out.println("circle draw"); }

}

최초에 나오는 ShapeFactory가 Factory Method 패턴의 기본 클래스이다. 

여기서 create() 메소드가 생성 메소드 역할을 담당한다. 이 메소드는 구체 메소드이므로 ShapeFactory를 상속 받은 클래스들은 모두 이를 이용할 수 있다. 또한 final 메소드이므로 재정의가 불가능하다. Shape을 리턴 타입으로 사용하기 때문에 ShapeFactory를 사용하는 곳에서는 리턴되는 Shape의 구체적인 타입(예제에서는 Rectangle인지 Circle인지)을 알 수 없다.

ShapeFactory는 createShape()이라는 추상 메소드를 선언하고 있다. ShapeFactory의 구체 클래스들은 이 메소드를 통해서 어떤 Shape 객체를 생성할지를 결정한다.

create() 메소드와 createShape() 메소드와의 관계는 Template Method 패턴과 같다.

createShape() 메소드는 protected로 선언되어 있어서 외부 클래스에서는 사용이 불가능하다. 즉, create() 메소드만을 이용하도록 해서 객체의 생성 이후 수행할 작업이 완료될 것을 강제하는 것이다. createShape() 메소드를 구현하는 구체 Factory 클래스들에서는 다양한 형태로 이를 구현할 수 있다. createShape() 메소드가 매개변수를 받는다면, 매개변수를 조건으로 하여 구체적으로 생성할 Shape의 종류를 바꿀 수도 있고, createShape() 메소드 내부에서 가지고 있는 정보들을 활용하여 생성 조건을 변경할 수도 있다.


Factory Method 패턴에서 문제가 될 부분은 구체 Factory들이 여러번 생성될 필요가 없다는 점이다. 따라서 Singleton 패턴을 이용해야할 경우가 있는데 이렇게 되면 패턴이 복잡해지는 문제점이 있다. 이 문제는 Enum 타입을 통해 Factory Method 패턴을 구현함으로써 해결할 수 있다. Enum Factory Method 패턴을 참고하기 바란다.

'5.디자인패턴' 카테고리의 다른 글

Observer 패턴  (0) 2016.09.16
Abstract Factory 패턴  (0) 2016.09.16
Memento 패턴  (2) 2016.09.13
Bridge 패턴  (0) 2016.09.13
Template Method 패턴  (0) 2016.09.10
Posted by 이세영2
,

Memento 패턴

5.디자인패턴 2016. 9. 13. 20:50

Memento 패턴은 특정 시점에 객체의 상태를 저장하고 복원하기 위해서 사용하는 패턴이다. 바둑이나 오목과 같이 일련의 진행 사항을 저장해 두었다가 다시 복구하기 위해서 사용할 수도 있고, 실패가 예상되는 작업이 있을 경우 복원 지점을 저장해 두었다가 실패했을 때 원 상태로 복원하기 위해서 사용할 수도 있다.


Memento 패턴 클래스 다이어그램

이 다이어그램은 User 클래스와 Memento 클래스의 관계를 표현한다. User 클래스는 id와 level, exp와 같은 필드들을 가진다. Memento 클래스는 User 클래스가 가진 필드들을 저장하는 역할을 한다. 따라서 User가 가지고 있는 모든 필드들을 가지고 있어야 한다. User 클래스는 다이어그램에서 save와 load 메소드를 제공하도록 되어 있다. 원래 Memento 패턴에서는 User 클래스의 데이터 저장과 복원을 별도로 관리하는 클래스를 두기도 하는데 이렇게 하는 경우 생각보다 구현이 복잡해 질 수 있다. 따라서 이 예제에서는 간단히 User 클래스 스스로가 저장과 복원을 관리하도록 하였다.

세부적인 구현은 아래 코드를 보고 이야기 하도록 하자.


Memento 패턴의 구현

class User

{

    private String id;

    private int level;

    private int exp;

  

    // 객체의 상태를 저장하기 위해 꼭 필요한 항목을 캡슐화

    class Memento

    {

        private String id;

        private int level;

        private int exp;

        public Memento(User user){

            id = user.id;

            level = user.level;

            exp = user.exp;

        }

    };

    Vector<Memento> backup = new Vector<>();

    public int save()

    {

        backup.add(new Memento(this));

        return backup.size() - 1;

    }

    public void load(int token)

    {

        Memento m = backup.get(token);

        id = m.id;

        level = m.level;

        exp = m.exp;

    }

}

특징적인 부분을 이야기해 보면, 우선 Memento 클래스가 User 클래스 내부에 선언되어 있다. 이것은 클래스가 단순할 경우 특히 유용한데, 다수의 Memento 패턴이 사용되어야 할 경우라면 Memento 클래스를 내부 클래스로 선언하는 편이 좋다. 

save와 load를 구현하기 위해서는 Memento 객체를 생성하여 저장해 둘 backup 리스트를 선언해 주어야 한다. save() 메소드에서는 새로운 Memento 객체를 생성하고, User 객체가 자기 자신을 인자로 넘겨 Memento 객체가 초기화 되도록 한다. 그리고 backup 리스트에 저장하여 복원에 대비한다. load() 메소드에서는 backup 리스트의 인덱스를 받아 Memento 객체를 꺼내고, 이 Memento 객체에 저장된 필드 값들을 User 객체의 필드 값으로 바꿔준다. 이를 통해 이전에 저장된 데이터를 복원할 수 있다.

이 구현 방식은 Memento 패턴의 간략화 된 버전이라 할 수 있다. 이 Memento 패턴의 save() load() 메소드를 이용하여 undo()나 redo()와 같은 기능들도 구현할 수 있다.

'5.디자인패턴' 카테고리의 다른 글

Abstract Factory 패턴  (0) 2016.09.16
Factory Method 패턴  (4) 2016.09.16
Bridge 패턴  (0) 2016.09.13
Template Method 패턴  (0) 2016.09.10
Singleton 패턴  (0) 2016.09.10
Posted by 이세영2
,

Bridge 패턴

5.디자인패턴 2016. 9. 13. 19:32

Bridge 패턴은 두 구체 클래스 간의 강한 결합을 제거하기 위해서 사용하는 패턴이다. 특히 두 클래스 모두 추상화된 상위 클래스 또는 인터페이스(= 타입)를 가지게 되고, 의존성은 상위 타입간에만 이루어지게 된다. 이를 통해 실제 의존성이 발생하더라도 서로의 구체 타입은 알 수 없도록 한다. 이렇게 되면 두 상위 타입을 구현하는 어느 쪽도 변경이 가능한 상태가 된다.

구체적인 예를 위해서 RPG 게임을 제작한다고 생각해 보자. 일단 우리에게는 각종 무기들(Bow, Sword 등)이 필요할 것이다. 그리고 무기를 다루는 전사들이 있을 것이다. 일단 Warrior가 있다고 가정하자. 그런데 무기를 다루는 것은 전사 뿐만 아니라 대장장이(Smith)도 있다. 대장장이도 어떻게든 무기를 다룰 수 있도록 해 주어야 한다.

만일 Warrior가 구체적으로 Bow와 Sword를 다루는 구현을 가지고 있다면 Bow와 Sword를 변경할 때마다 Warrior의 구현도 변경되어야 할 것이다. 또한 무기가 추가되면 또 해당 무기에 대한 구현을 해야 한다. 또한 Smith 역시 무기들에 대한 구체적인 구현을 가지고 있다면 같은 문제점을 가지게 될 것이다.

이러한 경우에 Bridge 패턴을 이용하면 구체적인 클래스들 간의 의존성을 배제시킬 수 있다.


무기(Weapon) - 무기 핸들러(WaponHandler) Bridge 패턴 Class Diagram


위 클래스 다이어그램에서 보는 바와 같이 Weapon과 WeaponHandler는 각각 인터페이스이다. 그리고 구체적인 의존 관계는 이 인터페이스들 간에만 존재한다. Weapon을 구현한 Bow 클래스와 Sword 클래스는 실제 자신들을 다룰 구체적인 클래스를 알지 못한다. 역시 WeaponHandler를 구현한 Warrior나 Smith도 구체적인 무기, 즉 Bow나 Sword를 알지 못한다. 따라서 Weapon이 Spear, Gun 등과 같이 늘어나더라도 WeaponHandler의 구체 클래스들에는 변경에 있어서 영향을 주지 않는다.

그러면 실제 구현을 살펴 보자.


Weapon - WeaponHandler Bridge 패턴 구현

interface Weapon{

    public void attack();

    public void repair();

}

class Bow implements Weapon{

    public void attack(){

        System.out.println("Bow attack");

    }

    public void repair(){

        System.out.println("Bow repair");

    }

}

class Sword implements Weapon{

    public void attack(){

        System.out.println("Sword attack");

    }

    public void repair(){

        System.out.println("Sword repair");

    }

}


interface WeaponHandler{

    public void handle();

}

class Warrior implements WeaponHandler{

    private Weapon weapon;

    public Warrior(Weapon weapon){

        this.weapon = weapon;

    }

    public void handle(){

        System.out.print("Warrior ");

        weapon.attack();

    }

}

class Smith implements WeaponHandler{

    private Weapon weapon;

    public Smith(Weapon weapon){

        this.weapon = weapon;

    }

    public void handle(){

        System.out.print("Smith ");

        weapon.repair();

    }

}

여기서 Weapon 클래스 군과 WeaponHandler 클래스 군 간의 구체적인 의존성이 나타나는 곳은 Warrior와 Smith 클래스의 생성자에서 Weapon을 인자로 받는 부분이다. 이 경우에서 보듯이 Warrior나 Smith 클래스는 인터페이스인 Weapon만 알 뿐, 구체적인 타입은 알지 못한다.

이러한 방식으로 무기의 종류를 늘리거나 무기를 다루는 사람을 늘리더라도 다른 한 쪽에는 변경에 영향을 미치지 않도록 만들어 준다.

'5.디자인패턴' 카테고리의 다른 글

Factory Method 패턴  (4) 2016.09.16
Memento 패턴  (2) 2016.09.13
Template Method 패턴  (0) 2016.09.10
Singleton 패턴  (0) 2016.09.10
Builder 패턴  (0) 2016.09.10
Posted by 이세영2
,