프로토타입 패턴

위키백과, 우리 모두의 백과사전.

프로토타입 패턴(prototype pattern)은 소프트웨어 디자인 패턴 용어로, 생성할 객체들의 타입이 프로토타입인 인스턴스로부터 결정되도록 하며, 인스턴스는 새 객체를 만들기 위해 자신을 복제(clone)하게 된다.

  • 프로토타입 패턴은, 추상 팩토리 패턴과는 반대로, 클라이언트 응용 프로그램 코드 내에서 객체 창조자(creator)를 서브클래스(subclass)하는 것을 피할 수 있게 해준다.
  • 프로토타입 패턴은 새로운 객체는 일반적인 방법(예를 들어, new를 사용해서라든지)으로 객체를 생성(create)하는 고유의 비용이 주어진 응용 프로그램 상황에 있어서 불가피하게 매우 클 때, 이 비용을 감내하지 않을 수 있게 해준다.

패턴을 구현하려면, 우선 clone() 메소드를 선언하는 추상 베이스 클래스를 하나 만든다. 다형적 생성자(polymorphic constructor) 기능이 필요한 클래스가 있다면, 그것을 앞에서 만든 클래스를 상속받게 한 후, clone() 메소드 내의 코드를 구현한다.

구조

원칙[편집]

객체 생성과 관련된 패턴들은 서로 영역이 겹치는 면이 있다. 프로토타입 패턴과 추상 팩토리 패턴 중 어느 하나가 적용될 수 있는 경우가 있다. 추상 팩토리 패턴이 프로토타입들의 집합을 갖고있다가, 클론(clone)한 뒤 프로덕트(product) 객체를 반환할 수도 있다.[1]:126쪽

추상 팩토리 패턴, 빌더 패턴, 프로토타입 패턴은 각 구현에 있어서 싱글턴 패턴을 활용할 수 있다.[1]:81쪽:134쪽

다시 말해 추상 팩토리 클래스는 종종 팩토리 메소드와 함께 구현하거나[2], 프로토타입을 이용해서 구현되기도 한다.[3]

보통 설계는 처음에는 팩토리 메소드로 출발한다. 다음에 설계자의 재량에 따라 추상 팩토리 패턴, 빌더 패턴, 프로토타입 패턴으로 바뀔 수 있다.[1]:136쪽

프로토타입은 서브클래싱을 필요로 하지 않는다. 하지만 "초기화" 동작을 필요로 한다. 팩토리 메서드 패턴은 서브클래싱을 필요로 하나, "초기화" 동작은 필요로 하지 않는다.

설계자는 컴포지트 패턴이나 데코레이터 패턴을 매우 많이 사용한 무거운 설계를 프로토타입을 사용하여 더 좋게 만들 수 있다.

원칙은 "런타임"에 또 다른 "객체"를 생성한다는 것이다. 다시 말해 이 시점에 가서 클로닝(cloning)을 하는 객체의 "실제 복사본"이 만들어지는 것이다. "실제 복사본"이라는 말은 새로 생성되는 객체가 클로닝(cloning) 당하는 객체의 애트리뷰트와 똑같은 애트리뷰트를 가질 것이라는 말이다. 반면에, "new"를 이용해 객체를 생성했다면, 새로이 생성된 객체의 애트리뷰트들은 초기값을 가질 것이다.

예를 들면, 은행 계좌 입출금 트랜잭션을 수행하는 시스템을 가정해 보자. 프로그래머는 은행 이용자의 계좌 정보를 갖고 있는 객체를 하나 복사한다. 그 객체를 가지고 그 객체 상에다 은행 입출금 트랜잭션을 수행한다. 그 다음 원래의 객체를 이 객체로 바꿔치기한다. 프로그래머는 이런 경우 new 대신 clone을 쓴다.

예제[편집]

C++[편집]

#include <iostream>
#include <map>
#include <string>
#include <cstdint>

using namespace std;

enum RECORD_TYPE_en
{
  CAR,
  BIKE,
  PERSON
};

/**
 * Record is the Prototype
 */

class Record
{
  public :

    Record() {}

    virtual ~Record() {}

    virtual Record* Clone() const=0;

    virtual void Print() const=0;
};

/**
 * CarRecord is Concrete Prototype
 */

class CarRecord: public Record
{
  private :
    string m_oStrCarName;

    uint32_t m_ui32ID;

  public :

    CarRecord(const string& _oStrCarName, uint32_t _ui32ID)
     : Record(), m_oStrCarName(_oStrCarName),
        m_ui32ID(_ui32ID)
    {
    }

    CarRecord(const CarRecord& _oCarRecord)
     : Record()
    {
      m_oStrCarName = _oCarRecord.m_oStrCarName;
      m_ui32ID = _oCarRecord.m_ui32ID;
    }

    ~CarRecord() {}

    CarRecord* Clone() const
    {
      return new CarRecord(*this);
    }

    void Print() const
    {
      cout << "Car Record" << endl
        << "Name : " << m_oStrCarName << endl
        << "Number: " << m_ui32ID << endl << endl;
    }
};

/**
 * BikeRecord is the Concrete Prototype
 */

class BikeRecord: public Record
{
  private :
    string m_oStrBikeName;

    uint32_t m_ui32ID;

  public :
    BikeRecord(const string& _oStrBikeName, uint32_t _ui32ID)
     : Record(), m_oStrBikeName(_oStrBikeName),
        m_ui32ID(_ui32ID)
    {
    }

    BikeRecord(const BikeRecord& _oBikeRecord)
     : Record()
    {
      m_oStrBikeName = _oBikeRecord.m_oStrBikeName;
      m_ui32ID = _oBikeRecord.m_ui32ID;
    }

    ~BikeRecord() {}

    BikeRecord* Clone() const
    {
      return new BikeRecord(*this);
    }

    void Print() const
    {
      cout << "Bike Record" << endl
        << "Name : " << m_oStrBikeName << endl
        << "Number: " << m_ui32ID << endl << endl;
    }
};

/**
 * PersonRecord is the Concrete Prototype
 */

class PersonRecord: public Record
{
  private :
    string m_oStrPersonName;

    uint32_t m_ui32Age;

  public :
    PersonRecord(const string& _oStrPersonName, uint32_t _ui32Age)
     : Record(), m_oStrPersonName(_oStrPersonName),
        m_ui32Age(_ui32Age)
    {
    }

    PersonRecord(const PersonRecord& _oPersonRecord)
     : Record()
    {
      m_oStrPersonName = _oPersonRecord.m_oStrPersonName;
      m_ui32Age = _oPersonRecord.m_ui32Age;
    }

    ~PersonRecord() {}

    Record* Clone() const
    {
      return new PersonRecord(*this);
    }

    void Print() const
    {
      cout << "Person Record" << endl
        << "Name: " << m_oStrPersonName << endl
        << "Age : " << m_ui32Age << endl << endl ;
    }
};

/**
 * RecordFactory is the client
 */

class RecordFactory
{
  private :
    map<RECORD_TYPE_en, Record* > m_oMapRecordReference;

  public :
    RecordFactory()
    {
      m_oMapRecordReference[CAR]    = new CarRecord("Ferrari", 5050);
      m_oMapRecordReference[BIKE]   = new BikeRecord("Yamaha", 2525);
      m_oMapRecordReference[PERSON] = new PersonRecord("Tom", 25);
    }

    ~RecordFactory()
    {
      delete m_oMapRecordReference[CAR];
      delete m_oMapRecordReference[BIKE];
      delete m_oMapRecordReference[PERSON];
    }

    Record* CreateRecord(RECORD_TYPE_en enType)
    {
      return m_oMapRecordReference[enType]->Clone();
    }
};

int main()
{
  RecordFactory* poRecordFactory = new RecordFactory();

  Record* poRecord;
  poRecord = poRecordFactory->CreateRecord(CAR);
  poRecord->Print();
  delete poRecord;

  poRecord = poRecordFactory->CreateRecord(BIKE);
  poRecord->Print();
  delete poRecord;

  poRecord = poRecordFactory->CreateRecord(PERSON);
  poRecord->Print();
  delete poRecord;

  delete poRecordFactory;
  return 0;
}

C#[편집]

public enum RecordType
{
   Car,
   Person
}

/// <summary>
/// Record is the Prototype
/// </summary>
public abstract class Record
{
   public abstract Record Clone();
}

/// <summary>
/// PersonRecord is the Concrete Prototype
/// </summary>
public class PersonRecord: Record
{
   string name;
   int age;

   public override Record Clone()
   {
      return (Record)this.MemberwiseClone(); // default shallow copy
   }
}

/// <summary>
/// CarRecord is another Concrete Prototype
/// </summary>
public class CarRecord: Record
{
   string carname;
   Guid id;

   public override Record Clone()
   {
      CarRecord clone = (Record)this.MemberwiseClone(); // default shallow copy
      clone.id = Guid.NewGuid(); // always generate new id
      return clone;
   }
}

/// <summary>
/// RecordFactory is the client
/// </summary>
public class RecordFactory
{
   private static Dictionary<RecordType, Record> _prototypes =
      new Dictionary<RecordType, Record>();

   /// <summary>
   /// Constructor
   /// </summary>
   public RecordFactory()
   {
      _prototypes.Add(RecordType.Car, new CarRecord());
      _prototypes.Add(RecordType.Person, new PersonRecord());
   }

   /// <summary>
   /// The Factory method
   /// </summary>
   public Record CreateRecord(RecordType type)
   {
      return _prototypes[type].Clone();
   }
}

Java[편집]

/** Prototype Class **/
public class Cookie implements Cloneable {

   public Object clone() {
      try {
         Cookie copy = (Cookie)super.clone();

         //In an actual implementation of this pattern you might now change references to
         //the expensive to produce parts from the copies that are held inside the prototype.

         return copy;

      }
      catch(CloneNotSupportedException e) {
         e.printStackTrace();
         return null;
      }
   }
}

/** Concrete Prototypes to clone **/
public class CoconutCookie extends Cookie { }

/** Client Class**/
public class CookieMachine {

   private Cookie cookie;//could have been a private Cloneable cookie;

   public CookieMachine(Cookie cookie) {
      this.cookie = cookie;
   }
   public Cookie makeCookie() {
      return (Cookie)cookie.clone();
   }
   public Object clone() { }

   public static void main(String args[]) {
      Cookie tempCookie =  null;
      Cookie prot = new CoconutCookie();
      CookieMachine cm = new CookieMachine(prot);
      for (int i=0; i<100; i++)
         tempCookie = cm.makeCookie();
   }
}

Python[편집]

    from copy import copy, deepcopy

    class Prototype:
        def __init__(self):
           self._objs = {}

        def registerObject(self, name, obj):
           """
           register an object.
           """
           self._objs[name] = obj

        def unregisterObject(self, name):
           """unregister an object"""
           del self._objs[name]

        def clone(self, name, **attr):
           """clone a registered object and add/replace attr"""
           obj = deepcopy(self._objs[name])
           obj.__dict__.update(attr)
           return obj

    # create another instance, w/state, of an existing instance of unknown class/state

    g = Graphic() # non-cooperative form
    shallow_copy_of_g = copy(g)
    deep_copy_of_g = deepcopy(g)

    class Graphic:
        def clone(self):
           return copy(self)
    g = Graphic() # cooperative form
    copy_of_g = g.clone()

PHP[편집]

PHP 5에서는, 객체는 레퍼런스 형태로 전달된다(pass by reference).

값 형태로 전달하기 위해서는, "매직 펑션" __clone()을 써야한다. 이러한 까닭으로 PHP 5에서는 프로토타입 패턴을 구현하기 매우 쉽다.[4]

<?php
abstract class BookPrototype {
    protected $title;
    protected $topic;
    abstract function __clone();
    function getTitle() {
        return $this->title;
    }
    function setTitle($titleIn) {
        $this->title = $titleIn;
    }
    function getTopic() {
        return $this->topic;
    }
}

class PHPBookPrototype extends BookPrototype {
    function __construct() {
        $this->topic = 'PHP';
    }
    function __clone() {
    }
}

class SQLBookPrototype extends BookPrototype {
    function __construct() {
        $this->topic = 'SQL';
    }
    function __clone() {
    }
}

  writeln('BEGIN TESTING PROTOTYPE PATTERN');
  writeln('');

  $phpProto = new PHPBookPrototype();
  $sqlProto = new SQLBookPrototype();

  $book1 = clone $sqlProto;
  $book1->setTitle('SQL For Cats');
  writeln('Book 1 topic: '.$book1->getTopic());
  writeln('Book 1 title: '.$book1->getTitle());
  writeln('');

  $book2 = clone $phpProto;
  $book2->setTitle('OReilly Learning PHP 5');
  writeln('Book 2 topic: '.$book2->getTopic());
  writeln('Book 2 title: '.$book2->getTitle());
  writeln('');

  $book3 = clone $sqlProto;
  $book3->setTitle('OReilly Learning SQL');
  writeln('Book 3 topic: '.$book3->getTopic());
  writeln('Book 3 title: '.$book3->getTitle());
  writeln('');

  writeln('END TESTING PROTOTYPE PATTERN');

  function writeln($line_in) {
    echo $line_in."<br/>";
  }
?>

[5]

출력은 다음과 같다.

BEGIN TESTING PROTOTYPE PATTERN

Book 1 topic: SQL
Book 1 title: SQL For Cats

Book 2 topic: PHP
Book 2 title: OReilly Learning PHP 5

Book 3 topic: SQL
Book 3 title: OReilly Learning SQL

END TESTING PROTOTYPE PATTERN

같이 보기[편집]

각주[편집]

  1. Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994년). 《Design Patterns》. Addison-Wesley. ISBN 0-201-63361-2. 
  2. 상속을 통해 객체를 생성
  3. 딜리게이션을 통해 객체를 생성
  4. object cloning
  5. Prototype Design Pattern in PHP