Prototype Pattern

Seungjae·2022년 4월 20일
0

디자인 패턴 공부

목록 보기
4/4

코드를 복사하려는 구체적인 클래스에 종속시키지 않고 기존 개체를 복사할 수 있는 패턴이다.

Problem


객체가 있고 그 객체의 정확한 복사본을 만들고 싶다고 가정해보자. 먼저 동일한 클래스의 새 객체를 생성해야 한다. 그런 다음 원본 객체의 모든 필드를 살펴보고 해당 값을 새 객체에 복사한다.

하지만 이런 경우도 있을 것이다. 개체의 필드 중 일부가 private이라서 객체 외부에서 볼 수 없는 경우에는 그런 식으로 복사할 수 없을 것이다.

또한 한 가지 문제가 더 있다. 복제본을 생성하려면 객체의 클래스를 알아야 하므로 클라이언트 코드가 해당 클래스에 종속된다. 그래서 클라이언트가 복사하려는 객체의 구체적인 클래스까지 알게되는 단점이 있다.

Solve


Prototype Pattern은 복제되는 실제 객체에 복제와 관련된 책임을 위임한다. 패턴을 적용하기 위해 복제를 지원하는 모든 객체에 대한 공통 인터페이스를 선언한다. 이 인터페이스를 사용하면 코드를 해당 객체의 구체적인 클래스에 연결하지 않고도 객체를 복제할 수 있다. 일반적으로 이러한 인터페이스에는 단일 clone메서드만 포함된다.

clone의 구현은 모든 클래스에서 매우 유사하다. 이 메서드는 현재 클래스의 객체를 만들고 이전 객체의 모든 필드 값을 새 객체로 전달한다. 동일 클래스이기에 private한 프로퍼티에 접근할 수 있다.

여기서 복제를 지원하는 객체를 프로토타입 이라고 한다. 객체에 수십 개의 필드와 수백 개의 가능한 구성이 있고, 이와 똑같은 객체가 필요할 경우 복제하는 것은 좋은 해결책이 될 수 있다.

Code


Shape

package prototype

abstract class Shape() {
  abstract val x: Int
  abstract val y: Int
  abstract val color: String

  abstract fun clone(): Shape

  override fun equals(other: Any?): Boolean {
    if (other is Shape) {
      return x == other.x && y == other.y && color == other.color
    }

    return false
  }
}

Circle

package prototype

class Circle(
  override val x: Int,
  override val y: Int,
  override val color: String,
  val radius: Int
) : Shape() {
  override fun clone(): Shape = Circle(x, y, color, radius)

  override fun equals(other: Any?): Boolean {
    if (other is Circle) {
      return super.equals(other) && (radius == other.radius)
    }

    return false
  }

  override fun toString(): String = super.toString() + " ,radius : ${radius}"
}

Rectangle

package prototype

class Rectangle(
  override val x: Int,
  override val y: Int,
  override val color: String,
  val width: Int,
  val height: Int
) : Shape() {
  override fun clone(): Shape = Rectangle(x, y, color, width, height)

  override fun equals(other: Any?): Boolean {
    if (other is Rectangle) {
      return super.equals(other) && (width == other.width && height == other.height)
    }

    return false
  }

  override fun toString(): String = super.toString() + " ,width: ${width}, height: ${height}"
}

Tester

package prototype

class Tester {
  fun compareClone(shapeList: List<Shape>) {
    shapeList.forEach {
      val clone = it.clone()
      println("shape1: ${it}")
      println("shape2: ${clone}")
      println("equality: ${it == clone}")
      println("identity: ${it === clone}")
    }
  }
}

Main

package prototype

fun main() {
  val tester = Tester()

  val shapeList = mutableListOf<Shape>()

  val circle = Circle(10, 20, "red", 15)
  shapeList.add(circle)

  val rectangle = Rectangle(10, 20, "blue", 15, 20)
  shapeList.add(rectangle)

  tester.compareClone(shapeList)
}

구조


적용가능성


  • 클라이언트가 복사해야 하는 구체적인 객체 클래스에 의존하지 않았으면 하는 경우
  • 각각의 객체를 초기화하는 방식만 다른 하위 클래스의 수를 줄이려는 경우(객체의 값 복사에 대한 답으로 서브 클래싱을 선택한 경우) (예를 들어 Circle의 특정 복사본을 얻기 위해 Circle을 상속하여 radius가 100인 BigCircle을 아예 따로 만드는 방안을 선택한 경우를 말하는 것 같다.)

장점﹒단점


장점

  • 구체적인 클래스에 연결하지 않고 개체를 복제할 수 있다.
  • 복제를 위해 반복되는 초기화 코드를 제거할 수 있다.
  • 복잡한 객체의 복사본을 보다 쉽게 얻을 수 있다.

단점

  • 순환 참조가 있는 복잡한 개체를 복제하는 것은 매우 까다로울 수 있다.

Git


https://github.com/oh980225/DesignPattern/tree/main/src/main/kotlin/prototype

Ref


https://refactoring.guru/design-patterns/prototype

profile
코드 품질의 중요성을 아는 개발자 👋🏻

0개의 댓글