애플사이다의 iOS 개발 일지

[디자인 패턴] Prototype - 의존성 없이 객체를 복사할 때 본문

프로그래밍 철학

[디자인 패턴] Prototype - 의존성 없이 객체를 복사할 때

Applecider 2023. 7. 16. 21:42

GoF 디자인 패턴 중 하나인 Prototype 패턴을 정리했다.

- Ref : 도서 <Dive into Design Patterns>, Alexander Shvets 저


문제 상황

  • 객체 A가 있고, 해당 객체의 복사본을 만들고 싶은 상황이다.
  • 이때 새로운 객체 인스턴스를 생성하고, 원본 (객체 A)의 프로퍼티 값들을 새 객체에 모두 복사하는 방법이 있다.
  • 문제점 : 객체의 프로퍼티 중 일부가 비공개라면 외부에서 접근할 수 없다. 또한 복사하려면 원본을 알아야 하므로 해당 객체에 대한 의존성이 생긴다.

패턴 설명

  • 요약 : 의존성 없이 객체를 복사하는 생성 패턴이다.
  • Prototype = 시제품, 시험용으로 만들어 본 샘플 제품
    • 실제 산업의 프로토타입 : 대량 생산 이전에 테스트용으로 만드는 샘플 제품
    • 프로그래밍의 프로토타입 : 세포 분열과 비슷함. ‘원본 세포’는 복사본을 만들어내기 위한 ‘프로토타입 역할’을 함
  • 복제를 지원하는 객체를 ‘프로토타입’이라고 부른다.
    • 매번 객체를 처음부터 새로 만들지 않는다.
    • ‘복제되는 객체’에게 복제 프로세스를 위임한다. (설정값을 지정하여 미리 만들어둔 프로토타입을 복제해서 쓴다.)

  1. Prototype Protocol : clone 메서드를 정의한다. (반환타입은 Prototype Protocol 자기 자신이다.)
  2. ConcretePrototype : clone 메서드를 구현한다. (원본 객체의 프로퍼티값을 복사한다.)
  3. 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

 

🍎 포스트가 도움이 되었다면, 공감🤍 / 구독🍹 / 공유🔗 / 댓글✏️ 로 응원해주세요. 감사합니다.

Comments