Published on

Learning Design Pattern: Prototype Pattern - The Matrix Is Here

Authors

Prototype Pattern sounds dangerous but is actually the easiest Pattern in Design Pattern. Read on to learn more!

The Problem With Prototype

In many situations, we need to create new objects that have many similarities to existing objects. Creating these objects from scratch can be costly in terms of time and resources, and it's also easy to make mistakes if we create them manually.

Imagine you are building a graphic editing application. This application allows users to create different shapes, such as rectangles, circles, and triangles. Users often create new shapes with the same properties (e.g., color, border thickness) as shapes they have created before.

The challenge here is how to create new shapes efficiently, avoiding having to re-set the same properties each time.

Code Without Prototype Pattern

class Shape(
    var color: String,
    var borderWidth: Int,
    val shapeType: String
)

class ShapeFactory {
    fun createShape(type: String, color: String, borderWidth: Int): Shape {
        return Shape(color, borderWidth, type)
    }
}

fun main() {
    val factory = ShapeFactory()

    val redRectangle = factory.createShape("Rectangle", "red", 2)
    val anotherRedRectangle = factory.createShape("Rectangle", "red", 2) // Create another similar red rectangle
    val blueCircle = factory.createShape("Circle", "blue", 1)

    println(redRectangle)
    println(anotherRedRectangle)
    println(blueCircle)
}

Here, we have to pass the attributes (color, border thickness) to the createShape function each time we create a shape, even if they are the same.

Code With Prototype Pattern

interface Shape {
    fun draw()
}

data class Rectangle(
    var color: String,
    var borderWidth: Int
) : Shape {

    override fun draw() {
        println("Drawing a $color rectangle with border width $borderWidth")
    }
}

data class Circle(
    var color: String,
    var borderWidth: Int
) : Shape {

    override fun draw() {
        println("Drawing a $color circle with border width $borderWidth")
    }
}

fun main() {
    val prototypeRectangle = Rectangle("red", 2) // Prototype

    val redRectangle1 = prototypeRectangle.copy() as Rectangle
    redRectangle1.color = "red"
    redRectangle1.borderWidth = 2
    redRectangle1.draw()

    val redRectangle2 = prototypeRectangle.copy() as Rectangle
    redRectangle2.color = "red"
    redRectangle2.borderWidth = 2
    redRectangle2.draw()

    val blueCircle = Circle("blue", 1)
    blueCircle.draw()
}

Here, we create a "prototype" object (prototypeRectangle) and use the copy() function to create copies of it. Then, we can customize the properties of these copies.

Lessons From Prototype Pattern

Prototype Pattern helps us:

  • Create new objects based on existing objects.
  • Avoid recreating objects from scratch, saving time and resources.
  • Increase flexibility in creating object variations.

When designing a system, consider using Prototype Pattern when you need to create many similar objects and want to optimize the creation process.

Good luck to you all! Remember to follow @dantech!