Design Patterns in Kotlin: Creational Patterns

Gang of Four’s Creational Patterns describe the way of creating and initializing objects.

1. Abstract Factory

Let’s assume we need to build a car using different parts, such as Tire and Engine. The data types can be defined like below:

interface Tire {}

class TireA : Tire

class TireB : Tire

interface Engine {}

class EngineA : Engine

class EngineB : Engine

class Car(val tire: Tire, val engine: Engine)

However, a certain type of tire can only be used with a certain type of engine. How should we write a method to build a car, especially if new types of tires and engines will be added in the future?

The abstract factory pattern can be used to solve this problem. This pattern provides a factory interface to create a family of products, which are designed to be used together, without specifying the concrete classes.

We can create a group of factories like this:

interface CarPartsFactory {
    fun produceTire(): Tire
    fun produceEngine(): Engine
}

class CarPartsFactoryA : CarPartsFactory {
    override fun produceTire(): Tire = TireA()
    override fun produceEngine(): Engine = EngineA()
}

class CarPartsFactoryB : CarPartsFactory {
    override fun produceTire(): Tire = TireB()
    override fun produceEngine(): Engine = EngineB()
}

Then we can write the buildCar() function like below:

fun buildCar(factory: CarPartsFactory): Car
    = Car(factory.produceTire(), factory.produceEngine())

In the future, if we have new parts like TireC and EngineC, all we need is to create a CarPartsFactoryC to utilize these new types, without changing anything inside the buildCar() function.

2. Builder

Let’s continue with the previous example. What if we can build different types of cars using the same car parts? How are we going to extend the buildCar() function to achieve this?

The builder pattern can be used to solve this problem. It separates the creation of an object from its representation.

We can create car builders like below:

interface CarBuilder {
    fun withTire(tire: Tire): CarBuilder
    fun withEngine(engine: Engine): CarBuilder
    fun build(): Car
}

class CarBuilderA: CarBuilder {
    private lateinit var tire: Tire
    private lateinit var engine: Engine
    
    override fun withTire(tire: Tire): CarBuilder {
        this.tire = tire
        return this
    }

    override fun withEngine(engine: Engine): CarBuilder {
        this.engine = engine
        return this
    }

    override fun build(): Car {
        return CarA(tire, engine)
    }
}

Now we can refactor the buildCar() function to something like below:

fun buildCar(factory: CarPartsFactory, builder: CarBuilder): Car {
    return builder.withTire(factory.produceTire())
                  .withEngine(factory.produceEngine())
                  .build()
}

In the future, if we introduce new car types, we just need to create a new CarBuilder and pass it to the buildCar() function.

3. Factory Method

Now assume we have a new CarManufacturer class, and one of the operations needs to build a new car. However, the logic inside the buildCar() function differs across the manufacturers.

The factory method pattern can be used here, which isolates the logic to create objects in subclasses.

We can define the manufacturers like this:

abstract class CarManufacturer {
    abstract fun buildCar(): Car
}

class CarManufacturerA: CarManufacturer() {
    override fun buildCar(): Car {
        val carPartsFactory: CarPartsFactory = CarPartsFactoryA()
        return CarBuilderA()
            .withTire(carPartsFactory.produceTire())
            .withEngine(carPartsFactory.produceEngine())
            .build()
    }
}

4. Prototype

Now assume we need to make a copy of a cerated Car instance. However, the cost of invoking the buildCar() function to create a new instance is high.

The prototype pattern is provided to create a copy of an instance.

Kotlin provides a copy() function for data classes. Suppose Car is a data class, then we can do something like below:

val anotherCar = car.copy()
val anotherCarWithCustomTire = car.copy(tire = TireA())

5. Singleton

Now assume that for a car manufacturer, it has at most one instance of any car parts factory class.

The singleton pattern is to handle such cases, where a class has only one instance.

Kotlin provides an object keyword to define singletons. So we can change the CarPartsFactoryA class to something like below:

object CarPartsFactoryA : CarPartsFactory {
    override fun produceTire(): Tire = TireA()
    override fun produceEngine(): Engine = EngineA()
}

Note that there is no change other than changing the keyword from class to object. In the client, we can directly use its name to reference it, e.g.:

val car = buildCar(CarPartsFactoryA, someCarBuilderInstance)


See also

comments powered by Disqus