SRP (Single Responsibility Principle)
- SRP is about making a single method/function do specific task or a class/structure do a specific set of functionalities that defines that Type (i.e. class or struct)
Note: I am using Swift, the flagship (now open-source) language of Apple Inc. to demonstrate the SRP principle.
SRP IN METHOD/FUNCTION
Example: Taking the example of a Coffee and Tea making machine to explain SRP in method/function
Tea-maker
struct TeaMaker
{
internal func prepareTea()
{
// Prepare tea here
}
internal func addMilk()
{
// Add milk to tea
}
}
Coffee-maker
struct CoffeeMaker
{
internal func prepareCoffee()
{
// Prepare Coffee here
}
internal func addMilk()
{
// Add milk to Coffee
}
}
-
It’s always a good idea to have a single task assigned to a single methods/functions.
-
Consider Tea which can be with milk or without it, now if we have only one method called prepareTea() with addMilk() method functionality inside it, then the tea will always be with milk.
struct TeaMaker
{
// Wrong way to implement a method which does more than one Task
internal func prepareTea()
{
// Prepare tea here
// Add milk to tea
}
}
-
Some people may ague over the point that using a condition factor we can control the addition of milk inside the prepareTea() method but then that leads to violation of another Software designing principle named DRY (Don’t Repeat Yourself).
-
Consider there is only one struct/class called HotDrinkMaker, now this can make both tea as well as coffee. Now if we write the addMilk() method inside prepareTea() and prepareCoffee() method then the addMilk functionality is repeated twice.
struct HotDrinkMaker
{
// Repetition of add milk functionality
internal func prepareTea()
{
// Prepare tea here
// Add milk to tea
}
// Repetition of add milk functionality
internal func prepareCoffee()
{
// Prepare coffee here
// Add milk to coffee
}
}
- Implementing SRP to a method/function prevents violation of DRY.
struct HotDrinkMaker
{
internal func prepareTea(isBlack: Bool)
{
// Prepare tea here
if !isBlack
{
addMilk()
}
}
internal func prepareCoffee(isBlack: Bool)
{
// Prepare coffee here
if !isBlack
{
addMilk()
}
}
internal func addMilk()
{
// Add milk to Coffee
}
}
- If we try to optimise this HotDrinkMaker struct further then I would prefer the below code.
enum Drinks
{
case Tea
case Coffee
}
enum Milk
{
case Yes
case No
}
struct HotDrinkMaker
{
internal func prepareDrink(drink: Drinks, milk: Milk)
{
// Prepare drink here
print("Drink prepared")
if milk == Milk.Yes
{
addMilk()
}
}
internal func addMilk()
{
// Add milk to Coffee
print("Milk added")
}
}
SRP IN CLASS/STRUCT
-
Classes/Structs must follow 2 things.
- SRP
- Cohesiveness
- SRP in class/struct deals with grouping of all the functionalities that defines that particular type.
- Cohesiveness is about having all the methods/functions whose functionalities are apt to the name of that class/struct.
Example: Taking the example of a Car to explain SRP in class/struct
struct Car
{
internal func controlSteering()
{
// controls the steering of a car
}
internal func controlAcceleration()
{
// controls the acceleration of a car
}
internla func controlBrakes()
{
// controls the brakes of a car
}
}
-
All the methods above controlSteering(), controlAcceleration() and controlBrakes() does a specific Task and all the methods are apt to the name of the struct that its contained within.
-
Consider I need a method called fillOil() but before that we need to decide few other factors.
- Type of Oil (Petrol, Diesel, Water, Air… who knows about the future !!)
- Categories of Oil (Power, Swift, Energy… etc etc)
- So all these are specific to Oil not to Car, so it can’t be a part of it… Thats what SRP is about in class/struct.
- The definition of methods within the Car struct aptly justifies the Type Car here, thats Cohesiveness.
enum Oils
{
case Petrol
case Diesel
}
enum OilTypes
{
case Power
case Swift
case Energy
}
struct Oil
{
private var oil: Oils?
private var oilType: OilTypes?
internal init(oil: Oils,oilType: OilTypes)
{
self.oil = oil
self.oilType = oilType
}
internal var getOil: Oils?
{
get
{
return self.oil
}
}
internal var getOilType: OilTypes?
{
get
{
return self.oilType
}
}
}
struct Car
{
// use of composition principle to include the type (Oil) within a Type (Car) which is a part of it but not fits into its own structure(ie not fits in Car's struct)
private var oil: Oils?
internal func controlSteering()
{
// controls the steering of a car
}
internal func controlAcceleration()
{
// controls the acceleration of a car
}
internal func controlBrakes()
{
// controls the brakes of a car
}
internal fun fillOil()
{
// initialise the oil instance here according to the Car
}
}