🤔문제
객체가 있고 그 객체의 정확한 복사본을 만들고 싶다고 가정하면, 어떻게 해야 할까?
먼저 같은 클래스의 새 객체를 생성해야 한다. 그런 다음 원본 객체의 모든 필드들을 살펴본 후 해당 값들을 새 객체에 복사해야 한다. 하지만 함정이 있다. 객체의 필드들 중 일부가 배공개여서 객체 자체의 외부에서 볼 수 없을 수 있으므로 모든 객체를 그런 식으로 복사하지 못한다.
이 직접적인 접근 방식에는 한 가지 문제가 더 있다. 객체의 복제본을 생성하려면 객체의 클래스를 알아야 하므로, 코드가 해당 클래스에 의존하게 된다는 것이다. 또, 예를 들어 메서드의 매개변수가 일부 인터페이스를 따르는 모든 객체를 수락할 때 당신은 그 객체가 따르는 인터페이스만 알고, 그 객체의 구상 클래스는 알지 못할 수 있다.
😎해결책
프로토타입 패턴은 실제로 복제되는 객체들에 복제 프로세스를 위임한다. 패턴은 복제를 지원하는 모든 객체에 대한 공통 인터페이스를 선언한다. 이 인터페이스를 사용하면 코드를 객체의 클래스에 결합하지 않고도 해당 객체를 복제할 수 있다. 일반적으로 이러한 인터페이스에는 단일 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}");
}
}
👍장단점
- 객체들을 그 구상 클래스들에 결합하지 않고 복제할 수 있다.
- 반복되는 초기화 코드를 제거한 후 그 대신 미리 만들어진 프로토타입들을 복제하는 방법을 사용할 수 있다.
- 복잡한 객체들을 더 쉽게 생성할 수 있다.
- 복잡한 객체들에 대한 사전 설정들을 처리할 때 상속 대신 사용할 수 있다.
- 순환 참조가 있는 복잡한 객체들을 복제하는 것은 매우 까다로울 수 있다.
🔍다른 패턴관의 관계
- 많은 디자인은 복잡성이 낮고 자식 클래스들을 통해 더 많은 커스터마이징이 가능한 팩토리 메서드로 시작해 더 유연하면서도 더 복잡한 추상 팩토리, 프로토타입 또는 빌더 패턴으로 발전해 나간다.
- 추상 팩토리 클래스들은 팩토리 메서드들의 집합을 기반으로 하는 경우가 많다. 프로토타입을 사용하여 추상 팩토리의 구상 클래스들의 생성 메서드들을 구현할 수도 있다.
- 프로토타입은 커맨드 패턴의 복사본들을 기록에 저장해야 할 때 도움이 된다.
- 데코레이터 및 복합체 패턴을 많이 사용하는 디자인들은 프로토타입을 사용하면 종종 이득을 볼 수 있다. 프로토타입 패턴을 적용하면 복잡한 구조들을 처음부터 다시 건축하는 대신 복제할 수 있기 때문이다.
- 프로토타입은 상속을 기반으로 하지 않으므로 상속과 관련된 단점들이 없다. 반면에 프로토타입은 복제된 객체의 복잡한 초기화가 필요하다. 팩토리 메서드는 상속을 기반으로 하지만 초기화 단계가 필요하지 않다.
- 때로는 프로토타입이 메멘토 패턴의 더 간단한 대안이 될 수 있으며, 이 패턴은 상태를 기록에 저장하려는 객체가 간단하고 외부 리소스에 대한 링크가 없거나 링크들이 있어도 이들을 재설정하기 쉬운 경우에 작동한다.
- 추상 팩토리들, 빌더들, 및 프로토타입들은 모두 싱글턴으로 구현할 수 있다.
함께 읽으면 좋은 글
참고 사이트
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
Unity 디자인 패턴과 SOLID 최신 개정판 무료 배포 (5) | 2024.10.14 |
---|---|
싱글톤(Singleton) 패턴 (1) | 2024.04.30 |
빌더(Builder) 패턴 (1) | 2024.04.24 |
추상 팩토리(Abstract Factory) 패턴 (1) | 2024.04.24 |
팩토리 메서드(Factory Method) 패턴 (0) | 2024.04.23 |
댓글