Creating the Creational Design Patterns in Kotlin

Creating the Creational Design Patterns in Kotlin


Published in ProAndroidDev

This article was published in ProAndroidDev so you could also read it here

This blog is for someone who is getting started with learning Design Patterns in Kotlin. Here, I have tried to create creational design patterns from scratch rather than just providing their syntax and theory.

First, let us revisit what are Design Patterns in general

Design Patterns are a proven solution to common problems. A Design pattern doesn’t invent something new it just leverages tools or functionalities of a system to solve a common problem in a scalable way. They are not limited to a specific programming language such as Kotlin. In fact, they are not specific to the concept of programming itself.

A very simple example could be that when we drive a vehicle, we have to look out for the vehicles which are coming from behind us and as we have to keep our focus on the road, rear-view Mirrors are installed on every vehicle to make this job easier and safe. We are surrounded by design patterns 😄

One thing to note is that mirrors that are installed for the rear-view need to be modified in terms of their shape depending upon the vehicle similarly in programming when we implement a Design Pattern we cannot simply copy it we have to modify it as per our needs as a Design Pattern is like a blueprint which can be followed to solve a problem rather than the actual solution.

The low-level patterns which are limited to a specific programming language are often referred to as idioms

Generally, Design Patterns in software design are divided into 3 types

  1. Creational patterns provide object creation mechanisms that increase flexibility and reuse of existing code. Examples include: Singleton, Factory Method, Abstract Factory, Builder etc.

  2. Structural patterns explain how to assemble objects and classes into larger structures, while keeping these structures flexible and efficient. Examples include: Adapter, Decorator, Façade, Proxy etc.

  3. Behavioral patterns take care of effective communication and the assignment of responsibilities between objects. Examples include: Observer, Visitor, Iterator, State etc.

Let’s get into the creation of the Creational Design Patterns.

Creating the Singleton Design Pattern

The Singleton Design Pattern makes sure that a class has only a single instance and that instance can be accessed globally from within the code. The most relevant use case can be the Implementation of Local Database of an app.

Kotlin makes creating singletons easy by introducing a keyword called object. E.g.

object SingletonDemo {  
  val value = "some value" 
}

That’s it, SingletonDemo can now be accessed globally and every time same instance will be returned.

fun main() {
 
 val o1 = SingletonDemo
 val o2 = SingletonDemo

 println(o1.hashCode() == o2.hashCode()) // true
}

Let us see how we can create a Singleton without using the object Keyword.

  1. As the Singleton can be accessed globally we have to make sure that the code which has access to it cannot create it’s instance. This can be accomplished by making the constructor of the class private because by design calling the constructor of a class must always create a new object.

  2. As the other classes cannot create the instance of our to-be Singleton class but still need to have access to its instance and that too same one every time. This can be done by creating a static method in the class which will access to the private constructor of the class for object creation. Again this static method needs to return the same instance every time so to facilitate this the instance of the class can be stored as a static variable and this variable can be returned by the static method to provide the class’s Instance. It will become clear in the code given below.

class SingletonImpl private constructor() {

 val value = "some value"

    companion object {
       private var instance: SingletonImpl? = null

        fun getInstance(): SingletonImpl {
            return instance ?: SingletonImpl().also { instance = it }
        }

    }
}
fun main() {
 
 val o1 = SingletonImpl.getInstance()
 val o2 = SingletonImpl.getInstance()

 // Verify that same object is returned
 println(o1.hashCode() == o2.hashCode()) // true
}

This is the bare minimum implementation and still has a lot of problems such as

  1. Not Thread Safe: The above implementation is not thread-safe as the getInstance() function can be called by multiple threads concurrently and if the instance is null create multiple instances of SingletonImpl in the memory thus breaking the Singleton pattern’s requirement of having a single instance.

Solution:
@Volatile Keyword: The @Volatile keyword is used to ensure that changes to the instance variable are immediately visible to all threads. This is important because the Singleton instance may be cached in a thread’s local memory, and volatile ensures proper visibility.

Double-Check Locking: The getInstance() function first checks if the instance is null without synchronization. If it’s null, it enters a synchronized block to perform a second check. This second check is necessary because multiple threads may have passed the first check simultaneously, and only one of them should create the instance. It minimizes the synchronization overhead by checking if the instance is null before entering the synchronized block, hence the term “Double-Check Locking.”

Synchronization: The synchronized block ensures that only one thread can create an instance of SingletonImpl at a time. This prevents multiple instances from being created in a multi-threaded environment.

class SingletonImpl private constructor() {
 var value = "some value"

    companion object {
        @Volatile
        private var instance: SingletonImpl? = null
        fun getInstance(): SingletonImpl {
            if (instance == null) {
                synchronized(this) {
                    if (instance == null) {
                        instance = SingletonImpl()
                    }
                }
            }
            return instance!!
        }
    }
}

Creating The Factory Method Design Pattern

Factory is a creational design pattern and is used to create objects. But why do we need a design pattern for creating objects? Isn’t the usual constructor call enough?

The Issues while using constructors for object creations are

  1. Lazy Initialization is not possible: A constructor’s design is such that a call to it should always return a new object. Hence even if the object that is to be returned is same as the previous one, calling the constructor of a class will force it to make a new one hence unnecessary overhead.

  2. By Design calling the constructor of a class should create and return an object of the same class and not it’s subclasses. This is not always the requirement hence the Factory design patterns enables a developer to overcome it.

For the sake of better understanding lets choose a real-world example. Lets say we are creating an app/plugin/website which takes a valid String and converts it into one of the two Text Formats of Json or Xml. While implementing our solution we need to make sure that our code should be scalable in such a way that more formats can be added in the future.

Let’s first create an Interface called TextFormat which will be implemented by all our current and future Text Formats. This will enable us to create certain rules and functionalities that each of out TextFormat must abide by and have.

// TextFormat interface
interface TextFormat {
    fun printTextLength()
}

Now we need to create the concrete Implementations of the TextFormat which are actual formats we want to have in our application

// Concrete TextFormat: Json
class Json(val content: String) : TextFormat {

    // relevant logic for converting to Json

    override fun printTextLength() {
        println(content.length)
    }
}

// Concrete TextFormat: Xml
class Xml(val content: String) : TextFormat {

    // relevant logic for converting to Xml

    override fun printTextLength() {
        println(content.length)
    }
}

Let’s create a class called ConverterFactory which will handle the conversion of JSON, XML and all forthcoming Text Formats. By creating a separate class for the construction of Json and Xml objects we are de-coupling their creation from their implementation. This not only adds flexibility to the code but also emphasizes the Single Responsibility principle of SOLID

// Factory class
class ConverterFactory {
    private var availableJson: Json? = null
    private var availableXml: Xml? = null
    fun convertText(type: String, content: String): TextFormat {
        when (type) {
            "Json" -> {
                return if (availableJson != null && availableJson!!.content == content) {
                    availableJson!!
                } else {
                    availableJson = Json(content)
                    availableJson!!
                }
            }

            "Xml" -> {
                return if (availableXml != null && availableXml!!.content == content) {
                    availableXml!!
                } else {
                    availableXml = Xml(content)
                    availableXml!!
                }
            }

            else -> throw IllegalArgumentException("Invalid content")
        }
    }
}
// Driver Code
fun main() {
    val factory = ConverterFactory()

    val jsonObject = factory.convertText("Json", "Hello, World!")
    jsonObject.printTextLength()

    val xmlObject = factory.convertText("Xml", "Hello, World!")
    xmlObject.printTextLength()

    val sameJson = factory.convertText("Json", "Hello, World!")
    println(sameJson.hashCode() == jsonObject.hashCode())
}

// Output
13
13
true

Factory Method design pattern is further extended to Static Factory Method and Abstract Factory Method.

Static Factory Method varies little in implementation and is a perfect example of how we can Lazily initialize our objects. An example of the Static Factory Method is how we implemented a Singleton Design Pattern.

Creating the Abstract Factory Method design pattern

First of all why do we need it?

Consider we need to scale up our application which converts strings to JSON, XML, etc. even more in such a way that other applications can use our functionality in their applications and even create variants of the already provided Json and XML with new features as per their need. That would be cool right? In short to create multiple and custom variants of the already existing Json and XML formats

To begin we need to abstract and decouple the creation of Json and Xml objects which were earlier housed inside the ConverterFactory. Let’s create an Interface called AbstractConverterFactory

interface AbstractConverterFactory {
    fun convertToJson(): Json
    fun convertToXml(): Xml
}

Suppose an application called ‘Textify’ needs to implement our functionality and create its own Formats called TextifyJson and TextifyXml respectively.

What Textify needs to do is just extend our AbstractConverterFactory and implement the methods rather than providing the Json and Xml which come for our functionality provide their own variants of the same by doing

class TextifyJson(content: String) : Json(content) {
    // add features to already implemented Json format
}

class TextifyXml(content: String) : Xml(content) {
   // add features to already implemented Xml format
}

As we already abstracted the creation of Json and Xml through AbstractConverterFactory, any class from Textify can implement it and create TextifyJson and TextifyXml

class TextifyConverterFactory: AbstractConverterFactory{

    override fun convertToJson(content: String): Json {
        return TextifyJson(content)
    }

    override fun convertToXml(content: String): Xml {
        return TextifyXml(content)
    }

}

// Driver Code
fun main() {
    val factory = TextifyConverterFactory()

    val jsonObject = factory.convertToJson("Hello, World!")

    val xmlObject = factory.convertToXml("Hello, World!")
}

One of the best features of the Abstract Factory Method is that it follows the Open/Closed Principle of SOLID but a disadvantage is that it requires a lot of abstraction in the code which can make it hard to read.

Creating The Builder Design Pattern

Builder design pattern is used to create objects which need a lot of parameters for their initialization. It decouples the assigning of arguments from the creation of objects hence simplifying the creation of complex objects. These ‘complex objects’ and ‘a lot of parameters’ can be a subjective thing but the overall goal is to make sure that when a developer creates an object of a class it should be intuitive and readable.

To be honest, we don’t use Builder Pattern much in Kotlin due to the presence of Data Classes and named arguments which make the creation of Complex objects easy but still learning never gets wasted.

Let’s see the Builder pattern in practice: Suppose we are creating a Library that will create an animated and interactive UI component equivalent to a Card. Let’s call it AnimatedCard.

This example is for explanation only.

What AnimatedCard needs to create it’s instance?

  1. Title — required

  2. Subtitle — not required

  3. Body — not required

  4. Image url — not required

  5. Animation-enabled flag — not required

data class AnimatedCard(
    val title: String,
    val subtitle: String?,
    val body: String?,
    val imageUrl: List<String>?,
    val animationEnabled: Boolean
)

Now if we try creating it’s object than we would need to pass a lot of values to the constructor.

val card = AnimatedCard(
    "Animated Card",
    "This card supports animation and images",
    null,
    null,
    true
)

Let’s create an inner data class named Builder within the AnimatedCard class. This inner class will be responsible for constructing AnimatedCard objects.

The Builder class includes private variables for each attribute of the AnimatedCard class and are mutable and have default values.

data class AnimatedCard(
    val title: String,
    val subtitle: String?,
    val body: String?,
    val imageUrl: List<String>?,
    val animationEnabled: Boolean,

) {
    // Builder class to construct an AnimatedCard
    data class Builder(
        private var title: String  = "",
        private var subtitle: String? = null,
        private var body: String? = null,
        private var imageUrl: List<String>? =  emptyList(),
        private var animationEnabled: Boolean = true,

    ) {

         // Builder methods will be added here

    }

}

Inside the AnimatedCard’s Builder class, we add builder methods for each attribute. These methods allow us to set the values of the AnimatedCard attributes one by one.

We use the apply extension function to modify the builder’s state and return this, allowing method chaining.

What’s left is to add a build method to the AnimatedCard.Builder class, which constructs and returns AnimatedCard object with the attributes set through the builder methods. The build method creates a new AnimatedCard instance using the values stored in the builder.

data class AnimatedCard(
    val title: String,
    val subtitle: String?,
    val body: String?,
    val imageUrl: List<String>?,
    val animationEnabled: Boolean,

) {

    // Builder class to construct an AnimatedCard

    data class Builder(
        private var title: String  = "",
        private var subtitle: String? = null,
        private var body: String? = null,
        private var imageUrl: List<String>? =  emptyList(),
        private var animationEnabled: Boolean = true,

    ) {

        fun title(title: String) = apply { this.title = title }

        fun subtitle(subtitle: String) = apply { this.subtitle =subtitle}

        fun body(body: String) = apply { this.body = body }

        fun imageUrl(imageUrl: List<String>) = apply { this.imageUrl = imageUrl }

        fun animationEnabled(animationEnabled: Boolean) = apply { this.animationEnabled = animationEnabled }

        fun build() = AnimatedCard(title, subtitle, body, imageUrl,animationEnabled)

    }

}
// Driver Code
fun main() {

    val card = AnimatedCard.Builder()
        .title("Raghav")
        .subtitle("Android Developer")
        .body("website: https://avidraghav.dev/")
        .imageUrl(listOf())
        .animationEnabled(true)
        .build()

}

This was the Builder pattern, again this pattern is not used much in Kotlin due to the advent of Data classes and named arguments in Kotlin.

Creating the Prototype Design Pattern

Prototype Design Pattern is used to create similar objects which vary slightly. It’s like cloning the already existing object so that we don’t have to go through the whole process of creating another object from scratch. This pattern reduces code duplicity to a great extent.

Consider a class Customer

class Customer(
    val id: String,
    val name: String,
    val age: Int,
    val hasSubscription: Boolean,
    val maritalStatus: Boolean
)

An object of Customer can be created by

val customer = Customer(
    "1-a",
    "John",
    30,
    true,
    true
)

Now if we have to create another customer given that his/her name and id are different and the rest of the properties are the same then rather than calling the constructor again and passing all the values we can implement the Prototype Design Pattern. Let’s see how

We will create a method called ‘clone’ (call it whatever you like) which will return an updated instance of the class in which it is defined.

class Customer(
    val id: String,
    val name: String,
    val age: Int,
    val hasSubscription: Boolean,
    val maritalStatus: Boolean

) {
    fun clone(
        id: String? = null,
        name: String? = null,
        age: Int? = null,
        hasSubscription: Boolean? = null,
        maritalStatus: Boolean? = null
    ): Customer {
        return Customer(
            id ?: this.id,
            name ?: this.name,
            age ?: this.age, 
            hasSubscription ?: this.hasSubscription,
            maritalStatus ?: this.maritalStatus

        )
    }
}

Driver code

fun main() {

    // object of Customer class can be created by
    val customer = Customer(
        "1-a",
        "John",
        30,
        true,
        true,
    )

    println(customer.id)

    // using our clone method
    val newCustomer = customer.clone("2-b")
    println(newCustomer.id)

    println(newCustomer.name)

}

// Output
1-a
2-b
John

We could a Marker Interface to classify what classes in the project can be cloned if we are going to this pattern at many places in the project.

Kotlin’s Data Classes’ copy method is a perfect example of an implementation of Prototype Design Pattern. If Customer had been a Data class then it could have been cloned just by using the copy method

val newCustomer = customer.copy(id = "2-b")

Modern Programming Languages such as Kotlin are so well written that more often than not a developer won’t be implementing most of the creational design patterns by himself because they are already provided via keywords such as object, Data Classes etc. but still learning them gives a whole new perspective to developer for writing readable, efficient and scalable code.

If you have read till here then I hope you have liked it and if this is the case indeed, do show some appreciation by sharing it those who could be benefited from the same. Thanks for your time :)

References:
https://refactoring.guru/design-patterns
https://www.packtpub.com/product/kotlin-design-patterns-and-best-practices-second-edition/9781801815727