프로토타입 패턴
개요
보고서 기본 설정을 매번 처음부터 채우는 대신, 설정이 끝난 원본을 복제해서 일부만 바꾸고 싶다고 하자. 이런 복제용 원본을 프로토타입(Prototype), 즉 원형이라고 한다.
프로토타입 패턴에서는 객체가 자신의 복제 방법을 제공한다. 호출부는 내부 생성 절차를 다시 적지 않고 원형을 복제해 새 객체를 얻는다.
UML
호출부는 IPreset.Copy()를 사용한다. Preset이 복제 정책을 가지고, 변경 가능한 Tags 목록을 새로 만든다.
역사
GoF는 1994년 Design Patterns에서 Prototype을 생성 패턴으로 정리했다. 생성할 객체의 구체적인 종류를 원형으로 지정하고 이를 복제하는 방식이다.1
왜 사용하는가?
어떤 설정 묶음으로 시작할지 원형을 고르는 곳에서 결정할 수 있다. 복제 구현이 한곳에 있어 호출부가 내부 필드를 따라 복사하는 코드도 줄어든다.
복사는 새로운 객체 하나를 만든다는 말만으로 충분하지 않다. 변경 가능한 목록을 공유할지 독립시킬지, 파일 핸들처럼 복제할 수 없는 자원은 어떻게 할지 정해야 한다.
예제
원형을 복제한 뒤 복제본의 이름만 바꾼다.
using System;
using System.Collections.Generic;
using System.Linq;
var original = new Preset("base", new[] { "draft" });
var copy = (Preset)original.Copy();
copy.Name = "custom";
Console.WriteLine(original.Name + " / " + copy.Name);
interface IPreset { IPreset Copy(); }
sealed class Preset(string name, IEnumerable<string> tags) : IPreset
{
public string Name { get; set; } = name;
public List<string> Tags { get; } = new(tags);
public IPreset Copy() => new Preset(Name, Tags);
}
base / custom원형의 이름은 그대로 남는다. Copy()가 새 Preset을 반환하기 때문이다.
범위 넓힌 예제
복제본의 Tags에도 값을 추가해 본다. 새 목록을 만들었으므로 원형 목록에는 영향을 주지 않는다.
using System;
using System.Collections.Generic;
using System.Linq;
var original = new Preset("base", new[] { "draft" });
var copy = (Preset)original.Copy();
copy.Tags.Add("reviewed");
Console.WriteLine(string.Join(",", original.Tags));
Console.WriteLine(string.Join(",", copy.Tags));
interface IPreset { IPreset Copy(); }
sealed class Preset(string name, IEnumerable<string> tags) : IPreset
{
public string Name { get; set; } = name;
public List<string> Tags { get; } = new(tags);
public IPreset Copy() => new Preset(Name, Tags);
}
draft
draft,reviewed이 예제는 문자열 목록을 새로 만들고, 변경할 수 없는 문자열 값은 공유한다. 중첩된 가변 객체까지 독립시켜야 한다면 그 객체의 복제 규칙도 구현해야 한다. 모든 상태를 무조건 깊게 복사하는 규칙은 아니다.
같이 보기
각 예제는 독립된 .NET 10 콘솔 프로그램이다.