Creational design patterns in Object-Oriented Programming (OOP) deal with the mechanisms of object creation, aiming to create objects in a manner suitable to the situation. This approach offers flexibility, allowing for more efficient and scalable designs.
In this guide, we will cover the following creational design patterns:
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
Each pattern will include an easy-to-understand Kotlin example, which you can run using the Kotlin Playground.
Singleton Pattern
The Singleton pattern ensures that a class has only one instance and provides a global access point to that instance.
Example
object DatabaseConnection {
init {
println("Database Connection created!")
}
fun connect() {
println("Connected to the database.")
}
}
fun main() {
val connection1 = DatabaseConnection
connection1.connect()
val connection2 = DatabaseConnection
connection2.connect()
println("Are connection1 and connection2 the same instance? ${connection1 === connection2}")
}
Factory Method Pattern
The Factory Method pattern allows subclasses to decide which class to instantiate. This pattern defines a method that should be used for object creation, letting subclasses modify the type of objects created.
Example
abstract class Transport {
abstract fun deliver(): String
}
class Truck : Transport() {
override fun deliver(): String {
return "Deliver by Truck"
}
}
class Ship : Transport() {
override fun deliver(): String {
return "Deliver by Ship"
}
}
abstract class Logistics {
abstract fun createTransport(): Transport
fun planDelivery(): String {
val transport = createTransport()
return transport.deliver()
}
}
class RoadLogistics : Logistics() {
override fun createTransport(): Transport {
return Truck()
}
}
class SeaLogistics : Logistics() {
override fun createTransport(): Transport {
return Ship()
}
}
fun main() {
val roadLogistics = RoadLogistics()
println(roadLogistics.planDelivery())
val seaLogistics = SeaLogistics()
println(seaLogistics.planDelivery())
}
Abstract Factory Pattern
The Abstract Factory pattern is used to create families of related or dependent objects. It provides an interface for creating objects, without specifying their exact classes.
Example
interface GUIFactory {
fun createButton(): Button
fun createCheckbox(): Checkbox
}
interface Button {
fun click(): String
}
interface Checkbox {
fun check(): String
}
class WindowsButton : Button {
override fun click(): String = "Windows Button clicked"
}
class MacButton : Button {
override fun click(): String = "Mac Button clicked"
}
class WindowsCheckbox : Checkbox {
override fun check(): String = "Windows Checkbox checked"
}
class MacCheckbox : Checkbox {
override fun check(): String = "Mac Checkbox checked"
}
class WindowsFactory : GUIFactory {
override fun createButton(): Button = WindowsButton()
override fun createCheckbox(): Checkbox = WindowsCheckbox()
}
class MacFactory : GUIFactory {
override fun createButton(): Button = MacButton()
override fun createCheckbox(): Checkbox = MacCheckbox()
}
fun main() {
val windowsFactory: GUIFactory = WindowsFactory()
val macFactory: GUIFactory = MacFactory()
val windowsButton = windowsFactory.createButton()
println(windowsButton.click())
val macCheckbox = macFactory.createCheckbox()
println(macCheckbox.check())
}
Builder Pattern
The Builder pattern allows for step-by-step object construction, especially useful for creating complex objects without overloading constructors.
Example
class House private constructor(
val windows: Int,
val doors: Int,
val hasGarage: Boolean,
val hasSwimmingPool: Boolean
) {
data class Builder(
var windows: Int = 0,
var doors: Int = 0,
var hasGarage: Boolean = false,
var hasSwimmingPool: Boolean = false
) {
fun windows(windows: Int) = apply { this.windows = windows }
fun doors(doors: Int) = apply { this.doors = doors }
fun garage(hasGarage: Boolean) = apply { this.hasGarage = hasGarage }
fun swimmingPool(hasSwimmingPool: Boolean) = apply { this.hasSwimmingPool = hasSwimmingPool }
fun build() = House(windows, doors, hasGarage, hasSwimmingPool)
}
override fun toString(): String {
return "House(windows=$windows, doors=$doors, hasGarage=$hasGarage, hasSwimmingPool=$hasSwimmingPool)"
}
}
fun main() {
val house = House.Builder()
.windows(10)
.doors(5)
.garage(true)
.swimmingPool(true)
.build()
println(house)
}
Prototype Pattern
The Prototype pattern is used to create duplicates (clones) of an object. This pattern is useful when creating an object from scratch is costly, and a similar object is available that can be copied.
Example
interface Prototype<T> {
fun clone(): T
}
data class Sheep(val name: String, val weight: Int) : Prototype<Sheep> {
override fun clone(): Sheep {
return Sheep(name, weight)
}
}
fun main() {
val originalSheep = Sheep("Dolly", 80)
val clonedSheep = originalSheep.clone()
println("Original: $originalSheep")
println("Cloned: $clonedSheep")
}
Conclusion
In this guide, we covered five important Creational Design Patterns:
- Singleton: Ensures a class has only one instance.
- Factory Method: Allows subclasses to decide which class to instantiate.
- Abstract Factory: Creates families of related or dependent objects.
- Builder: Simplifies the construction of complex objects.
- Prototype: Creates duplicates of an object.
By understanding these patterns, you can gain more control over how your objects are created and make your code more flexible and maintainable. Try experimenting with the provided examples in a Kotlin playground to see how these patterns work in practice.
Comments
Post a Comment