본문 바로가기
디자인패턴

원형패턴 (Prototype Pattern)

by 이석준석이 2021. 6. 3.

사용의도

  • 미리 원형(Prototype) 으로 초기화 해 둔 뒤, 이를 복제해서 사용합니다.
    • boilerplatecode 를 제거할 수 있습니다.
  • 이를 복제해서 사용하므로써, 인스턴스화 하는 클래스를 런타임에 초기화하여 생성합니다.

   
미리 원형으로 초기화한다. 어떠한 객체를 미리 생성해두는 것을 말합니다.
이를 복제해서 사용한다.  생성해둔 객체를 복사하여 사용한다는 뜻입니다.
인스턴스화 하는 클래스를 런타임에 초기화하여 생성한다. 복제한 클래스들 여러개를 합성(Composition) 하여 갖고있는 인스턴스를 런타임에 생성한다는 뜻입니다.

구조


예시) 비행기의 부품을 선택하여, 완제품인 비행기를 만드는 게임

 

완제품인 비행기 를 만들기위해서 부품으로

  • 엔진
  • 날개

를 선택해야 한다고 가정합니다.

@AllArgsConstructor
public class AirPlane {
    private Wing wing;
    private Engine engine;
}

 

 

이러한 과정에서 우리는 날개와 엔진을 생성하는 코드를 날개와 엔진에 위임한 뒤

public abstract class Engine {
    protected abstract Engine clone();
}

public abstract class Wing {
    protected abstract Wing clone();
}

 

이를 선택하여 비행기를 만드는 팩토리(registry)를 선언합니다.

  • 팩토리(registry)는 미리 모든 객체들을 갖고있도록 합니다.
public class AirplaneFactory {

    private static HashMap<String, Wing> wingHashMap = new HashMap<>();
    private static HashMap<String, Engine> engineHashMap = new HashMap<>();

    static {
        wingHashMap.put("cheapWing", new CheapWing());
        wingHashMap.put("expensiveWing", new ExpensiveWing());

        engineHashMap.put("cheapEngine", new CheapEngine());
        engineHashMap.put("expensiveEngine", new ExpensiveEngine());
    }

    public static AirPlane createAirplane(String wingName, String engineName) {
        return new AirPlane(
                wingHashMap.get(wingName),
                engineHashMap.get(engineName));
    }
}

 

엔진과 날개에는 각 각 비싼 부품, 싼 부품이 있다고 가정할 때

public class CheapEngine extends Engine {
    @Override
    protected Engine clone() {
        return new CheapEngine();
    }
}

public class CheapWing extends Wing {
    @Override
    protected Wing clone() {
        return new CheapWing();
    }
}
public class ExpensiveEngine extends Engine {
    @Override
    protected Engine clone() {
        return new ExpensiveEngine();
    }
}

public class ExpensiveWing extends Wing {
    @Override
    protected Wing clone() {
        return new ExpensiveWing();
    }
}

 

사용자 입장에서는 버튼클릭을 통해서 

  • 싼 날개와 비싼 엔진을 클릭해서 전달됐다면 아래와같이 비행기를 생성할 수 있습니다.
public class AirplanePrototypeTest {
    @Test
    void test() {
        final AirPlane airplane =
                AirplaneFactory.createAirplane("cheapWing", "expensiveEngine");

        airplane.doSomething()....
    }
}

아니.. 그러면 clone 안하고 바로 미리 생성한 곳에서 뽑아서 새로만들어주면 되는거아닌가요?

  • 개인적으로는 clone 메소드 이후에,
    • initial하는 과정을 추가하므로써,
      • 해당 객체를 복제하는 역할을 그 객체에 위임하는 과정을 통해서 해당 과정을 서비스로직에서 드러나지 않게해서
      • 객체의 책임을 명확하게 하는 것이 이유라고 생각합니다. 
  • 아래와 같이 initialize 하는 과정을 수행하면 ExpensiveEngine 에 책임을 확실하게 부여할 수 있겠죠
    • 나중에 ExpensiveEngine의 initial 과정이 변하면 아래의 클래스 수정하면 될 것입니다.
public class ExpensiveEngine extends Engine {
    @Override
    protected Engine clone() {
        final ExpensiveEngine expensiveEngine = new ExpensiveEngine();
        expensiveEngine.initialize();
        return expensiveEngine;
    }
    
    public void initialize() {
        doSomething...
    }
}