이번에는 가장 많이 사용하는 패턴 중 하나인 FactoryMethod 패턴에 대해 살펴본다.
정의를 내려보자면 생성될 객체의 정확한 클래스를 명시하지 않고 특정 메소드에서 결정한다.
역시 와닿지 않는다....

Observer 때와 마찬가지로 스타크래프트를 예로 들어본다.
테란의 메카닉 유닛을 생산하는 건물이 마침 Factory 니까... 그걸로 설명 한다.

사용자 삽입 이미지

FactoryMethod 는 말 그대로 생산, 생성의 의미를 가지고 있다.
생산될 대상은 Product 이고 이는 스타크래프트의 팩토리가 생산하는 유닛들이다.
이 유닛들은 SiegeTank, Vulture 같은 유닛들로 구체화된다.
그리고 이들 유닛을 생성하는 처리를 담당하는 Creator 를 사용함으로써 FactoryMethod 패턴이 구성된다.
위의 그림과 같이 SiegeTank 란 유닛을 생산하기 위한 SiegeTankCreator 로써 존재하는데.
Creator 에서 유닛을 생성하여 반환하는 메소드를 factoryMethod 라 했다. 
그렇다면 Creator 가 곧 스타크래프트의 팩토리 일까?
아니다 Creator 는 각각의 유닛별로 고유한 생산 로직을 담은 모듈에 해당된다.
스타크래프트의 팩토리는 그 Creator 를 모아놓은 것이다. (이는 좀 더 뒤에 다른 패턴에서 다루게 된다.)
Creator 는 게이머가 팩토리를 선택하고 시즈탱크를 생산하겠다라고 명령을 내릴때 동작한다.
유닛 생산시 필요한 시간을 소비시키고 적합한 애니메이션이나 소리를 들려주는 등의 처리가 Creator 에서 일어나는 일이 될 것이다.

예제 소스를 통해 살펴보자..
나름 재미가 있길래 조금 오바를 했다. FactoryMethod 패턴을 설명하기 위한 핵심은 진하게 표시된 부분이다.
public abstract class Unit {
    protected final String name;
    protected Unit(String name) {
        this.name = name;
    }
    public String getName() { return this.name; }
    public void command1() { System.out.println(name + ":이동"); }
    public abstract void command2();
    public abstract void command3();
}
유닛은 공통적인 처리나 로직등을 담을 필요가 있다 판단해서 인터페이스를 사용하지 않고 추상 객체로 작성했다.
유닛의 이름이나 command1 에서 일어나는 이동 같은 것들이 이런 이에 해당한다.
이 유닛을 상속하여 SiegeTank와 Vulture 를 만들수 있다.
public class Vulture extends Unit {
    private int countSpiderMine;
    Vulture() {
        super("벌처");
        this.countSpiderMine = 3;
    }
    public void command2() { System.out.println(name + ":수류탄"); }
    public void command3() {
        if ( this.countSpiderMine > 0 ) {
            this.countSpiderMine--;
            System.out.println(name + ":마인심기");
        } else {
            System.out.println(name + ":마인없음");
        }
    }
}
public class SiegeTank extends Unit {
    private boolean siegeMode;
    SiegeTank() {
        super("탱크");
        this.siegeMode = false;
    }
    public void command1() {
        if ( !this.siegeMode ) {
            super.command1();
        } else {
            System.out.println(name + ":이동못함");  // 시즈모드일때는 이동하지 못한다.
        }
    }
    public void command2() {
        if ( this.siegeMode ) {
            System.out.println(name + ":시즈포");
        } else {
            System.out.println(name + ":통통포");
        }
    }
    public void command3() {
        if ( this.siegeMode ) {
            this.siegeMode = false;
            System.out.println(name + ":탱크모드");
        } else {
            this.siegeMode = true;
            System.out.println(name + ":시즈모드");
        }
    }
}
나름  SiegeTank와 Vulture 의 특성을 구현해 보았다. 
이렇게 해서 만들어야 할 대상 Product 가 준비되었다. 이제 Creator 를 만들어본다.
public abstract class Creator {
    protected Creator() {}
    public abstract Unit createUnit();
}
위의 경우는 인터페이스로 작성해도 되지만 추상 객체로 만든 이유는 어떠한 공통 로직이 나중에라도 필요하지 않을까 해서이다. 이제 SiegeTank와 Vulture 를 만드는 각각의 Creator 를 작성한다.
public class SiegeTankCreator extends Creator {
    public Unit createUnit() {
        Unit unit = new SiegeTank();
        System.out.println(unit.getName() + " 생산");
        return unit;
    }
}
public class VultureCreator extends Creator {
    public Unit createUnit() {
        Unit unit = new Vulture();
        System.out.println(unit.getName() + " 생산");
        return unit;
    }
}
각각의 Creator 가 Unit 이라는 추상 객체로 반환하지만, 내부에서 각각 new SiegeTank(), new Vulture() 했다.
다시 한번 SiegeTank, Vulture 의 소스를 보면 Constructor 이 package private 이기 때문에 package 밖에서는 생성할 수 없다. 오로지 createUnit 으로만 생산할 수 있다. FactoryMethod 패턴을 사용하는 의미를 바로 안다면 SiegeTank, Vulture 는 결코 밖에서 보이면 안되고, 오로지 Unit 으로만 핸들링 되어야 한다. 그렇지 않다면 구지 FactoryMethod 패턴을 사용할 이유는 없다.
이제 이들 Creator 들을 모아놓은 팩토리를 만들고 테스트 해본다.
public class Factory {
    private Creator creator1;
    private Creator creator2;
    public Factory() {
        this.creator1 = new SiegeTankCreator();
        this.creator2 = new VultureCreator();
    }
    public Unit createUnit1() { return this.creator1.createUnit(); }
    public Unit createUnit2() { return this.creator2.createUnit(); }
}
public static void main(String[] args) {
    Factory factory = new Factory();
    Unit unit1 = factory.createUnit1();
    unit1.command1();
    unit1.command2();
    unit1.command3();
    unit1.command2();
    unit1.command1();
    unit1.command3();
    unit1.command1();

    Unit unit2 = factory.createUnit2();
    unit2.command1();
    unit2.command2();
    unit2.command3();
    unit2.command3();
    unit2.command3();
    unit2.command3();
    unit2.command1();
}

사용자 삽입 이미지

실행된 모습


그런데 왜 객체를 직접 생성하지 않고, FactoryMethod 패턴을 사용하여 생성할까..?
이유는 간단하다. ExtentionPack Blood War 를 만들기 위해서다.
스타크래프트 오리지날에 골리앗이란 유닛이 없었다고 가정하고...
확장팩에 골리앗이라는 새로운 유닛을 만들어 추가로 넣고자 한다...
FactoryMethod 패턴이 적용되어 있지 않다면 꽤나 많은 수정이 일어날 것이다.
FactoryMethod 패턴을 바탕으로 설계되어 있다면
먼저 Unit 을 상속해 특화된 기술 요소를 넣은 골리앗을 만든다.
그리고 이를 생성하는 Creator 를 만들고, 그곳에 빌드 타임이나 제한 요소등을 넣을 수 있다.
지금까지 새로운 객체를 추가만 했지 기존 소스의 수정은 없었다는 점이 중요 포인트다...
앞서의 예에서 팩토리에 골리앗을 만드는 Creator 를 넣는 부분은 팩토리를 수정해야 하지만...
차후 이 부분은 다른 패턴을 가지고 설명할 기회가 있을 것이다.

참고 삼아서 스타크래프트의 사용자 인터페이스를 잠시 말한다.
게이머가 유닛을 선택하고 그 유닛에 내리는 명령의 가짓수는 몇가지 일까?
한 유닛에 한번에 선택할 수 있는 명령은 우측 하단의 최대 9개 버튼뿐이다.
마우스 왼쪽버튼, 오른쪽버튼으로 맵을 찍거나, 키보드로 단축키를 넣는 것은 
Observer 패턴에 의해 9개 버튼중의 하나인 명령을 실행하는 것뿐이다.
건물짖기를 누르면 지을 수 있는 건물로 버튼이 바뀌는 것은 Shift 일 뿐이다.
예제 소스에서 유닛의 명령메쏘드 이름을 move, attack 으로 하지 않고, command1, command2 로 한건 이 때문이다.
이렇게 해서 각각의 개성넘치는 유닛들의 기능이 한 곳에서 단순한 구성으로 제어가 가능해진다.
여기서 이 얘기를 하는 것은 FactoryMethod 패턴에서 모든 유닛이 Unit 이란 슈퍼 클래스하나로 사용될 수 있어야 함을 이야기 하고자 한 것이다. 이것은 FactoryMethod 패턴의 활용 가치를 높여주는 핵심적 사항이다.
결국 제일 어려운 것은 FactoryMethod 가 아니라 Product 를 결정하는 것이다.

스타크레프트 예제도 어려우면 다음을 보자..
public class creator1 {
    public Number createNumber() {  return new Integer(1234); }
}
public class creator2 {
    public Number createNumber() {  return new Long(1234); }
}

이 예제도 객체(Integer, Long)의 생성이 메쏘드 내에서 이루어 지고, 
그렇게 생성된 객체의 슈퍼클래스 형태(Number)로 리턴되는 FactoryMethod 패턴이다.
즉, createNumber() 란 메쏘드가 객체를 생성해서 반환하는 공장 메소드란 것이다.
그리고...
지금 필요한 건 Integer 와 Long 이었지만, 혹시 나중에 new Double 을 붙일 필요가 있다면...
그것만 쉽게 만들어 붙일 수 있도록 공통적인 부분을 인터페이스나 추상 객체로 만들어 놓은 것일 뿐이다.

아마 객체를 상속해서 쓰기 좋아했던 개발자라면...
실제 이게 팩토리메소드 패턴이야라고 알지 못하고도 그간 사용하고 있었을 수도 있다.

 


+ Recent posts