Factory Method Design Pattern in Kotlin

1. Definition

The Factory Method Design Pattern is a creational pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created.

In this tutorial, we will learn how to implement a Factory Method Design Pattern using Kotlin programming language.

2. Problem Statement

Imagine you have a framework that requires the instantiation of various products, but the exact type of product to be created isn't known until runtime. Directly instantiating the class can introduce tight coupling and hinder flexibility.

3. Solution

Instead of directly calling the constructor, delegate the responsibility of object creation to a factory method. This allows for flexibility, as subclasses can override this method to produce different objects.

4. Real-World Use Cases

1. GUI libraries where each OS provides a different implementation of a button or a window.

2. E-commerce platforms offering different membership plans with varied benefits.

5. Implementation Steps

1. Define an interface or abstract class with a factory method.

2. Concrete classes will implement or override this factory method to produce objects.

3. Clients will call the factory method instead of directly using the new keyword.

6. Generic Implementation in Kotlin Programming

// Abstract product
interface Product {
    fun showProductType(): String
}
// Concrete products
class ConcreteProductA : Product {
    override fun showProductType() = "Product Type A"
}
class ConcreteProductB : Product {
    override fun showProductType() = "Product Type B"
}
// Creator class with the factory method
abstract class Creator {
    abstract fun factoryMethod(): Product
}
// Concrete creators that override the factory method
class ConcreteCreatorA : Creator() {
    override fun factoryMethod() = ConcreteProductA()
}
class ConcreteCreatorB : Creator() {
    override fun factoryMethod() = ConcreteProductB()
}
fun main() {
    // Client code
    val creatorA: Creator = ConcreteCreatorA()
    val productA: Product = creatorA.factoryMethod()
    println(productA.showProductType())
    val creatorB: Creator = ConcreteCreatorB()
    val productB: Product = creatorB.factoryMethod()
    println(productB.showProductType())
}

Output:

Product Type A
Product Type B

Explanation:

1. Product is an interface representing the abstract product.

2. ConcreteProductA and ConcreteProductB are concrete implementations of the Product.

3. Creator is an abstract class with an abstract factoryMethod which returns an object of type Product.

4. ConcreteCreatorA and ConcreteCreatorB are subclasses of Creator that override the factoryMethod to produce ConcreteProductA and ConcreteProductB respectively.

5. In the client code (main function), instead of directly instantiating the product, we use the concrete creators' factoryMethod to get the product.

7. Real-World Example

Problem Statement

When developing a system, you might need to connect to multiple databases like MySQL, PostgreSQL, or SQLite. Directly initializing connections can make the code rigid and harder to maintain when new databases need to be added.

Solution

Use the Factory Method pattern to create database connections. By doing this, the system can easily adapt to new database types or changes in database configurations.

Implementation Steps

1. Define an interface for creating a database connection.

2. Let subclasses decide which class to instantiate.

3. The client uses the factory method instead of creating instances directly.

Implementation in Kotlin Programming

// Abstract database connection
interface DatabaseConnection {
    fun connect(): String
}
// Concrete database connections
class MySQLConnection : DatabaseConnection {
    override fun connect() = "Connected to MySQL Database"
}
class PostgreSQLConnection : DatabaseConnection {
    override fun connect() = "Connected to PostgreSQL Database"
}
class SQLiteConnection : DatabaseConnection {
    override fun connect() = "Connected to SQLite Database"
}
// Database connection creator with the factory method
abstract class DatabaseFactory {
    abstract fun createConnection(): DatabaseConnection
}
// Concrete database connection creators
class MySQLFactory : DatabaseFactory() {
    override fun createConnection() = MySQLConnection()
}
class PostgreSQLFactory : DatabaseFactory() {
    override fun createConnection() = PostgreSQLConnection()
}
class SQLiteFactory : DatabaseFactory() {
    override fun createConnection() = SQLiteConnection()
}
fun main() {
    // Client code
    val mysqlFactory: DatabaseFactory = MySQLFactory()
    val mysqlConnection: DatabaseConnection = mysqlFactory.createConnection()
    println(mysqlConnection.connect())
    val postgresFactory: DatabaseFactory = PostgreSQLFactory()
    val postgresConnection: DatabaseConnection = postgresFactory.createConnection()
    println(postgresConnection.connect())
    val sqliteFactory: DatabaseFactory = SQLiteFactory()
    val sqliteConnection: DatabaseConnection = sqliteFactory.createConnection()
    println(sqliteConnection.connect())
}

Output:

Connected to MySQL Database
Connected to PostgreSQL Database
Connected to SQLite Database

Explanation:

1. DatabaseConnection is an interface representing an abstract database connection.

2. MySQLConnection, PostgreSQLConnection, and SQLiteConnection are concrete implementations.

3. DatabaseFactory is an abstract class containing the factory method createConnection.

4. MySQLFactory, PostgreSQLFactory, and SQLiteFactory are concrete implementations of DatabaseFactory that instantiate the correct database connection.

5. In the client code (main function), we utilize the factory to create specific database connections without directly instantiating them.

8. When to use?

The Factory Method pattern is useful when:

1. You're dealing with systems that require adaptability and scalability.

2. Classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate.

3. You want to encapsulate the creation logic and keep it separate from the main logic.

Comments