SOLID principles are the basic essential thing to know for every developer before he/she starts coding in any IDE.
Here is the full form
S - Single Responsibility Principle
- While developing we should always try to achieve high level cohesion.
Ex: Lets say, we have a class Square and there are four methods which is as follows
class Rectangle {
func caluclateArea() { ... }
func caluclatePerimeter() { ... }
func draw() { ... }
func rotate() { ... }
}
If we observe the above class, we can say that 'caluclateArea' and 'caluclatePerimeter' are related and 'draw' and 'rotate' are related. But 'caluclateArea' and 'draw' are not related to anything. Therefore we can say the level of Cohesion is low.
So what can we do here is, we can splint the code as follows.
class Rectangle {
func caluclateArea() { ... }
func caluclatePerimeter() { ... }
}
class RectangleUI {
func draw() { ... }
func rotate() { ... }
}
If we observe above, I have divided the code into Rectangle and RectangleUI.
RectangleUI handle the responsibility of UI update and Rectangle handles the responsibility of calculation. Now we can say that the level of Cohesion is high.
- While developing the code we should always try to do loose coupling.
Definitions:
1. Cohesion - Cohesion is the degree to which the various parts of the software components are related.
2. Coupling - Coupling is defined as the level of inter dependency between various software components.
Notes:
Software always changes with the time. And time is money. If any software component holds the single responsibility principle, changes can made easy so always consider the below points before starting developing anything.
1. The changes required for the software component should have only one reason to change / modify.
2. Every Software component should handle one and only responsibility for the same reason above.
O - Open Close Principle
- Software component should be closed for modification, but open for extension.
The meaning of closed for modification, but open for extension is as follows.
1. New features adding to the software component, shouldn't have to modify the existing functionality.(Which we can say as "Closed for modification").
2. A software component should be extendable to add a new feature or to add a new behaviour to it.
Ex:
class Flat {
let ownerName: String
let flatNumber: Int
init(ownerName: String, flatNumber: Int) {
self. ownerName = ownerName
self.flatNumber = flatNumber
}
}
class Apartment {
var flats: [Flat]
init(flats: [Flat]) {
self.flats = flats
}
func addFlat(flat: Flat)) {
flats.append(flat)
}
}
Usage as follows:
let app = Apartment(flats: [])
app.append(flat: <someValue>)
Now If I introduce new type of Flats, lets say NewFlats. The above code will not work.
protocol ResidentialFlats {}
class Flat: ResidentialFlats {
let ownerName: String
let flatNumber: Int
init(ownerName: String, flatNumber: Int) {
self. ownerName = ownerName
self.flatNumber = flatNumber
}
}
class Apartment {
var flats: [ResidentialFlats]
init(flats: [ResidentialFlats]) {
self.flats = flats
}
func addFlat(flat: ResidentialFlats)) {
flats.append(flat)
}
}
Usage as follows:
let app = Apartment(flats: [])
app.append(flat: <someValue type 'Flat'>)
Now If I introduce new type of Flats
class NewFlat: ResidentialFlats {
let ownerName: String
let flatNumber: Int
let isCornerFlat: Bool
}
Usage as follows:
let app = Apartment(flats: [])
app.append(flat: <someValue type 'NewFlat'>)
The above code still works without any issue. We can say that now 'Apartment' class is closed for modification and open for extension.
L - Liskov Substitution Principle
class Rectangle {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
func area() -> Int{
return width * height
}
}
class Square: Rectangle {
ovveride var width: Int {
didSet {
super.height = width
}
}
ovveride var height: Int {
didSet {
super. width = height
}
}
}
let square = Square(width: 10, height: 10)
let rectangle: Rectangle = square
rectangle.height = 7
rectangle.widht = 5
rectangle.area() // This value will be 25 instead 35
What is happening here is, we are creating Rectangle attaching the reference of the Square, which is Polymorphism. So the actual reference will be pointed to the Square, therefore when we call the area(), the value we get will be the result of Square instead of Rectangle.
So according to the LSP, even though we change the subclass type, it should not effect the program. To achieve this, we have to modify the above code as follows.
protocol Shape {
func area() -> Int
}
class Rectangle: Shape {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
func area() -> Int{
return width * height
}
}
class Square: Shape {
var side: Int
init(side: Int) {
self.side = side
}
func area() -> Int {
return side * side
}
}
let square: Shape = Square(side: 10)
//let rectangle: Rectangle = square// This will throw compile time error, because Rectangle and Square are 2 different types.
So we can use it as
let rectangle: Shape = Rectangle(width: 10, height: 12)
print(rectangle.area())
I - Interface Segregation Principle
- The name it self suggest the interface segregation. No client should be forced to use unwanted methods.
Example:
protocol XeroxMachine {
func print()
func scan()
func fax()
}
class MultiFunctionMachine: XeroxMachine {
func print() { ... }
func scan() { ... }
func fax() { ... }
}
class PrintAndScanMachine: XeroxMachine {
func print() { ... }
func scan() { ... }
func fax() {// Empty}
}
class FaxMachine: XeroxMachine {
func print() { // Empty }
func scan() { // Empty }
func fax() { ... }
}
If we observe the above example, FaxMachine class have the empty methods scan and print because we are using XeroxMachine protocol. Similarly different classes uses different mandatory methods that are required and we have some empty functions. If any other classes call this empty methods, it doesn't do anything.
According to the ISP, we shouldn't have these kind of empty methods, therefore I am going to change the above code as follows.
protocol PrintScanProtocol {
func print()
func scan()
}
protocol FaxProtocol {
func fax()
}
class MultiFunctionMachine: PrintScanProtocol, FaxProtocol {
func print() { ... }
func scan() { ... }
func fax() { ... }
}
class PrintAndScanMachine: PrintScanProtocol {
func print() { ... }
func scan() { ... }
}
class FaxMachine: FaxProtocol {
func fax() { ... }
}
Identification of Violations:
1. Fat Protocols/ Interface -> Follow SRP, every component should have one and only one responsibility.
2. Low cohesion -> Again Follow SRP, try to achieve high cohesion.
3. Empty methods -> Again SRP, loose coupling.
D - Dependency Inversion Principle
var idvar name
func getProducts() -> [Products] {
// Returns the products having in the catalog
}
}
var products: [Products]?var productCatalog: ProductCatalog?func getProducts() {
productCatalog = ProductCatalog()products = productCatalog.getProducts()
}
var id: String { get set }var name: String { get set }
func getProducts() -> [ProductProtocol]
}
var id: Stringvar name: String
func getProducts() -> [ProductProtocol] {
// Returns the products having in the catalog
}
}
var products: [ProductProtocol]?var productCatalog: ProductCatalogProtocol?
init(....) {
....
}
func getProducts() {
products = productCatalog.getProducts()
}
1. Dependency Injection2. Inversion of control
No comments:
Post a Comment