Design Patterns
Design Patterns
Design Patterns
Creational
This pattern gives us a heads-up on how we create the objects on demand. They
provide various object creation mechanisms, which increase flexibility and reuse of
existing code.
Structural
This pattern gives us an idea on how various objects and components in our
application relates to each other. This also explains how to assemble objects and
classes into larger structures while keeping these structures flexible and efficient. This
can also help you achieve some good objectives like separation-of-concerns,
scalability, testability...etc.
Behavioural Pattern
This pattern gives more emphasis on how an object function inside your code. They
are more concerned with algorithms and the assignment of responsibilities between
objects.
Creational Patterns
Singleton
This is a very simple/common design pattern. So, lets assume that you have few
components in your application that are trying to access some external
environments/utility like "Network Communication Instance". Instantiating such an
instance on demand is quite an overhead. So, we basically need only "One" instance
of such services. This single instance idea here is pretty memory efficient. Because we
don't need an additional space in the memory heap. In other words this design
pattern lets you ensure that a class has only one instance, while providing a global
access point to this instance.
Kotlin gives you out of the box support for the singleton implementation using the
keyword object
refer to Singleton.kt
object NetworkDriver { init {
println("Initializing: $this")
}
Factory
As the name sounds, Factory is something that produces a particular type of product.
Taking this in the kotlin perspective, You can think of factory as a class that can
instantiate objects according to the needs of the subclass. In other words Factory
method provides an interface of creating objects in the super class but allows the
subclass to alter the type of objects that will be created.
refer to Factory.kt
For the sake of explanation, let's take the case of currencies. Ideally, we need to
abstract the country from currency. "Abstract"? yep, you can you interface for this
purpose. But as this repos is completely kotlin based we use a special class
named sealed class for restricting our hierarchy.
Now, let's take the nature of Factory. Do we need multiple instances of factory? nope.
For this example, you only need one. Yes, I am referring to using Singleton
Pattern for Factory class.
object CurrencyFactory {
fun currencyForCountry(country: Country): Currency = when (country) {
is Spain -> Currency("EUR") is Greece -> Currency("EUR")
is USA -> Currency("USD") is Canada -> Currency("CAD")
is Poland -> Currency("PLN")
}
}
We are now pretty much done with the Factory implementation. Now, let's get to the
real bread and butter of this methodology.
Abstract Factory
You can think of abstract factory as kind of another level on the factory method that
we had seen above. Let's take the above-mentioned example, Let's say we have a
display/presentation that will be fetching the data from a certain data source. This
Data-source is can be thought of as an interface that will have multiple
implementation. There are two key reasons for doing this:
The presentation layer should have a clear boundary with the dataSource/Factory
layers.
The only thing that the presentation layer needs is to have a contract to the
DataSource-Interface (Dependency Rule).
So, that being said, The presentation layer should get the data according to the
request that they are sending. i.e. It should not care about how the data is
created/delivered. Now here comes the Abstract Factory. In other words, we
can think of abstract factory as a design pattern as is a creational design
pattern that lets you produce families of related objects without specifying
their concrete classes.
Now, lets dive in to the example. We can have the two data sources as Database
and Network.
interface DataSource
Let's start of with a blueprint of the Abstract factory. Fow now I am using Abstract
class from kotlin that gives us the structure/body for factory.
Now here is the core part of the factory method. We need to have a static
function that can give you the factory that you needed to produce the specific
datasource.
companion object {
inline fun <reified T : DataSource> createFactory(): DataSourceFactory = when
(T::class) {
DatabaseDataSource::class -> DatabaseFactory()
NetworkDataSource::class -> NetworkFactory() else -> throw
IllegalArgumentException()
}
}
This is the only implementation that we are going to use in the DataSourceFactory
class for the abstracted-object delivery.
please take a look at the AbstractFactory.kt to see the complete code example.
Builder
Basically a builder is used when we have multiple parameters to initialize. This pattern
will save you a lot of boilerplate code that you will need to write for the constructors.
In other words, this pattern lets you construct complex objects step by step. The
pattern allows you to produce different types and representations of an object using
the same construction code.
class Builder {
private var param1: String? = null private var param2: Int? = null
init {
param1 = builder.getParam1() param2 = builder.getParam2()
}
}
Lazy Initialization
This design patterns is a very common in software development. The whole idea
behind the lazy initialization is that, you will only create data when needed.
Kotlin gives you out of the box support for the lazy initialization.
class Foo {
The instantiation of the object only happens when the item is called for the first time.
There is one more way of doing this when we use var class Foo {
lateinit var item: SomeObject
fun doSomething() {
// your code
// initialization. item = Something()
// your code
}
}
Prototype
This design pattern let's copy an existing object without depending on their classes.
All we need are interfaces. The copied object should provide the full functionality of
the object that it was cloned. Yes, I am referring to the cloneable interface. But Again,
Kotlin gives you out of the box support for implementing this. You can use this class
named data for the same.
you can clone/copy the same even after tweaking the values:
Structural-Patterns
Adapter
If you have experience with Android development, Then you will be pretty familiar
with the name Adapter. Basically an Adapter converts an interface of a class into
another interface that client expects. You can think of it as, You have two different
classes A and B that both have their own interfaces (Kotlin-Interface, methods .etc.)
So, like we said, The adapter pattern converts interface A to interface B. In other
words Adapter pattern gives the class B to adapt and accept the changes that are
posted from A.
Consider that we have a client class that interacts with the Target class. This Target
class has an interface -> that has a method named call(). On the other side, We have a
library named Adaptee that has an interface -> that will have a function named
specificCall().
interface TargetInterface {
fun call(limit: Int): List<Int>
}
interface AdapteeInterface {
fun specificCall(data: List<String>): String
}
interface TargetAdapteeConverter {
fun convertTargetToAdaptee(limit: Int): List<String>
}
Class Implementations
Adapter Implementation
Bridge
Let's now look into a hypothetical example.
In the above example, we have a class named ShapeColor(stores shape and colors).
So, we had derived RedCircle, BlueCircle, RedSquare and BlueSquare. We presently
have only two features, but what if you wanted to add a new feature? - You will need
to think of making use of inheritance. Consider there are a lot more features to be
added in the future down the line. Then this approach is not going to scale. This is
where the Bridge pattern comes into play.
A better solution for the above problem will be to separate each features and
connect them in some way.
interface Shape {
fun renderShape(): String
}
interface Color {
fun renderColorShape(): String
}
Shape Implementation
Bridge
Facade
This is the most commonly used design pattern, We can think of facade as an
interface to a complex functionality. This will also help us in eliminating the need of
designing complex objects and their memory management. In a broader sense,
facade eliminates and simplifies the client side complexity and gives you a simple
higher level API.
fun commit() {
// mock apply
print("status of database $databaseName saved successfully!")
}
}
let's now design a User class that can in that is able to pass in values to our repository.
Now, As the final step we encapsulate all the User-DB interactions inside the
repository:
Decorator
This pattern is also called wrapper pattern. The basic idea about this design pattern is
that you are going to attach a new behaviour to an object. But, critically you cannot
change the existing code. Hmmm sounds like a Hotfix release right?... This can be
because the existing code is in a different library or we want to preserve some
functionality that already exists but the main idea is that we want to add some more
features to it (override).
interface CoffeeMachine {
Now, One of my friend named Brett bought a Coffee machine CF from a nearby store.
Now, This CF is made based on the blueprint CoffeeMachine.
Brett hates plain coffee, He always wanted to make add a Colombian flavour to the
coffee. He came across the CoffeeMachine blueprint. He is exceptionally brilliant, so
he plans to create a new machine out of the CF.
Now, he has the flavor as well as the old coffee machine functionality.
Composite
This design pattern is applicable to problem that includes a tree structure where each
individual component provides its own functionality. So the key idea about choosing
this design pattern is that it works when the core functionality can be represented as
a tree, and we can encapsulate(consider) them as a single entity.
Let's take the example of assembling a PC. Lucia is a friend of mine, She is smart and
decides to assemble a Gaming PC. She now wants to build a system to simulate the
PC such a way that each component can give its own price, and when connecting
them together they give the total sum that she has to pay.
let's say, The base component (base of all PC component) name Equipment can be
considered as
open class Equipment( open val price: Float, val name: String
)
Each Equipment can be logically bounded in its Composite class - you can call them
as composition.
// maps each objects and find its sum. override val price: Int
get() {
She has pretty much all the base classes ready. Now, it is time to select each
component of her PC from the nearby store.
class Monitor : Equipment(700, "Lucia's Fav monitor with 144hz refresh rate") class
class PopToy : Equipment(10, "Lucia's Fav pop toy to keep near her PC")
//Main composite
val computer = Computer()
// Sub-composite
val cabinet = Cabinet() val other = Others()
// Equipments
val processor = Processor() val ram = Ram()
val graphicsCard = GraphicsCard()
val otherComponent = OtherCpuComponents()
// Equipments
val monitor = Monitor() val keyboard = Keyboard() val mouse = Mouse()
val popToy = PopToy()
Proxy
This design pattern comes into play when the program has a dependency with disk
storage. Whenever you have such dependencies, fetching the files each time from
the Disk is not efficient. Instead, we will provide a middle man who can hold the
value temporarily for the course of the runtime(like a proxy). This is very similar to
Facade, but the only difference is that, there will be an additional logic added for
proxy mechanism.
init {
loadFromDisk(filename)
}
}
Proxy
Behavioral-Patterns
Observer
The observer is quite a common patterns, it allows us to notify a set of subscribers
a certain even that have occurred. So, basically at it core this design pattern
describes a subscription mechanism. So, in real life, you can think of it when you
subscribe for a magazine/youtube channel. Everytime something is published to
that service, the user gets a notification. So that is the same idea here. Observer
pattern allows the object to be an observer of a service. This allows us to notify
multiple objects simultaneously, and in a broader view, this pattern showcases
one-many relationship.
This is the general diagram of the observer pattern. Like the other patterns, Observer
also work on top of interfaces. The event-manager has two functionalities. Store the
references of the subscribers, and notify the subscribers. Event generator will make
the event manager to push the events to the subscribers.
Let's consider a typical push notification service for the code example: Basic push
event
Event manager
init {
for (operation in operations) { listeners[operation] = ArrayList()
}
}
// Subscription.
fun subscribe(eventType: String, listener: EventListener) { val users =
listeners[eventType]
users?.add(listener)
}
// removal of subscription.
fun unsubscribe(eventType: String, listener: EventListener) { val users =
listeners[eventType]
users?.remove(listener)
}
Event generator
class EventGenerator {
var events = EventManager("a", "b") private set
String) {
pushEvent = PushEvent(filePath)
events.notify("a", pushEvent)
}
Chain-of-Responsibility
Basically this pattern implies that there are a chain of so-called Handlers (Something
that can handle requests in some way). This is very much useful when you want to
solve a problem that has to process certain requests in n-number of steps to produce
the result.
Request header creation is a common thing that developers does, let's take that as a
typical example for projecting the use of this behavioural pattern.
interface HandlerChain {
fun addHeader(inputHeader: String): String
}
// Auth Header
class AuthenticationHeader(val token: String?, var next: HandlerChain? = null) :
HandlerChain {
override fun addHeader(inputHeader: String) =
"$inputHeader\nAuthorization: $token"
.let { next?.addHeader(it) ?: it }
}
// Payload header
class BodyPayloadHeader(val body: String, var next: HandlerChain? = null) :
HandlerChain {
override fun addHeader(inputHeader: String) =
"$inputHeader\n$body"
.let { next?.addHeader(it) ?: it }
}
Creating chain-of-responsibility
Command
Command is a behavioral design pattern that turns a request into a stand-alone
object that contains all information about the request. This transformation lets you
pass requests as a method arguments, delay or queue a request’s execution, and
support undoable operations.
class CommandProcessor {
private val queue = arrayListOf<Command>()
CommandProcessor()
.addToQueue(OrderAddCommand(1L))
.addToQueue(OrderAddCommand(2L))
.addToQueue(OrderCheckoutCommand(2L))
.addToQueue(OrderCheckoutCommand(1L))
.processCommands()
Strategy
Strategy is a behavioral design pattern that lets you define a family of algorithms, put
each of them into a separate class, and make their objects interchangeable.
Now, if you closely take a look at this printer class, you can see that there is a generic
strategy (constructor) with which you can print a given string. Now, Let's formulate
our strategies
State
State is a behavioral design pattern that lets an object alter its behavior when its
internal state changes. It appears as if the object changed its class.
You can think of an ACCESS-TOKEN store in android as a simple example. The store
works in two states, It will deliver an access token in when the login is completed,
else it will return a state Not-Authorized-Exception.
If you take a closer look at this scenario, The Characteristics of the Store is actually
controlled by state of the module.
Defining States
class AuthorizationPresenter {
private var state: AuthorizationState = Unauthorized
Visitor
Visitor is a behavioral design pattern that lets you separate algorithms from the
objects on which they operate.
Store interface
interface Store {
fun <R> deliver(visitor: StoreVisitor<R>): R
}
// VanillaIceCream lover
class StoreVisitorA(private val contract: Long) : StoreVisitor<VanillaIceCream>
{ override fun visit(): VanillaIceCream {
return when (contract) {
in 1..5 -> {
VanillaIceCream(Scoop.Small)
}
in 5..10 -> {
VanillaIceCream(Scoop.Medium)
}
else -> {
VanillaIceCream(Scoop.Large)
}
}
}
}
// BlueBerryIceCream lover
class StoreVisitorB(private val contract: Long) : StoreVisitor<BlueBerryIceCream>
{
override fun visit(): BlueBerryIceCream { return when (contract) {
in 1..7 -> {
BlueBerryIceCream(Scoop.Small)
}
in 7..20 -> {
BlueBerryIceCream(Scoop.Medium)
}
else -> {
BlueBerryIceCream(Scoop.Large)
}
}
}
}
// SpanishDelightIceCream lover
class StoreVisitorC(private val contract: Long) :
StoreVisitor<SpanishDelightIceCream> {
override fun visit(): SpanishDelightIceCream {
return when (contract) { in 1..2 -> {
SpanishDelightIceCream(Scoop.Small)
}
in 2..7 -> {
SpanishDelightIceCream(Scoop.Medium)
}
else -> {
SpanishDelightIceCream(Scoop.Large)
}
}
}
}
let's see how the store works for the three customers
val visitorA = StoreVisitorA(5) val visitorB = StoreVisitorB(8) val visitorC =
StoreVisitorC(10)
assert(visitorA.visit().scoopType == Scoop.Small)
assert(visitorB.visit().scoopType == Scoop.Medium)
assert(visitorC.visit().scoopType == Scoop.Large)
Mediator
Mediator is a behavioral design pattern that lets you reduce chaotic dependencies
between objects. The pattern restricts direct communications between the objects
and forces them to collaborate only via a mediator object. In simple terms, you can
think of the mediator pattern as a middle man for facilitating the communication
between objects.
Let's take an example of a chat system, where in a multiple clients communicates to
each other.
class ChatUser(private val mediator: Mediator, private val name: String) { fun
send(msg: String) {
println("$name: Sending message: $msg")
mediator.sendMessage(msg, this)
}
This is how the mediator looks like(Mediator helps in delivering a msg to multiple
Chat users)
class Mediator {
private val users = arrayListOf<ChatUser>()
communication example:
mediator.addUser(shinaz)
.addUser(sethu)
.addUser(nikhil)
nikhil.send("Hi everyone!")
Memento
Memento is a behavioral design pattern that lets you save and restore the previous
state of an object without revealing the details of its implementation.
Text Editor applications are the most common examples of this design pattern, you
might be wondering where exactly in a Text Editor is this DP applicable ->>>>Undo
button<<<<-
There are 3 components for this patterns:
Memento : Storage
Originator : Creates a state, you may think of this as an object factory in some cases.
Decision Maker (Care Taker): Decides to save or restore the states.
Originator:
Decision Maker
class CareTaker {
// Memento Store
private val mementoList = arrayListOf<Memento>()
// Saving a state adds the memento to the store. fun saveState(state: Memento) {
mementoList.add(state)
}
State restoring.
originator.restoreMemento(careTaker.restore(1))
println("Current state is ${originator.state}")
assert(originator.state == "State 1")