디자인 패턴

Prototype 패턴

Keisa 2024. 6. 26. 14:18

 의도

프로토타입은 코드를 그들의 클래스들에 의존시키지 않고 기존 객체들을 복사할 수 있도록 하는 생성 디자인 패턴입니다.

 문제

객체가 있고 그 객체의 정확한 복사본을 만들고 싶다고 가정하면, 어떻게 하시겠습니까? 먼저 같은 클래스의 새 객체를 생성해야 합니다. 그런 다음 원본 객체의 모든 필드들을 살펴본 후 해당 값들을 새 객체에 복사해야 합니다.

너무 쉽군요! 하지만 함정이 있습니다. 객체의 필드들 중 일부가 비공개여서 객체 자체의 외부에서 볼 수 없을 수 있으므로 모든 객체를 그런 식으로 복사하지 못합니다.

객체를 '외부에서부터' 복사하는 것은 항상 가능하지 않습니다.

이 직접적인 접근 방식에는 한 가지 문제가 더 있습니다. 객체의 복제본을 생성하려면 객체의 클래스를 알아야 하므로, 당신의 코드가 해당 클래스에 의존하게 된다는 것입니다. 또, 예를 들어 메서드의 매개변수가 일부 인터페이스를 따르는 모든 객체를 수락할 때 당신은 그 객체가 따르는 인터페이스만 알고, 그 객체의 구상 클래스는 알지 못할 수 있습니다.

 해결책

프로토타입 패턴은 실제로 복제되는 객체들에 복제 프로세스를 위임합니다. 패턴은 복제를 지원하는 모든 객체에 대한 공통 인터페이스를 선언합니다. 이 인터페이스를 사용하면 코드를 객체의 클래스에 결합하지 않고도 해당 객체를 복제할 수 있습니다. 일반적으로 이러한 인터페이스에는 단일 clone 메서드만 포함됩니다.

clone 메서드의 구현은 모든 클래스에서 매우 유사합니다. 이 메서드는 현재 클래스의 객체를 만든 후 이전 객체의 모든 필드 값을 새 객체로 전달합니다. 대부분의 프로그래밍 언어는 객체들이 같은 클래스에 속한 다른 객체의 비공개 필드들에 접근​(access) 할 수 있도록 하므로 비공개 필드들을 복사하는 것도 가능합니다.

복제를 지원하는 객체를 이라고 합니다. 당신의 객체들에 수십 개의 필드와 수백 개의 가능한 설정들이 있는 경우 이를 복제하는 것이 서브클래싱의 대안이 될 수 있습니다.

미리 만들어진 프로토타입은 서브클래싱의 대안이 될 수 있습니다.

프로토타이핑은 다음과 같이 작동합니다. 일단, 다양한 방식으로 설정된 객체들의 집합을 만듭니다. 그 후 설정한 것과 비슷한 객체가 필요할 경우 처음부터 새 객체를 생성하는 대신 프로토타입을 복제하면 됩니다.

 실제상황 적용

실제 산업에서의 프로토타입​(원기)​은 제품의 대량 생산을 시작하기 전에 다양한 테스트를 수행하는 데 사용됩니다. 그러나 프로그래밍의 프로토타입의 경우 프로토타입들은 실제 생산과정에 참여하지 않고 대신 수동적인 역할을 합니다.

세포의 분열

산업 프로토타입들은 실제로 자신을 복제하지 않기 때문에, 프로토타입 패턴에 더 가까운 예시는 세포의 유사분열 과정입니다. 유사분열 후에는 한 쌍의 같은 세포가 형성됩니다. 원본 세포는 프로토타입 역할을 하며 복사본을 만드는 데 능동적인 역할을 합니다.

 

using std::string;

// Prototype Design Pattern
//
// Intent: Lets you copy existing objects without making your code dependent on
// their classes.

enum Type {
  PROTOTYPE_1 = 0,
  PROTOTYPE_2
};

/**
 * The example class that has cloning ability. We'll see how the values of field
 * with different types will be cloned.
 */

class Prototype {
 protected:
  string prototype_name_;
  float prototype_field_;

 public:
  Prototype() {}
  Prototype(string prototype_name)
      : prototype_name_(prototype_name) {
  }
  virtual ~Prototype() {}
  virtual Prototype *Clone() const = 0;
  virtual void Method(float prototype_field) {
    this->prototype_field_ = prototype_field;
    std::cout << "Call Method from " << prototype_name_ << " with field : " << prototype_field << std::endl;
  }
};

/**
 * ConcretePrototype1 is a Sub-Class of Prototype and implement the Clone Method
 * In this example all data members of Prototype Class are in the Stack. If you
 * have pointers in your properties for ex: String* name_ ,you will need to
 * implement the Copy-Constructor to make sure you have a deep copy from the
 * clone method
 */

class ConcretePrototype1 : public Prototype {
 private:
  float concrete_prototype_field1_;

 public:
  ConcretePrototype1(string prototype_name, float concrete_prototype_field)
      : Prototype(prototype_name), concrete_prototype_field1_(concrete_prototype_field) {
  }

  /**
   * Notice that Clone method return a Pointer to a new ConcretePrototype1
   * replica. so, the client (who call the clone method) has the responsability
   * to free that memory. If you have smart pointer knowledge you may prefer to
   * use unique_pointer here.
   */
  Prototype *Clone() const override {
    return new ConcretePrototype1(*this);
  }
};

class ConcretePrototype2 : public Prototype {
 private:
  float concrete_prototype_field2_;

 public:
  ConcretePrototype2(string prototype_name, float concrete_prototype_field)
      : Prototype(prototype_name), concrete_prototype_field2_(concrete_prototype_field) {
  }
  Prototype *Clone() const override {
    return new ConcretePrototype2(*this);
  }
};

/**
 * In PrototypeFactory you have two concrete prototypes, one for each concrete
 * prototype class, so each time you want to create a bullet , you can use the
 * existing ones and clone those.
 */

class PrototypeFactory {
 private:
  std::unordered_map<Type, Prototype *, std::hash<int>> prototypes_;

 public:
  PrototypeFactory() {
    prototypes_[Type::PROTOTYPE_1] = new ConcretePrototype1("PROTOTYPE_1 ", 50.f);
    prototypes_[Type::PROTOTYPE_2] = new ConcretePrototype2("PROTOTYPE_2 ", 60.f);
  }

  /**
   * Be carefull of free all memory allocated. Again, if you have smart pointers
   * knowelege will be better to use it here.
   */

  ~PrototypeFactory() {
    delete prototypes_[Type::PROTOTYPE_1];
    delete prototypes_[Type::PROTOTYPE_2];
  }

  /**
   * Notice here that you just need to specify the type of the prototype you
   * want and the method will create from the object with this type.
   */
  Prototype *CreatePrototype(Type type) {
    return prototypes_[type]->Clone();
  }
};

void Client(PrototypeFactory &prototype_factory) {
  std::cout << "Let's create a Prototype 1\n";

  Prototype *prototype = prototype_factory.CreatePrototype(Type::PROTOTYPE_1);
  prototype->Method(90);
  delete prototype;

  std::cout << "\n";

  std::cout << "Let's create a Prototype 2 \n";

  prototype = prototype_factory.CreatePrototype(Type::PROTOTYPE_2);
  prototype->Method(10);

  delete prototype;
}

int main() {
  PrototypeFactory *prototype_factory = new PrototypeFactory();
  Client(*prototype_factory);
  delete prototype_factory;

  return 0;
}

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

Adapter 패턴  (0) 2024.06.29
Singleton 패턴  (0) 2024.06.26
Builder 패턴  (0) 2024.06.17
Abstarct Factory 패턴  (0) 2024.06.13
Factory 패턴  (0) 2024.06.13