본문 바로가기
프로그래밍/디자인패턴

프로토타입(Prototype) 패턴

by bantomak 2024. 4. 30.
반응형

🤔문제

객체가 있고 그 객체의 정확한 복사본을 만들고 싶다고 가정하면, 어떻게 해야 할까?

먼저 같은 클래스의 새 객체를 생성해야 한다. 그런 다음 원본 객체의 모든 필드들을 살펴본 후 해당 값들을 새 객체에 복사해야 한다. 하지만 함정이 있다. 객체의 필드들 중 일부가 배공개여서 객체 자체의 외부에서 볼 수 없을 수 있으므로 모든 객체를 그런 식으로 복사하지 못한다.

 

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

 

😎해결책

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

 

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

 

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

 

🏠구조

예제 코드

using System;

namespace RefactoringGuru.DesignPatterns.Prototype.Conceptual
{
    public class Person
    {
        public int Age;
        public DateTime BirthDate;
        public string Name;
        public IdInfo IdInfo;

        public Person ShallowCopy()
        {
            return (Person) this.MemberwiseClone();
        }

        public Person DeepCopy()
        {
            Person clone = (Person) this.MemberwiseClone();
            clone.IdInfo = new IdInfo(IdInfo.IdNumber);
            clone.Name = String.Copy(Name);
            return clone;
        }
    }

    public class IdInfo
    {
        public int IdNumber;

        public IdInfo(int idNumber)
        {
            this.IdNumber = idNumber;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Person p1 = new Person();
            p1.Age = 42;
            p1.BirthDate = Convert.ToDateTime("1977-01-01");
            p1.Name = "Jack Daniels";
            p1.IdInfo = new IdInfo(666);

            // Perform a shallow copy of p1 and assign it to p2.
            Person p2 = p1.ShallowCopy();
            // Make a deep copy of p1 and assign it to p3.
            Person p3 = p1.DeepCopy();

            // Display values of p1, p2 and p3.
            Console.WriteLine("Original values of p1, p2, p3:");
            Console.WriteLine("   p1 instance values: ");
            DisplayValues(p1);
            Console.WriteLine("   p2 instance values:");
            DisplayValues(p2);
            Console.WriteLine("   p3 instance values:");
            DisplayValues(p3);

            // Change the value of p1 properties and display the values of p1,
            // p2 and p3.
            p1.Age = 32;
            p1.BirthDate = Convert.ToDateTime("1900-01-01");
            p1.Name = "Frank";
            p1.IdInfo.IdNumber = 7878;
            Console.WriteLine("\nValues of p1, p2 and p3 after changes to p1:");
            Console.WriteLine("   p1 instance values: ");
            DisplayValues(p1);
            Console.WriteLine("   p2 instance values (reference values have changed):");
            DisplayValues(p2);
            Console.WriteLine("   p3 instance values (everything was kept the same):");
            DisplayValues(p3);
        }

        public static void DisplayValues(Person p)
        {
            Console.WriteLine("      Name: {0:s}, Age: {1:d}, BirthDate: {2:MM/dd/yy}",
                p.Name, p.Age, p.BirthDate);
            Console.WriteLine("      ID#: {0:d}", p.IdInfo.IdNumber);
        }
    }
}

예제 코드 두 번째

using System;

public partial class Program
{
    public interface IClass
    {
        IClass Clone();
    }

    public class Warrior : IClass
    {
        public int hp;
        public int str;
        public string introduce;
        public Item weapon;

        public IClass Clone()
        {
            return this.MemberwiseClone() as IClass;
        }
    }

    public class Item
    {
        public string name;
    }

    static void Main(string[] args)
    {
        var warrior = new Warrior();
        warrior.hp = 2;
        warrior.str = 3;
        warrior.introduce = "hi, i am warriror.";
        warrior.weapon = new Item();
        warrior.weapon.name = "sword";


        var new_warrior = (Warrior)warrior.Clone();
        new_warrior.hp = 5;
        new_warrior.str = 10;
        new_warrior.introduce = "hi, i am new warriror.";
        warrior.weapon.name = "spear";

        Console.WriteLine($"warrior hp: {warrior.hp} str: {warrior.str} introduce: {warrior.introduce} weapon: {warrior.weapon.name}");
        Console.WriteLine($"new warrior hp: {new_warrior.hp} str: {new_warrior.str} introduce: {new_warrior.introduce} weapon: {new_warrior.weapon.name}");
    }
}

👍장단점

  • 객체들을 그 구상 클래스들에 결합하지 않고 복제할 수 있다.
  • 반복되는 초기화 코드를 제거한 후 그 대신 미리 만들어진 프로토타입들을 복제하는 방법을 사용할 수 있다.
  • 복잡한 객체들을 더 쉽게 생성할 수 있다.
  • 복잡한 객체들에 대한 사전 설정들을 처리할 때 상속 대신 사용할 수 있다.
  • 순환 참조가 있는 복잡한 객체들을 복제하는 것은 매우 까다로울 수 있다.

🔍다른 패턴관의 관계

  • 많은 디자인은 복잡성이 낮고 자식 클래스들을 통해 더 많은 커스터마이징이 가능한 팩토리 메서드로 시작해 더 유연하면서도 더 복잡한 추상 팩토리, 프로토타입 또는 빌더 패턴으로 발전해 나간다.
  • 추상 팩토리 클래스들은 팩토리 메서드들의 집합을 기반으로 하는 경우가 많다. 프로토타입을 사용하여 추상 팩토리의 구상 클래스들의  생성 메서드들을 구현할 수도 있다.
  • 프로토타입커맨드 패턴의 복사본들을 기록에 저장해야 할 때 도움이 된다.
  • 데코레이터 복합체 패턴을 많이 사용하는 디자인들은 프로토타입을 사용하면 종종 이득을 볼 수 있다. 프로토타입 패턴을 적용하면 복잡한 구조들을 처음부터 다시 건축하는 대신 복제할 수 있기 때문이다.
  • 프로토타입은 상속을 기반으로 하지 않으므로 상속과 관련된 단점들이 없다. 반면에 프로토타입은 복제된 객체의 복잡한 초기화가 필요하다. 팩토리 메서드는 상속을 기반으로 하지만 초기화 단계가 필요하지 않다.
  • 때로는 프로토타입메멘토 패턴의 더 간단한 대안이 될 수 있으며, 이 패턴은 상태를 기록에 저장하려는 객체가 간단하고 외부 리소스에 대한 링크가 없거나 링크들이 있어도 이들을 재설정하기 쉬운 경우에 작동한다.
  • 추상 팩토리들, 빌더들, 및 프로토타입들은 모두 싱글턴으로 구현할 수 있다.

함께 읽으면 좋은 글

 

C# 얕은 복사(Shallow Copy), 깊은 복사(Deep Copy)

얕은 복사, 깊은 복사에 대해서얕은 복사(Deep Copy) class Point{ public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } public overri..

jettstream.tistory.com

참고 사이트

 

리팩터링과 디자인 패턴

Hello, world! Refactoring.Guru는 리팩토링, 디자인 패턴, SOLID 원칙 및 기타 스마트 프로그래밍 주제에 대해 알아야 할 모든 것을 쉽게 찾을 수 있는 자원입니다. 이 사이트에서는 이러한 모든 주제가 어

refactoring.guru

댓글