프로그래밍 철학
[디자인 패턴] Prototype - 의존성 없이 객체를 복사할 때
Applecider
2023. 7. 16. 21:42
GoF 디자인 패턴 중 하나인 Prototype 패턴을 정리했다.
- Ref : 도서 <Dive into Design Patterns>, Alexander Shvets 저
문제 상황
- 객체 A가 있고, 해당 객체의 복사본을 만들고 싶은 상황이다.
- 이때 새로운 객체 인스턴스를 생성하고, 원본 (객체 A)의 프로퍼티 값들을 새 객체에 모두 복사하는 방법이 있다.
- 문제점 : 객체의 프로퍼티 중 일부가 비공개라면 외부에서 접근할 수 없다. 또한 복사하려면 원본을 알아야 하므로 해당 객체에 대한 의존성이 생긴다.
패턴 설명
- 요약 : 의존성 없이 객체를 복사하는 생성 패턴이다.
- Prototype = 시제품, 시험용으로 만들어 본 샘플 제품
- 실제 산업의 프로토타입 : 대량 생산 이전에 테스트용으로 만드는 샘플 제품
- 프로그래밍의 프로토타입 : 세포 분열과 비슷함. ‘원본 세포’는 복사본을 만들어내기 위한 ‘프로토타입 역할’을 함
- 복제를 지원하는 객체를 ‘프로토타입’이라고 부른다.
- 매번 객체를 처음부터 새로 만들지 않는다.
- ‘복제되는 객체’에게 복제 프로세스를 위임한다. (설정값을 지정하여 미리 만들어둔 프로토타입을 복제해서 쓴다.)
- Prototype Protocol : clone 메서드를 정의한다. (반환타입은 Prototype Protocol 자기 자신이다.)
- ConcretePrototype : clone 메서드를 구현한다. (원본 객체의 프로퍼티값을 복사한다.)
- Client : Prototype Protocol을 채택한 모든 객체의 복사본을 생성 가능하다.
예시 코드
// MARK: - Prototype Protocol
protocol Prototype {
init(prototype: Self)
}
extension Prototype {
func clone() -> Self {
return type(of: self).init(prototype: self)
}
}
// MARK: - Concrete Prototype
class ConcretePrototype1: Prototype {
var name: String
var weight: Double
init(name: String, weight: Double) {
self.name = name
self.weight = weight
}
required convenience init(prototype: ConcretePrototype1) {
self.init(name: prototype.name, weight: prototype.weight)
}
func printStatus() {
print("ConcretePrototype1 - name \(name) / weight \(weight)")
}
}
class ConcretePrototype2: Prototype {
var name: String
var weight: Double
init(name: String, weight: Double) {
self.name = name
self.weight = weight
}
required convenience init(prototype: ConcretePrototype2) {
self.init(name: prototype.name, weight: prototype.weight)
}
func printStatus() {
print("ConcretePrototype2 - name \(name) / weight \(weight)")
}
}
// MARK: - Client
class Client {
func main() {
let concrete1 = ConcretePrototype1(name: "con1", weight: 1.0)
let concrete2 = ConcretePrototype2(name: "con2", weight: 2.0)
print("Original")
concrete1.printStatus()
concrete2.printStatus()
let copied1 = concrete1.clone() // 완전히 새로운 객체 (원본의 참조가 복사된 게 아님)
let copied2 = concrete2.clone()
print("Clone")
copied1.printStatus()
copied2.printStatus()
print("---")
copied1.weight += 10.0 // 원본에 영향을 미치지 않음
concrete1.printStatus() // 1
copied1.printStatus() // 11
}
}
//Original
//ConcretePrototype1 - name con1 / weight 1.0
//ConcretePrototype2 - name con2 / weight 2.0
//Clone
//ConcretePrototype1 - name con1 / weight 1.0
//ConcretePrototype2 - name con2 / weight 2.0
//---
//ConcretePrototype1 - name con1 / weight 1.0
//ConcretePrototype1 - name con1 / weight 11.0
장점
- 복사할 객체에 대한 의존성을 없앤다.
- 중복되는 initializer 코드를 제거할 수 있다. (만들어 둔 프로토타입을 복제해서 쓰면 되므로)
- 복잡한 객체를 쉽게 생성한다.
- 복잡한 객체에 대한 설정값을 처리할 때 상속 대신 사용 가능한 방법이다.
단점
- 순환 참조를 하는 복잡한 객체를 복제하는 것은 어려울 수 있다.
- Reference
🍎 포스트가 도움이 되었다면, 공감🤍 / 구독🍹 / 공유🔗 / 댓글✏️ 로 응원해주세요. 감사합니다.