프로토타입 패턴
원형이 되는 객체를 사용하여 생성할 객체의 종류를 명시하고, 이렇게 만든 견본을 복사해서 새로운 객체를 생성하는데 목적은 둔 패턴이다.
구조
- Prototype : 자신을 복제하는 데 필요한 인터페이스를 정의한다.
- ConcretePrototype - 자신을 복제하는 연산을 정의한다.
- Client - 원형 객체에 자기 자신의 복제를 요청하여 새로운 객체를 생성한다.
기본 인터페이스
namespace Prototype;
//프로토타입 인터페이스
public interface Prototype
{
public Prototype Clone();
}
//프로토타입 상속 클래스
public class ConcretePrototypeA : Prototype
{
private int a;
private int b;
public ConcretePrototypeA(ConcretePrototypeA origin)
{
this.a = origin.a;
this.b = origin.b;
}
public ConcretePrototypeA(int a, int b)
{
this.a = a;
this.b = b;
}
public Prototype Clone()
{
ConcretePrototypeA clone = new ConcretePrototypeA(this);
return clone;
}
public void Print()
{
Console.WriteLine($"a : {a} b : {b}");
}
}
public class MainTest
{
public static int Main(string[] args)
{
//원본 생성
ConcretePrototypeA origin = new ConcretePrototypeA(3, 5);
origin.Print();
//원본 복사
ConcretePrototypeA clone = (ConcretePrototypeA)origin.Clone();
clone.Print();
return 0;
}
}
a : 3 b : 5
a : 3 b : 5
왜 사용해야 할까?
단적으로 말하자면 객체를 복사하고 원본에서 독립시키기 위해 사용한다. 그림이나 사진을 복사할 때 복사본은 원본과 똑같은 값을 가지지만, 원본에서 독립하여 다른 형태로 변형하거나 값을 수정할 수 있다. 필요할 때마다 원본을 복제해서 사용하는 게 매번 필요한 상태 조합을 입력하여 수동적으로 초기화하는 것보다 더 편리할 수 있다.
프로토타입 패턴을 사용하지 않고 외부에서 복제를 실시할 수도 있다. 그러나 이 경우 해당 객체의 속성들에 직접 접근할 수 있기 때문에 캡슐화에 실패한 경우로 생각할 수 있다. 또한 이후 해당 클래스가 수정될 경우 외부 클래스의 코드까지 수정해야 하기 때문에 부적절하다.
public class Marine : Unit
{
public int _hp;
public int _damage;
public Marine();
public Marine(int hp, int damage)
{
this._hp = hp;
this._damage = damage;
}
public LooseHp(int damaged)
{
this._hp -= damaged;
}
}
public class MainTest
{
public static int Main(string[] args)
{
Unit marineA = new Marin(40, 5);
marineA.LooseHP(5);
//외부 클래스에서 직접 마린 객체를 복제한다.
//마린 객체의 속성에 직접 접근한다.
Unit marineB = new Marine(marineA._hp, marineA._damage);
//마린A : hp : 35, damage : 5
//마린B : hp : 35, damage : 5
return 0;
}
}
또한 클래스에 접근 불가능한 속성이 있다면 완전한 복제에 성공할 수 없을 것이다.
public class Marine : Unit
{
public int _hp;
private int _damage;
public Marine();
public Marine(int hp, int damage)
{
this._hp = hp;
this._damage = damage;
}
public LooseHp(int damaged)
{
this._hp -= damaged;
}
}
public class MainTest
{
public static int Main(string[] args)
{
Unit marineA = new Marin(40, 5);
marineA.LooseHP(5);
Unit marineB = new Marine(marineA._hp, marineA._damage); // Error! _damage에 직접 접근할 수 없다.
return 0;
}
}
프로토타입 패턴은 원본에서 직접 복제하여 복사본을 반환하기 때문에 모든 속성을 복제할 수 있고, 클라이언트에선 복제 함수만 호출하면 되기 때문에 결합도가 낮아진다.
public class Marine : Unit
{
public int _hp;
private int _damage;
public Marine();
public Marine(int hp, int damage)
{
this._hp = hp;
this._damage = damage;
}
public LooseHp(int damaged)
{
this._hp -= damaged;
}
public Unit Clone()
{
Unit clone = new Marine(_hp, _damage);
return clone;
}
}
public class MainTest
{
public static int Main(string[] args)
{
Unit marineA = new Marin(40, 5);
marineA.LooseHP(5);
//원본이 직접 복제본을 만든다.
Unit marineB = marineA.Clone();
return 0;
}
}
장점
- 객체를 만드는 과정을 숨길 수 있다.
- 객체에서 직접 복사본을 만들 수 있기 때문에 런타임 동안 새로운 객체를 추가하고 삭제할 수 있다.
- 클라이언트 코드와 객체의 결합도가 낮아진다.
- 복잡한 객체들을 더 쉽고 싸게 생성할 수 있다.
- 반환하는 객체를 추상적인 타입으로 반환할 수 있기 때문에 좀 더 유연한 객체를 생성할 수 있다.
- 팩토리 메서드를 사용하여 원본을 생성하는 것에 비해 서브 클래스의 수가 많이 줄어든다.
단점
- 복잡한 객체를 만드는 과정 자체가 복잡할 수 있다.(특히, 순환 참조)
참고자료
'프로그래밍 이론 > 디자인 패턴' 카테고리의 다른 글
06. [Design Pattern] 어댑터(Adapter) 패턴 (0) | 2023.11.21 |
---|---|
05. [Design Pattern] 싱글톤(Singleton) 패턴 (0) | 2023.11.16 |
03. [Design Pattern] 빌더(Builder) 패턴 (0) | 2023.11.07 |
02. [Design Pattern] 추상 팩토리(Abstract Factory) 패턴 (0) | 2023.10.27 |
01. [Design Pattern] 팩토리 메서드(Factory Method) 패턴 (0) | 2023.10.27 |