Software Design Patterns in Kotlin
Creational patterns encapsulate knowledge about concrete classes the system use, hide how instances of these classes are created and separate the system from how the objects are composed and represented.
-
Abstract Factory (usage)
What. Abstract Factory provides an interface for creating families of related/dependent objects without specifying their concrete classes. Abstract Factory abstracts and encapsulates the creation of a suite of products for a given platform/family that the system depends on
How.AbstractFactory
interface provides methods for creating all kinds of products for a given platform/family. Client works only with theAbstractFactory
and theProduct
interfaces. The concrete implementations of theAbstractFactory
interface are singletons
Example. The system depends on aLetter
=Product
and aResume
=Product
instances. The system uses aDocumentCreator
=AbstractFactory
for creating the concreteLetter
andResume
instances from the two families: modern and fancy -
Builder (usage)
What. Builder separates a construction of a complex object from its representation, allowing the same construction step by step process to create various representations
How. Client uses (a) a separateBuilder
object which receives each initialization parameter step by step in a fluent interface or (b) aBuilder
DSL initialization method which configures properties by direct property assignment or function call and returns the resulting constructed complex object at once
Example.Car.Builder
provides a builder DSL (Car.build { ... }
) for building aCar
instance -
Dependency Injection (usage)
What. A class (client) accepts through an interface the objects (services/dependencies) the class requires from an injector instead of creating the objects directly
How. TheInjector
passes theService
object to theClient
class via theService
interface by the inversion of control.Injector
decouples theClient
from creating theService
object directly. TheInjector
creates theService
object and calls theClient
. TheClient
does not know about theInjector
. TheClient
works only with theService
interfaces provided by theInjector
via constructor injection or setter injection
Example. TheClient
requires theConstructorInjectedDependency
and theSetterInjectedDependency
both implementing theService
interface. TheInjector
creates theClient
and sets up its dependentServices
via the constructor injection and the setter injection -
Factory Method (usage)
What. Factory method defines an interface for creating a single object, but let subclasses decide which class to instantiate
How.FactoryMethod
interface provides a method for creating a single object. Client works only with theFactoryMethod
and theProduct
interfaces. The concrete implementations of theFactoryMethod
interface are singletons
Example. The system depends on anArticle
=Product
instances. The system usesArticleCreator
=FactoryMethod
for creating a singleArticle
instance from the two families: modern and fancy -
Prototype (usage)
What. Prototype creates new objects by cloning prototypical instance, boosting performance and keeping memory footprint to a minimum
How. Client works only with theProduct
interface and uses theProduct::clone()
method for creating new instances of theProduct
Example. TheProkaryoteCell
=Product
and theEukaryoteCell
=Product
implement theCellPrototype
interface. In order to create a new instance of the specific cell theCellPrototype::clone()
method is used -
Singleton (usage)
What. Singleton ensures that a class has only one instance and provides a global point of access to the instance. Singleton global variable like dependency is not declared in any component interface, but is tightly coupled with all component implementations. Replace Singleton pattern with Depencency Injection pattern based on Dependency Inversion Principle
How. Make the class constructor private and provide one public static method that always returns the same single instance of the class stored in a private static variable
Example. The expressionobject Singleton
defines a Singleton class and immediately instantiate the class with the single point of access to the instance being theSingleton
class name
Structural patterns provide simple way of implementing relationships between objects.
-
Adapter (usage)
What. Adapter converts the interface of the class without modifying class code into another interface that client expects
How. Client works with the class through an implementation of theAdapter
interface that client expects and delegation to the class methods
Example. Client expects thePhone
=Adapter
interface.XiaomiPhoneAdapter
implements thePhone
interface that client expects and delegates to theXiaomiPhone
class methods -
Bridge (usage)
What. Bridge decouples an abstraction from its implementation (two orthogonal dimensions) allowing the two to vary independently
How. TheAbstraction
and itsImplementation
are defined and extended independently. TheAbstraction
is implemented by delegating to itsImplementation
Example. TheDevice
=Abstraction
has two implementationsPhoneDevice
andTabletDevice
. TheVendor
=Implementation
has two implementationsXiaomiVendor
andNokiaVendor
. TheDevice
implementations acceptVendor
implementation. TheDevice::switchOn()
method delegates to theVendor::support(Device)
method -
Composite (usage)
What. Composite treats individual objects and composition of objects uniformly. Composes objects into tree structures to represent part-whole hierarchies
How. TheLeaf
and theComposite
classes implement theComponent
interface. TheLeaf
class implements the request directly while theComposite
class forwards recursively the request to composite's children
Example. TheExpression
=Component
is a uniformComponent
interface. TheOperand
=Leaf
implements the request directly by returning the operand value. TheOperation
=Composite
implements the request recursively by evaluating its right and left expressions and than applying the actual operation to the results of the left and right expression evaluations -
Decorator (usage)
What. Decorator attaches additional behavior to an individual object dynamically keeping the same interface without affecting the behavior of other objects of the same class
How. TheDecorator
class implements the originalComponent
interface by delegating the request to the original object and adding behavior before/after the original request. Multiple decorators can be stacked on top of each other
Example. TheSimpleCoffee
original class implements theCoffee
=Component
interface. TheCoffeeWithSugar
=Decorator
and theCoffeeWithMilk
=Decorator
decorators implement theCoffee
interface and accept theCoffee
instance to delegate to -
Facade (usage)
What. Facade defines a higher-level simplified interface that makes a system/library easier to use. Facade hides the complexities of a larger system with dependencies and provides a simpler interface to the client
How. Client works only with theFacade
higher-level simplified interface to interact with the larger system
Example. The larger systemDesktop
implements theComputer
=Facade
interface which is used by the client. TheDesktop
manages internally all the complexities involved with the subsystemsCpu
,Ram
andSsd
-
Flyweight (usage)
What. Flyweight uses sharing to support a large number of similar objects efficiently
How. Shares theInvariant
/intrinsic object state in an external data structure. When a new object is created theFlyweightFactory
provides the cachedInvariant
/intrinsic object state and allows theVariant
/extrinsic object state to be set through theFlyweight
interface
Example. TheGlyphCode
=Invariant
represents the intrinsic glyph state (code) that can be cached and shared between glyphs. TheGlyphFlyweight
=Invariant
+Variant
class implements theGlyph
=Flyweight
interface that allows extrinsic glyph state (position) modification. TheGlyphFactory
=FlyweightFactory
caches and shares efficiently theGlyphCode
instances -
Proxy (usage)
What. Proxy provides a placeholder/wrapper for another object for access control, request validation, response caching, etc.
How. The real object and theProxy
implement the sameSubject
interface, so the client cannot distinguish between the real object and theProxy
. TheProxy
uses delegation to the real object
Example. The realAccount
object implements thePayment
=Subject
interface without any balance/amount validations. ThePaymentProxy
=Proxy
implements thePayment
interface with the balance/amount validation. Client uses only theSubject
interface to work with both the realAccount
object or with thePaymentProxy
proxy object
Behavioral patterns provide simple way of implementing interactions between objects.
-
Chain of Responsibility (usage)
What. Chain of Responsibility chains the receiving objects/functions (handlers) and pass the request along the chain until an object/function handles the request completely or partially. Avoids coupling of the request sender to the request receiver allowing more than one receiver a chance to handle the request
How. TheRequest
represents the initial data, then intermediary results and finally the final result. TheRequestHandler
functional interface processes theRequest
partially or completely. TheChainOfResponsibility
composes theRequestHandler
s and implements theRequestHandler
functional interface to process theRequest
Example. ThecashRequestHandlerChain
=ChainOfResponsibility
composes theCashRequestHandler
=RequestHandler
and implements theRequestHandler
interface to process theCashRequest
=Request
. TheCashRequest
has the initial amount to represent with the set of notes. TheCashRequest
is used to handle intermediary results by representing the remaining amount and a set of already processed notes. TheCashRequest
finally represent the final result of the set of notes with the amount remainder if any -
Command (usage)
What. Command encapsulates an action with the request parameters as an function/object and decouples the request for an action from the actual action performer. Allows action/request queueing, logging and undoable operations
How. TheCommand
object stores the request parameters and delegates the request to theReceiver
which performs the action. TheInvoker
object uses theCommand
interface and provides request queueing, logging and undoable operation functionality
Example. ThecookStarter()
,cookMainCourse()
andcookDessert()
functions implement theOrder
=Command
=Receiver
interface and store the request arguments in a closure. TheWaiter
=Invoker
queues theOrder
s and serves theOrder
s by using theOrder
interface -
Interpreter (usage)
What. Given a language (DSL), define a language grammar with an interpreter that use the grammar to interpret the language sentences
How. Define anExpression
class hierarchy for eachTerminalExpression
andNonterminalExpression
symbol in the language. The abstract syntax tree (AST) of the language sentence is aComposite
ofExpression
s and is used to evaluate the sentence. TheTerminalExpression
interprets the expression directly. TheNonterminalExpression
has a container of childrenExpression
s and recursively interprets every childExpression
. Interpreter does not describe how to build an AST. The AST can be build with a parser
Example. TheConstant
=TerminalExpression
, theAdd
=NonterminalExpression
and theMul
=NonterminalExpression
implement theExpression
interface. TheInterpreter
implements theExpression
interpretation algorithm -
Iterator (usage)
What. Iterator provides a way to access the elements of an aggregate object/container of components sequentially without exposing aggregate underlying representation (data structure). Iterator encapsulates the traversal algorithm of a given aggregate object/container of components
How. TheContainerIterator
implements theIterator
interface for theContainer
ofComponent
s to traverse sequentially theComponent
s of theContainer
without exposing the underlying aggregate representation. Client access theComponent
s of theContainer
only through theIterator
interface
Example. TheFruitsIterator
=ContainerIterator
implements theIterator
interface for theFruits
=Container
ofFruit
=Component
-
Mediator (usage)
What. Mediator defines an object that encapsulates how a set of other objects interact. Mediator promotes loose coupling between the colleagues
How. TheColleague
s interact with each other through theMediator
interface without having any references to each other. TheMediator
object has references to everyColleague
and interacts with aColleague
through a commonColleague
interface
Example. TheAirplane
=Colleague
and theHelicopter
=Colleague
implement theAircraft
=Colleague
interface and accept theControlTower
=Mediator
. TheAirplane
and theHelicopter
communicate with each other through theControlTower
interface. TheControlTower
has the references to theAirplane
and theHelicopter
and forwards the messages through theAircraft
interface -
Memento (usage)
What. Memento captures and externalizes an object's internal state without violating encapsulation. Allows the object to be restored (undo/rollback) to the captured state later
How. TheCaretaker
requests theOriginator
to snapshotOriginator
's internal state into theMemento
object before using theOriginator
. To rollback theOriginator
's internal state theCaretaker
returns back theMemento
object to theOriginator
Example. TheCounter
=Originator
object provides aCounterMemento
=Memento
object to store to/retrieve from theCounter
internal state. The incremental count of theCounter
object could be altered by using theCounterMemento
object -
Observer (usage)
What. Observer defines a one-to-many dependency between objects where a state change in one objectSubject
is automatically notified to all subjects' dependentsObservers
How. TheSubject
maintains a list of theObserver
s which implements theObserver
interface. When theSubject
's state changes theSubject
notifies all theObserver
s using theObserver
interface. TheSubject
and theObserver
s are loosely coupled as the state change notification is done through theObserver
interface
Example. TheBidder
=Observer
implements theBidObserver
=Observer
interface. TheAuctioneer
=Subject
implements theAuctioneerSubject
=Subject
interface. When the bid changes theAuctioneer
notifies all theBidders
through theBidObserver
interface -
State (usage)
What. State alters objects' behavior when object's state changes
How. TheState
interface implementation provides the request handling functionality and sets the nextState
.State
implements a state machine where each individual state is a derived class of theState
interface and each transition is defined in state interface method invocation
Example. TheVendingMachine
internal state goes through theShowProducts
,SelectProduct
,DepositMoney
andDeliverProduct
State
s by invoking theVendingMachine::proceed()
method which delegates to the currentState::handleRequest()
method which handles the request and sets the nextVendingMachine
State
-
Strategy (usage)
What. Strategy defines a family of interchangeable at runtime algorithms and provides excellent support for the Open-Closed Principle
How. Define a set of interchangeable algorithms/strategies that implement theStrategy
interface. Based on the conditions at runtime dynamically select the appropriate algorithm/strategy. Client works only with theStrategy
interface
Example. TheTransportCompany
dynamically select the appropriate algorithm/strategygoByBus
orgoByTaxi
based on the size of the tourist group. BothgoByBus
andgoByTaxi
implements theTransport
=Strategy
interface under which the algorithms/strategies are provided to the client -
Template Method (usage)
What. Template Method defines the skeleton of an algorithm (invariant) in one operation deferring some steps (variable) to subclasses (inversion of control) and preserving the overall structure of the algorithm
How. The abstract class defines the abstract algorithmTemplate
with the overall algorithm structure. The invariant algorithm steps are defined as final methods in the abstract class and the variant steps are open for overriding in theTemplate
specializations
Example. TheEmployee
=Template
defines the overall algorithm structure, the invariant and variant algorithm steps. TheDeveloper
and theArchitect
algorithm specialization override the variable algorithm steps -
Visitor (usage)
What. Visitor separates an algorithm from an object structure on which the algorithm operates. Visitor allows to add new operations through theVisitor
s to the existent object structure known asElement
s without modifying the structure
How. TheElement
interface defines a visitor operation for everyVisitor
type based on the abstractVisitor
interface implementing the dynamic dispatch on the abstractVisitor
interface. EveryVisitor
interface defines the overloaded for eachElement
type operation implementing the static dispatch on the concreteElement
type. EveryVisitor
interface implementation has the cross between the concreteElement
and the concreteVisitor
implementing the double dispatch (dynamic on theVisitor
type and static on theElement
type)
Example. TheXiaomiPhone
and theNokiaPhone
implement thePhone
=Element
interface for the dynamic dispatch on the abstractVisitor
type (one operation for eachVisitor
type). TheIntelWiFi
and theBroadcomWiFi
implement theWiFi
=Visitor
interface for the static dispatch on the concretePhone
type to switch on theWiFi
visitor operation. TheSonyCamera
and theSamsungCamera
implement theCamera
=Visitor
interface for the static dispatch on the concretePhone
type to take phones with theCamera
visitor operation. So the two visitor operations (switch on WiFi and take photo) are implemented on thePhone
Element
structure
- KISS - Keep It Simple, Stupid. Do the simplest thing that could possibly work. Avoid unnecessary complexity. Maintain balance between code simplicity and system flexibility, extensibility
- YAGNI - You Aren't Gonna Need It. Do not write code that is not necessary at the moment, but might be necessary in the future. Do the simplest thing that could possibly work
- DRY - Don't Repeat Yourself. Avoid duplication. Every piece of knowledge must have a single, unambiguous, authoritative representation within the system. The modification of any single element of the system does not require a change in other logically unrelated elements
- Information Hiding. One piece of code that calls another piece of code should not know internals about that other piece of code. This make it possible to change internal parts of the called piece of code without being forced to change the calling piece of code accordingly. Expose as little as possible of the internal implementation details of a module to promote Loose Coupling between modules. Provide a stable interface to module functionality that will protect the clients of the module from changes in module implementation. A module implements the Information Hiding principle by applying the encapsulation technique
- High Cohesion. High Cohesion is a degree to which the components inside a module belongs together. A module has High Cohesion when the module responsibility is clearly defined and the module has as few dependencies as possible. Single Responsibility Principle fosters High Cohesion
- Loose Coupling. Each module in the system has as little knowledge as possible about other modules in the system. Use interfaces to implement Loose Coupling between modules. High Cohesion fosters Loose Coupling
- Top-down approach. Decomposition a system into the compositional subsystems. An overview of the system is formulated specifying but not detailing any first-level subsystems. Each subsystem is then refined in yet greater detail, until the entire specification is reduced to base elements. Emphasize on complete understanding of the system. No coding can begin until a sufficient level of detail has been reached in design phase
- Bottom-up approach. Composition of basic elements together into a more complex system. The individual base elements of a system are first specified in great detail. These elements are then linked together to form larger subsystems, until a complete top-level system is formed. Emphasized coding and early testing, which can begin as soon as the first module has been specified. There is a risk of how the modules can be linked together to form a top-level system
- RAII - Resource Acquisition Is Initialization. Smart Pointer: constructor acquires, destructor releases. Smart Pointer is scope-based resource management. When a resource gets out of scope via normal execution or thrown exception the resource is deallocated automatically by the Smart Pointer destructor. RAII only works for resources acquired and released by stack-allocated objects where there is well-defined static object lifetime
- OOP - Object-Oriented Programming. OOP is like biological cells: messaging, state hiding, late binding. Object has well encapsulated structure (properties) and provides behavior (methods) through well defined interface. Abstraction, responsibilities and modularization are the keys to master complexity
- SRP - Single Responsibility Principle. Software unit should have only one single and well defined responsibility, only one reason to change. High Cohesion fosters SRP
- OCP - Open-Closed Principle. Software unit should be open for extension (inheritance, Strategy or Decorator design patterns), but closed for modification (interface with multiple polymorphic implementations)
- LSP - Liskov Substitution Principle. Hierarchy is used to build specialized types from a more general type. Polymorphism means that one single interface provides access to objects of different types. Subtype must be completely substitutable for its supertype. Preconditions cannot be strenthened in a subtype. Postconditions cannot be weakened in a subtype. Supertype invariants must be preserved in a subtype
- ISP - Interface Segregation Principle. Segregate one broad single interface into a set of smaller and highly cohesive interfaces
- DIP - Dependency Inversion Principle. Avoid tight coupling between modules with the mediation of an abstraction (interface) layer. Each module should depend on an abstraction (interface), not other modules directly. The abstraction (interface) provides the behavior needed by the module through possibly multiple implemenattions. Common features should be consolidated in a shared abstractions exposed through interfaces. Depencency Inversion Principel fosters testability of components
- FCoI - Favor Composition + Delegation over Inheritance. Composition is black box reuse through an interface and promotes loose coupling. Inheritance is white box reuse through public/protected members
- ADP - Acyclic Dependency Principle. Circular dependencies should be avoided. Dependency Inversion Principle or creation of a new package with common components breaks the circular dependencies
- LoD - Law of Demeter. The Principle of Least Knowledge/Dependencies - don't talk to strangers, only talk to your immediage neighbors. LoD fosters Loose Coupling and Information Hiding