티스토리 뷰

반응형

안녕하세요 Pingu입니다.🐧

 

지난 글에서는 Factory Method Pattern에 대해 알아봤는데요, 이번 글에서는 또 다른 Creational Pattern 중 하나인 Prototype Pattern(프로토타입)에 대해 알아보도록 하겠습니다.

 

프로토타입 패턴이란?

프로토타입 패턴의 정의는 코드를 클래스에 종속시키지 않고 기존에 존재하던 객체를 복사할 수 있는 디자인 패턴입니다.

 

어떤 객체가 존재하고 해당 객체와 정확하게 똑같은 객체를 만들고 싶을 때 프로토타입 패턴을 사용할 수 있습니다. 어떻게 하면 특정 객체와 동일한 객체를 만들 수 있을까요? 일단 클래스와 같은 참조 타입을 복사하는 방법에는 얕은 복사(shallow copy)와 깊은 복사 (deep copy)가 있습니다. 얕은 복사는 그냥 새로운 변수에 기존 객체를 할당하는 방법인데, 이렇게 생성한 객체는 기존 객체를 계속 가리키고 있기 때문에 새로운 객체를 만든 것이라 할 수 없어요. 따라서 해당 객체의 모든 정보를 동일하게 가진 새로운 객체를 만들기 위해서는 깊은 복사를 해줘야 하며 프로토타입 패턴은 이러한 작업을 쉽게 할 수 있도록 도와주는 패턴입니다.

  • Prototype
    • 객체를 복제할 방법을 선언합니다.
  • Concrete Prototype
    • 객체를 복제하는 방법을 실제로 구현하는 곳 입니다.
  • Client
    • 프로토타입 인터페이스를 따르는 모든 객체의 복제본을 생성할 수 있습니다.

프로토타입 패턴은 언제 쓰나요?

어떤 객체를 새로 만드는데 똑같은 값을 갖는 객체로 만들고 싶을 때 사용할 수 있습니다. 이를 해결하는 가장 간단한 방법은 원본 객체의 모든 값을 가지고 직접 새로운 객체를 만드는 방법인데요, 이렇게 직접 만드는 경우 접근 제한이 private인 프로퍼티에는 접근할 수 없기 때문에 복사를 하지 못할 수도 있습니다. 또한 복사본을 만들기 위해 원본 객체의 클래스를 알아야 하기 때문에 코드가 해당 클래스에 종속되어 의존성이 발생합니다. 

 

이런 상황에서 프로토타입 패턴을 사용할 수 있는데요, 매번 객체를 직접 만들기보다는 기존 객체의 정보와 동일한 정보를 갖는 새로운 객체를 생성하는 역할을 하는 프로토타입 클래스를 만들어서 간편하게 사용할 수 있습니다. 또한 프로토타입이 인터페이스를 갖는 객체를 허용하여 클래스 의존성도 해결할 수 있습니다. 즉 객체의 복제를 직접 하는 것이 아닌 프로토타입 클래스에 넘겨서 처리하는 패턴이라고 할 수 있습니다.

프로토타입 패턴의 결과

  • 프로토타입을 사용하면 클라이언트에 프로토타입 인스턴스를 등록하기만 하면 새로운 객체를 복제할 수 있게 됩니다. 이를 런타임에서 처리하기 때문에 다른 생성 패턴보다 유연하다고 할 수 있어요.
  • 새로운 객체를 만들 때 default 값이 아닌 다른 값으로 객체를 생성할 수 있게 됩니다.
  • 다양한 구조로 객체를 하위 클래스에서 만들 수 있게 됩니다.

 예제

그럼 Swift언어로 프로토타입 패턴을 실제로 구현해보도록 하겠습니다.

이번 코드에서는 스타크래프트라는 게임에서 마린과 메딕이라는 유닛을 계속 생성하는 예제를 만들어보려고 합니다.

같은 유닛은 모두 같은 능력을 갖지만 서로 다른 객체니까 프로토타입 패턴을 사용하여 계속 같은 객체를 복제만 해주면 될 거 같아요.

 

먼저 Prototype이 필요하겠죠?

// Prototype
public protocol Prototype: class {
    init(prototype: Self)
}
extension Prototype {
    public func clone() -> Self {
        return type(of: self).init(prototype: self)
    }
}

그럼 Prototype 프로토콜을 채택하여 실제 복사를 하는 Concrete Prototype인 Marin, Medic 클래스를 만들어보겠습니다.

// Concrete Prototype
public class Marin: Prototype {
    
    public var health: Int
    public var attack: Int
    public var defence: Int
    
    public init(health: Int, attack: Int, defence: Int) {
        self.health = health
        self.attack = attack
        self.defence = defence
    }
    
    public required convenience init(prototype: Marin) {
        self.init(health: prototype.health, attack: prototype.attack, defence: prototype.defence)
    }
    
    func printStatus() {
        print("Marin Health: \(health) Attack: \(attack) Defence: \(defence)")
    }
}
// Concrete Prototype
public class Medic: Prototype {
    
    public var health: Int
    public var mana: Int
    public var defence: Int
    
    public init(health: Int, mana: Int, defence: Int) {
        self.health = health
        self.mana = mana
        self.defence = defence
    }
    
    public required convenience init(prototype: Medic) {
        self.init(health: prototype.health, mana: prototype.mana, defence: prototype.defence)
    }
    func printStatus() {
        print("Medic Health: \(health) Mana: \(mana) Defence: \(defence)")
    }
}

그럼 이제 이렇게 만든 Marin, Medic 객체를 하나씩 만들어 보겠습니다.

그럼 이렇게 만들어진 Marin, Medic 객체를 clone() 메서드를 사용해서 복제해보겠습니다.

 

그럼 정말 새로운 객체가 생성된 것인지 궁금하니 한 번 확인해보겠습니다.

코드를 보시면 얕은 복사로 생성한 마린의 체력을 7 올렸는데 원본 마린의 체력도 올라간 것을 볼 수 있습니다.

하지만 깊은 복사로 생성한 마린의 값은 변경하더라도 원본 마린에 영향을 주지 않는 것을 볼 수 있어요.

 

이렇게 프로토타입 패턴을 알아보고 직접 구현도 해봤습니다.

혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다.

 

전체 코드는 여기에서 볼 수 있습니다!

 

감사합니다.

 

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함