iOS – Is SOLID Valuable Design Principles Applicable For Swift ?

Hello Readers, CoolMonkTechie heartily welcomes you in this article.

In this article, We will learn about most popular design principles SOLID in Swift. We will see that how SOLID is applicable for Swift. Now a days , a Maintainable and Reusable component is just a dream. Maybe not. SOLID principles, may be the way.

A famous quote about learning is :

Change is the end result of all true learning.

So Let’s begin.

Origin of the acronym SOLID

SOLID is an acronym named by Robert C. Martin (Uncle Bob). It represents 5 principles of object-oriented programming :

  • Single responsibility Principle
  • Open/Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

If we apply these five principles:

  • We will have flexible code, which we can easily change and that will be both reusable and maintainable.
  • The software developed will be robust, stable and scalable (we can easily add new features).
  • Together with the use of the Design Patterns, it will allow us to create software that is highly cohesive (that is, the elements of the system are closely related) and loosely coupled (the degree of dependence between elements is low).

So, SOLID can solve the main problems of a bad architecture:

  • Fragility: A change may break unexpected parts—it is very difficult to detect if you don’t have a good test coverage.
  • Immobility: A component is difficult to reuse in another project—or in multiple places of the same project—because it has too many coupled dependencies.
  • Rigidity: A change requires a lot of efforts because affects several parts of the project.

Of course, as Uncle Bob pointed out in a his article, these are not strict rules, but just guidelines to improve the quality of your architecture.

Principles will not turn a bad programmer into a good programmer. Principles have to be applied with judgement. If they are applied by rote it is just as bad as if they are not applied at all.

Principles

The Single Responsibility Principle (SRP)

According to this principle, a class should have a reason, and only one, to change. That is, a class should only have one responsibility.

Now let’s describe Single Responsibility Principle says :

THERE SHOULD NEVER BE MORE THAN ONE REASON FOR A CLASS TO CHANGE.

Every time you create/change a class, you should ask yourself: How many responsibilities does this class have?

Let’s take a look into Swifty communication program.

import Foundation

class InterPlanetMessageReceiver {

    func receiveMessage() {
        print("Received the Message!")
    }

    func displayMessageOnGUI() {
      print("Displaying Message on Screen!")
    }
}

Now let’s understand what is Single Responsibility Principle (SRP) and how the above program doesn’t obey it.

SRP says, “Just because you can implement all the features in a single device, you shouldn’t”.

In Object Oriented terms it means: There should never be more than one reason for a class to change. It doesn’t mean you can’t have multiple methods but the only condition is that they should have one single purpose.

Why? Because it adds a lot of manageability problems for you in the long run.

Here, the InterPlanetMessageReceiver class does the following:

  • It receives the message.
  • It renders it on UI.

And, two applications are using this InterPlanetMessageReceiver class:

  • A messaging application uses this class to receive the message
  • A graphical application uses this class to draw the message on the UI

Do you think it is violating the SRP?

YES, The InterPlanetMessageReceiver class is actually performing two different things. First, it handles the messaging, and second, displaying the message on GUI. This causes some interesting problems:

  • Swifty must include the GUI into the messaging application and also while deploying the messaging application, we must include the GUI library.
  • A change to the InterPlanetMessageReceiver class for the graphical application may lead to a change, build, and test for the messaging application, and vice-versa.

Swifty got frustrated with the amount of changes it required. He thought it would be a minute job but now he has already spent hours on it. So he decided do make a change into his program and fix this dependency.

This is what Swifty came up with

import Foundation

// Handles received message
class InterPlanetMessageReceiver {

    func receive() {
        print("Received the Message!")
    }
}

// Handles the display part
class InterPlanetMessageDisplay {

    func displayMessageOnGUI() {
      print("Displaying Message on Screen!")
    }
}

Here’s how Swifty explained this:

InterPlanetMessageReceiver class will be used by the messaging application, and the InterPlanetMessageDisplay class will be used by the graphical application. We could even separate the classes into two separate files, and that will allow us not to touch the other in case a change is needed to be implemented in one.

Finally, Swifty noted down :Why we need SRP?

  • Each responsibility is an agent of change.
  • Code becomes coupled if classes have more than one responsibility.

Open/Closed Principle

According to this principle, we must be able to extend the a class without changing its behaviour. This is achieved by abstraction.

Now let’s describe Open/Closed Principle says :

SOFTWARE ENTITIES (CLASSES, MODULES, FUNCTIONS, ETC.) SHOULD BE OPEN FOR EXTENSION, BUT CLOSED FOR MODIFICATION.

If you want to create a class easy to maintain, it must have two important characteristics:

  • Open for extension: You should be able to extend or change the behaviours of a class without efforts.
  • Closed for modification: You must extend a class without changing the implementation.

Let’s see our swifty example. Swifty was quite happy with these change and later he celebrated it with a drink in Swiftzen’s best pub and there his eyes fell upon an artifact hanging on the front wall and he found all the symbols he received in the message. Quickly, he opened his diary and completed deciphering all those shapes.

Next day when he returned back, he thought why not fix the DrawGraphic class which draws only circle shape, to include the rest of the shapes and display the message correctly.

// This is the DrawGraphic
class DrawGraphic {

  func drawShape() {
     print("Circle is drawn!")
  }
}

// Updated Class code

enum Shape {
  case circle
  case rectangle
  case square
  case triangle
  case pentagon
  case semicircle
}
class circle {

}

// This is the DrawGraphic
class DrawGraphic {

  func drawShape(shape: Shape) {
     switch shape {
        case .circle:
          print("Circle is drawn")
        case .rectangle:
          print("Rectangle is drawn")
        case square:
          print("Square is drawn")
        case triangle:
          print("Triangle is drawn")
        case pentagon:
          print("Pentagon is drawn")
        case semicircle:
          print("Semicircle is drawn")
        default:
          print("Shape not provided")
     }
  }
}

Swifty was not happy with these changes, what if in future a new shape shows up, after all he saw in the artifacts that there were around 123 shapes. This class will become one fat class. Also, DrawGraphics class is used by other applications and so they also have to adapt to this change. it was nightmare for Swifty.

Open Closed Principle solves nightmare for Swifty. At the most basic level, this means, you should be able to extend a class behavior without modifying it. It’s just like I should be able to put on a dress without doing any change to my body. Imagine what would happen if for every dress I have to change my body.

After hours of thinking, Swifty came up with below implementation of DrawGraphic class.

protocol Draw {
    func draw()
}

class Circle: Draw {
    func draw() {
        print("Circle is drawn!")
    }
}

class Rectangle: Draw {
    func draw() {
        print("Rectangle is drawn!")
    }
}

class DrawGraphic {
    func drawShape(shape: Draw) {
        shape.draw()
    }
}

let circle = Circle()
let rectangle = Rectangle()
let drawGraphic = DrawGraphic()

drawGraphic.drawShape(shape: circle)  // Circle is drawn!
drawGraphic.drawShape(shape: rectangle) // Rectangle is drawn!

Since the DrawGraphic is responsible for drawing all the shapes, and because the shape design is unique to each individual shape, it seems only logical to move the drawing for each shape into its respective class.

That means the DrawGraphic still have to know about all the shapes, right? Because how does it know that the object it’s iterating over has a draw method? Sure, this could be solved with having each of the shape classes inherit from a protocol: the Draw protocol (this can be an abstract class too).

Circle and Rectangle classes holds a reference to the protocol, and the concrete DrawGraphic class implements the protocol Draw class. So, if for any reason the DrawGraphic implementation is changed, the Circle and Rectangle classes are not likely to require any change or vice-versa.

Liskov Subsitution Principle

This principle, introduced by Barbara Liskov in 1987, states that in a program any class should be able to be replaced by one of its subclasses without affecting its functioning.

Now let’s describe Liskov Substitution Principle says :

FUNCTIONS THAT USE POINTERS OR REFERENCES TO BASE CLASSES MUST BE ABLE TO USE OBJECTS OF DERIVED CLASSES WITHOUT KNOWING IT.

Inheritance may be dangerous and you should use composition over inheritance to avoid a messy codebase. Even more if you use inheritance in an improper way.

This principle can help you to use inheritance without messing it up.

Let’s see our swifty example. Swifty was implementing the SenderOrigin class to know whether the sender is from a Planet or not.

The Sender class looked something like this

Class Planet {
  func orbitAroundSun() {
  }
}

class Earth: Planet {
  func description() {
    print("It is Earth!")
  }
}

class Pluto: Planet {
  func description() {
    print("It is Pluto!")
  }
}

class Sender {
  func senderOrigin(planet: Planet) {
    planet.description()
  }
}

In the class design, Pluto should not inherit the Planet class because it is a dwarf planet, and there should be a separate class for Planet that has not cleared the neighborhood around its orbit and Pluto should inherit that.

So the principle says that Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

Swifty whispered it is the polymorphism. Yes it is. “Inheritance” is usually described as an “is a” relationship. If a “Planet” is a “Dwarf”, then the “Planet” class should inherit the “Dwarf” class. Such “Is a” relationships are very important in class designs, but it’s easy to get carried away and end up with a wrong design and a bad inheritance.

The “Liskov’s Substitution Principle” is just a way of ensuring that inheritance is used correctly.

In the above case, both Earth and Pluto can orbit around the Sun but Pluto is not a planet. It has not cleared the neighborhood around its orbit. Swifty understood this and changed the program.

class Planet {
    func oribitAroundSun() {
        print("This planet Orbit around Sun!")
    }
}

class Earth: Planet {
    func description() {
        print("Earth")
    }
}

class DwarfPlanet: Planet {
    func notClearedNeighbourhoodOrbit() {

    }
}

class Pluto: DwarfPlanet {
  func description() {
        print("Pluto")
    }
}

class Sender {
    func senderOrigin(from: Planet) {
        from.description()
    }
}

let pluto = Pluto()
let earth = Earth()

let sender = Sender()
sender.senderOrigin(from: pluto) // Pluto
sender.senderOrigin(from: earth) // Earth

Here, Pluto inherited the planet but added the notClearedNeigbourhood method which distinguishes a dwarf and regular planet.

  • If LSP is not maintained, class hierarchies would be a mess, and if a subclass instance was passed as parameter to methods, strange behavior might occur.
  • If LSP is not maintained, unit tests for the base classes would never succeed for the subclass.

Swifty can design objects and apply LSP as a verification tool to test the hierarchy whether inheritance is properly done.

Interface Segregation Principle

The Principle of segregation of the interface indicates that it is better to have different interfaces (protocols) that are specific to each client, than to have a general interface. In addition, it indicates that a client would not have to implement methods that he does not use.

Now let’s describe Interface Segragation Principle says :

CLIENTS SHOULD NOT BE FORCED TO DEPEND UPON INTERFACES THAT THEY DO NOT USE.

This principle introduces one of the problems of object-oriented programming: the fat interface.

An interface is called “fat” when has too many members/methods, which are not cohesive and contains more information than we really want. This problem can affect both classes and protocols.

Let’s continue our swifty example. Swifty was quite astonished with the improvement in his program. All the changes were making more sense. Now, it was time to share this code with different planet. Swiftzen 50% GDP was dependent on selling softwares and many planet has requested and signed MOU for the Inter Planet communication system.

Swifty was ready to sell the program and but he was not satisfied with current client interface. Let’s us look into it.

protocol interPlanetCommunication {
  func switchOnAntenna()
  func setAntennaAngle()
  func transmitMessage()
  func receivedMessage()
  func displayMessageOnGUI()
}

Now for anyone who want to use interPlanetCommunication, he has to implement all the five methods even-though they might not required.

So the principle says that Many client-specific interfaces are better than one general purpose interface. The principle ensures that Interfaces are developed so that each of them have their own responsibility and thus they are specific, easily understandable, and re-usable.

Swifty quickly made changes to his program interface:

protocol antenna {
  func switchOnAntenna()
  func setAntennaAngle()
}

protocol message {
  func transmitMessage()
  func receivedMessage()
}

protocol dispaly {
  func displayMessageOnGUI()
}

Dependency Inversion Principle

According to the Dependency inversion principle:

“HIGH LEVEL MODULES SHOULD NOT DEPEND UPON LOW LEVEL MODULES. BOTH SHOULD DEPEND UPON ABSTRACTIONS.”

“ABSTRACTIONS SHOULD NOT DEPEND UPON DETAILS. DETAILS SHOULD DEPEND UPON ABSTRACTIONS.”

This principle tries to reduce the dependencies between modules, and thus achieve a lower coupling between classes.

This principle is the right one to follow if you believe in reusable components.

DIP is very similar to Open-Closed Principle: the approach to use, to have a clean architecture, is decoupling the dependencies. You can achieve it thanks to abstract layers.

Let’s continue our swifty example. Before finally shipping the program to all the clients, Swifty was trying to fix the password reminder class which looks like this.

class PasswordReminder {
  func connectToDatabase(db: SwiftZenDB) {
    print("Database Connected to SwiftzenDB")
  }

  func sendReminder() {
    print("Send Reminder")
  }
}

PasswordReminder class is dependent on a lower level module i.e. database connection. Now, let suppose that you want to change the database connection from Swiftzen to Objective-Czen, you will have to edit the PasswordReminder class.

Finally the last principle states that Entities must depend on abstractions not on concretions.

To fix above program Swifty made these changes:

protocol DBConnection {
  func connection()
}

class SwiftzenDB: DBConnection {
  func connection() {
    print("Connected to SwiftzenDB")
  }
}

class PasswordReminder {
  func connectToDatabase(db: DBConnection) {
    db.connection()
  }

  func sendReminder() {
    print("Send Reminder")
  }
}

The DBConnection protocol has a connection method and the SwiftzenDB class implements this protocol, also instead of directly type-hinting SwiftzenDB class in PasswordReminder, Swifty instead type-hint the protocol and no matter the type of database your application uses, the PasswordReminder class can easily connect to the database without any problems and OCP is not violated.

The point is rather than directly depending on the SwiftzenDB, the passwordReminder depends on the abstraction of some specification of Database so that if any the Database conforms to the abstraction, it can be connection with the PasswordReminder and it will work.

That’s all about in this article.

Conclusion

In this article, We understood about SOLID principles in Swift. We learnt that how SOLID is application for Swift. If we follow SOLID principles judiciously, we can increase the quality of our code. Moreover, our components can become more maintainable and reusable.

The mastering of these principles is not the last step to become a perfect developer, actually, it’s just the beginning. We will have to deal with different problems in our projects, understand the best approach and, finally, check if we are breaking some principles.

We have 3 enemies to defeat: Fragility, Immobility and Rigidity. SOLID principles are our weapons. We tried to explain the SOLID concepts in Swift easy way with examples.

Thanks for reading ! I hope you enjoyed and learned about SOLID Principles in Swift. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe us on this blog and and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

If you have any comments, questions, or think I missed something, feel free to leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

ReactJS – Is SOLID Valid For React?

Hello Readers, CoolMonkTechie heartily welcomes you in this article.

In this article, we will learn about topic ” Is Solid principle valid for React? “. We will focus each principle details of the SOLID one by one using below points:

  • What does the principle mean?
  • How does React adhere to it ?


SOLID is an interesting topic in React. While React doesn’t force the principles onto you, at least it often allows you to follow them.

A famous quote about learning is :

That is what learning is. You suddenly understand something you’ve understood all your life, but in a new way. ”  

So, Let’s begin.


What does SOLID stand for?

SOLID is an acronym build by the first letter of 5 object oriented programming design principles. The basic idea is, if you follow these principles, your software gets better.

  • Single responsibility principle
  • Open/closed principle
  • Liskov substitution principle
  • Interface segregation principle
  • Dependency inversion principle


What do these principles imply and how does React adhere to it?


1. Single Responsibility Principle

What does it mean?

A class should only have a single responsibility.

How does React adhere to it?

React applications consist of components, which are classes that inherit from the React.Component class. You can start building your application as a component and if it gets too complex, you can split this component up into multiple smaller components.

React doesn’t force you to adhere to the principle, but you can split up your component classes into smaller components till you achieved single responsibility for all of your components.

For example, you could have a button component that just handles clicks and an input component that just handles user input. A level above you use a form component that uses multiple instances of the button and input component to get user credentials and above that a connection component that takes form data and sends it to a server.


2. Open Close Principle

What does it mean?

Software entities should be open for extension, but closed for modification. Which means you can extends it without modifying its source code.

How does React adhere to it?

Reacts component model is build around aggregation instead of inheritance. So you only extend the base React.Component and not its children. This prevents you from overriding behavior of existing components directly. The only way is to wrap it with your own component.

You could for example wrap a Button with a RedButton that always applies specific styles to the basic Button, but the Button is closed for modification.

This is less flexible than inheritance, but it also simplifies the API. While you don’t have direct access to the methods like in an extension, you only have to care about props in your aggregation.


3. Liskov Substitution Principle

What does it mean?

Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

How does React adhere to it?

Well, it doesn’t use inheritance at all. Sure you extend React.Component, but this class is essentially treated as abstract in React applications, you never directly create an object from it, so you never have to replace it with a child-class later.

On the other hand, you find yourself writing aggregations that should act like their wrapped components rather often. Like the Button I mentioned before. You want that RedButton to be already styled, but you also want it to act like the Button, but since the API between components always is just props, it’s often simple to add something while your wrappers props are passed down to the wrapped component. Because everything is dynamic, your wrapper doesn’t even have to know everything about the data that would originally be passed down to the wrapped component, in the RedButton example it would just have to know about the style.


4. Interface Segregation Principle

What does it mean?

Many client-specific interfaces are better than one general-purpose interface.

How does React adhere to it?

Because React is written in JavaScript, it benefits from the dynamic nature of this language. There are no formal interfaces. If you don’t use refs, which allow you to directly call class methods of a component, the only interaction between components is via props and nobody forces you to use props you don’t need.

If you have a wrapper component that passes down an onClick handler that shows an alert with the wrapped components class name, you can use this wrapper to wrap all components that use this onClick prop and if they don’t, the handler is just ignored.

My experience with this fact was that it simplified many things, you wouldn’t get lost in defining many small interfaces beforehand. The drawback was that I often found me in situations where I passed down props the wrapped component did simply ignore silently. At least glamorous-native threw a few warnings when I tried to pass down unknown CSS attributes. For this it often helps to use PropTypes or something.


5. Dependency Inversion Principle

What does it mean?

One should depend upon abstractions, not concretions.

How does React adhere to it?

In practice, this principle is often followed by removing class names from other classes. Like, you could have a List that has Items, so you could get the idea to create your Item objects inside the List class, now you have your List tightly coupled with your Item. Somewhere in your List class is a new Item(...) or Item.create(...) etc.

React doesn’t strictly adhere to it, you can pass an array of string to your List component and create Item children from it no problem.

But you can also tell the List it should simply render out its children independent of what they are, maybe add some keys to it or justify them etc.

Now you can create an array of Items, sprinkle it with some HighlightItems, both created from different string arrays and put them inside your List who won’t be the wiser.

That’s all about in this article.


Conclusion

In this article, We understood What do SOLID principles imply and how does React adhere to it. We learnt that React doesn’t force the principles onto you, but at least it often allows you to follow them. Sometimes it gets easier because of JavaScript, sometimes JavaScript makes it harder, but overall it is possible to write SOLID applications with React..

Thanks for reading ! I hope you enjoyed and learned about SOLID imply and adhere concepts in React. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe us on this blog and and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

If you have any comments, questions, or think I missed something, feel free to leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

JavaScript – S.O.L.I.D – The Five Principles Of Object Oriented Design

Hello Readers, CoolMonkTechie heartily welcomes you in this article.

In this article, we will learn about the Javascript 5 SOLID Principles. The SOLID principles are a set of software design principles, that help us to understand how we can structure our code in order to be robust, maintainable and flexible as much as possible.

“JavaScript is a loosely typed language, some consider it a functional language, others consider it an object oriented language, some think its both, and some think that having classes in JavaScript is just plain wrong. — Dor Tzur.”

From my experience, we’ll rarely want to use classes and long inheritance chains in JavaScript. But still, SOLID principles are pretty solid.

SOLID principles are the basic pillars of OOP set forward by our beloved Uncle Bob.

A famous quote about learning is :

” Live as if you were to die tomorrow. Learn as if you were to live forever. “

So Let’s begin.


What is S.O.L.I.D. ?

S.O.L.I.D. stands for :

  • S — Single responsibility principle
  • O — Open closed principle
  • L — Liskov substitution principle
  • I — Interface segregation principle
  • D — Dependency Inversion principle

Let’s see them one by one.


1. Single Responsibility Principle

“A class should have one and only one reason to change, meaning that a class should only have one job.”

For example, say we have some shapes and we wanted to sum all the areas of the shapes. 

const circle = (radius) => {
  const proto = { 
    type: 'Circle',
    //code 
  }
  return Object.assign(Object.create(proto), {radius})
}
const square = (length) => {
  const proto = { 
    type: 'Square',
    //code 
  }
  return Object.assign(Object.create(proto), {length})
}

First, we create our shapes factory functions and setup the required parameters.

so, What is a factory function ?

“In JavaScript, any function can return a new object. When it’s not a constructor function or class, it’s called a factory function.”

Next, we move on by creating the areaCalculator factory function and then write up our logic to sum up the area of all provided shapes.

const areaCalculator = (s) => {
  const proto = {
    sum() {
      // logic to sum
    },
    output () {
     return `
       <h1>
         Sum of the areas of provided shapes:
         ${this.sum()} 
       </h1>
    }
  }
  return Object.assign(Object.create(proto), {shapes: s})
}

To use the areaCalculator factory function, we simply call the function and pass in an array of shapes, and display the output at the bottom of the page.

const shapes = [
  circle(2),
  square(5),
  square(6)
]
const areas = areaCalculator(shapes)
console.log(areas.output())

The problem with the output method is that the areaCalculator handles the logic to output the data. Therefore, what if the user wanted to output the data as json or something else ?

All of the logic would be handled by the areaCalculator factory function, this is what ‘Single Responsibility principle’ frowns against; the areaCalculator factory function should only sum the areas of provided shapes, it should not care whether the user wants JSON or HTML.

So, to fix this you can create an SumCalculatorOutputter factory function and use this to handle whatever logic you need on how the sum areas of all provided shapes are displayed.

The sumCalculatorOutputter factory function would work liked this:

const shapes = [
  circle(2),
  square(5),
  square(6)
]
const areas  = areaCalculator(shapes)
const output = sumCalculatorOputter(areas)
console.log(output.JSON())
console.log(output.HAML())
console.log(output.HTML())
console.log(output.JADE())

Now, whatever logic you need to output the data to the users is now handled by the sumCalculatorOutputter factory function.


2. Open-Closed Principle

“Objects or entities should be open for extension, but closed for modification.”

Open for extension means that we should be able to add new features or components to the application without breaking existing code. Closed for modification means that we should not introduce breaking changes to existing functionality, because that would force you to refactor a lot of existing code — Eric Elliott”

In simpler words, means that a class or factory function in our case, should be easily extendable without modifying the class or function itself. Let’s look at the areaCalculator factory function, especially it’s sum method.

sum () {
 
 const area = []
 
 for (shape of this.shapes) {
  
  if (shape.type === 'Square') {
     area.push(Math.pow(shape.length, 2)
   } else if (shape.type === 'Circle') {
     area.push(Math.PI * Math.pow(shape.length, 2)
   }
 }
 return area.reduce((v, c) => c += v, 0)
}

If we wanted the sum method to be able to sum the areas of more shapes, we would have to add more if/else blocks and that goes against the open-closed principle.

A way we can make this sum method better is to remove the logic to calculate the area of each shape out of the sum method and attach it to the shape’s factory functions.

const square = (length) => {
  const proto = {
    type: 'Square',
    area () {
      return Math.pow(this.length, 2)
    }
  }
  return Object.assign(Object.create(proto), {length})
}

The same thing should be done for the circle factory function, an area method should be added. Now, to calculate the sum of any shape provided should be as simple as:

sum() {
 const area = []
 for (shape of this.shapes) {
   area.push(shape.area())
 }
 return area.reduce((v, c) => c += v, 0)
}

Now we can create another shape class and pass it in when calculating the sum without breaking our code. However, now another problem arises, how do we know that the object passed into the areaCalculator is actually a shape or if the shape has a method named area ?

Coding to an interface is an integral part of S.O.L.I.D., a quick example is we create an interface, that every shape implements.

Since JavaScript doesn’t have interfaces, So how can we achieved this, in the lack of interfaces ?

Function Composition to the rescue !

First we create shapeInterface factory function, as we are talking about interfaces, our shapeInterface will be as abstracted as an interface, using function composition.

const shapeInterface = (state) => ({
  type: 'shapeInterface',
  area: () => state.area(state)
})

Then we implement it to our square factory function.

const square = (length) => {
  const proto = {
    length,
    type : 'Square',
    area : (args) => Math.pow(args.length, 2)
  }
  const basics = shapeInterface(proto)
  const composite = Object.assign({}, basics)
  return Object.assign(Object.create(composite), {length})
}

And the result of calling the square factory function will be the next one:

const s = square(5)
console.log('OBJ\n', s)
console.log('PROTO\n', Object.getPrototypeOf(s))
s.area()

// output
OBJ
 { length: 5 }
PROTO
 { type: 'shapeInterface', area: [Function: area] }
25

In our areaCalculator sum method we can check if the shapes provided are actually types of shapeInterface, otherwise we throw an exception:

sum() {
  const area = []
  for (shape of this.shapes) {
    if (Object.getPrototypeOf(shape).type === 'shapeInterface') {
       area.push(shape.area())
     } else {
       throw new Error('this is not a shapeInterface object')
     }
   }
   return area.reduce((v, c) => c += v, 0)
}

and again, since JavaScript doesn’t have support for interfaces like typed languages the example above demonstrates how we can simulate it, but more than simulating interfaces, what we are doing is using closures and function composition.


3. Liskov Substitution Principle

“Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.”

All this is stating is that every subclass/derived class should be substitutable for their base/parent class.

In other words, as simple as that, a subclass should override the parent class methods in a way that does not break functionality from a client’s point of view.

Still making use of our areaCalculator factory function, say we have a volumeCalculator factory function that extends the areaCalculator factory function, and in our case for extending an object without breaking changes in ES6 we do it by using Object.assign() and the Object.getPrototypeOf():

const volumeCalculator = (s) => {
  const proto = {
    type: 'volumeCalculator'
  }
  const areaCalProto = Object.getPrototypeOf(areaCalculator())
  const inherit = Object.assign({}, areaCalProto, proto)
  return Object.assign(Object.create(inherit), {shapes: s})
}

For Another example, if we want to implement classes for a bunch of shapes, we can have a parent Shape class, which are extended by all classes by implementing everything in the Shape class.

We can write the following to implement some shape classes and get the area of each instance:

class Shape {
  get area() {
    return 0;
  }
}
class Rectangle extends Shape {
  constructor(length, width) {
    super();
    this.length = length;
    this.width = width;
  }
  get area() {
    return this.length * this.width;
  }
}
class Square extends Shape {
  constructor(length) {
    super();
    this.length = length;
  }
  get area() {
    return this.length ** 2;
  }
}
class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }
  get area() {
    return Math.PI * (this.radius ** 2);
  }
}
const shapes = [
  new Rectangle(1, 2),
  new Square(1, 2),
  new Circle(2),
]
for (let s of shapes) {
  console.log(s.area);
}

Since we override the area getter in each class that extends Shape , we get the right area for each shape since the correct code is run for each shape to get the area.


4. Interface Segregation Principle

“A client should never be forced to implement an interface that it doesn’t use or clients shouldn’t be forced to depend on methods they do not use.”

Continuing with our shapes example, we know that we also have solid shapes, so since we would also want to calculate the volume of the shape, we can add another contract to the shapeInterface:

const shapeInterface = (state) => ({
  type: 'shapeInterface',
  area: () => state.area(state),
  volume: () => state.volume(state)
})

Any shape we create must implement the volume method, but we know that squares are flat shapes and that they do not have volumes, so this interface would force the square factory function to implement a method that it has no use of. 

Interface segregation principle says no to this, instead we could create another interface called solidShapeInterface that has the volume contract and solid shapes like cubes etc. can implement this interface.

const shapeInterface = (state) => ({
  type: 'shapeInterface',
  area: () => state.area(state)
})
const solidShapeInterface = (state) => ({
  type: 'solidShapeInterface',
  volume: () => state.volume(state)
})
const cubo = (length) => {
  const proto = {
    length,
    type   : 'Cubo',
    area   : (args) => Math.pow(args.length, 2),
    volume : (args) => Math.pow(args.length, 3)
  }
  const basics  = shapeInterface(proto)
  const complex = solidShapeInterface(proto)
  const composite = Object.assign({}, basics, complex)
  return Object.assign(Object.create(composite), {length})

This is a much better approach, but a pitfall to watch out for is when to calculate the sum for the shape, instead of using the shapeInterface or a solidShapeInterface.

We can create another interface, maybe manageShapeInterface, and implement it on both the flat and solid shapes, this is way we can easily see that it has a single API for managing the shapes, for example:

const manageShapeInterface = (fn) => ({
  type: 'manageShapeInterface',
  calculate: () => fn()
})
const circle = (radius) => {
  const proto = {
    radius,
    type: 'Circle',
    area: (args) => Math.PI * Math.pow(args.radius, 2)
  }
  const basics = shapeInterface(proto)
  const abstraccion = manageShapeInterface(() => basics.area())
  const composite = Object.assign({}, basics, abstraccion)
  return Object.assign(Object.create(composite), {radius})
}
const cubo = (length) => {
  const proto = {
    length,
    type   : 'Cubo',
    area   : (args) => Math.pow(args.length, 2),
    volume : (args) => Math.pow(args.length, 3)
  }
  const basics  = shapeInterface(proto)
  const complex = solidShapeInterface(proto)
  const abstraccion = manageShapeInterface(
    () => basics.area() + complex.volume()
  )
  const composite = Object.assign({}, basics, abstraccion)
  return Object.assign(Object.create(composite), {length})
}

As we can see until now, what we have been doing for interfaces in JavaScript are factory functions for function composition.

And here, with manageShapeInterface what we are doing is abstracting again the calculate function, what we doing here and in the other interfaces (if we can call it interfaces), we are using “high order functions” to achieve the abstractions.


5. Dependency Inversion Principle

“Entities must depend on abstractions not on concretions. It states that the high level module must not depend on the low level module, but they should depend on abstractions.”

As a dynamic language, JavaScript doesn’t require the use of abstractions to facilitate decoupling. Therefore, the stipulation that abstractions shouldn’t depend upon details isn’t particularly relevant to JavaScript applications. The stipulation that high level modules shouldn’t depend upon low level modules is, however, relevant.

From a functional point of view, these containers and injection concepts can be solved with a simple higher order function, or hole-in-the-middle type pattern which are built right into the language.”

This principle allows for decoupling. And we have made it before, let’s review our code with the manageShapeInterface and how we accomplish the calculate method.

const manageShapeInterface = (fn) => ({
  type: 'manageShapeInterface',
  calculate: () => fn()
})

What the manageShapeInterface factory function receives as the argument is a higher order function, that decouples for every shape the functionality to accomplish the needed logic to get to final calculation, let see how this is done in the shapes objects.

const square = (radius) => {
  // code
 
  const abstraccion = manageShapeInterface(() => basics.area())
 
 // more code ...
}
const cubo = (length) => {
  // code 
  const abstraccion = manageShapeInterface(
    () => basics.area() + complex.volume()
  )
  // more code ...
}

For the square what we need to calculate is just getting the area of the shape, and what we need is summing the area with the volume and that is everything need to avoid the coupling and get the abstraction.

For Another example, we analyse that the Http Request depends on the setState function, which is a detail. These code violates the Dependency Inversion principle.

http.get("http://address/api/examples", (res) => {
 this.setState({
  key1: res.value1,
  key2: res.value2,
  key3: res.value3
 });
});

The correct code is :

//Http request
const httpRequest = (url, setState) => {
 http.get(url, (res) => setState.setValues(res))
};

//State set in another function
const setState = {
 setValues: (res) => {
  this.setState({
    key1: res.value1,
    key2: res.value2,
    key3: res.value3
  })
 }
}

//Http request, state set in a different function
httpRequest("http://address/api/examples", setState);

That’s all about in this article.


Conclusion

In this article, We understood about the Javascript 5 SOLID Principles. The main goal of the SOLID principles is that any software should tolerate change and should be easy to understand. We conclude that SOLID is very useful for write code :

  • Easy to understand
  • Where things are where they’re supposed to be
  • Where classes do what they were intended to do
  • That can be easily adjusted and extended without bugs
  • That separates the abstraction from the implementation
  • That allows to easily swap implementation (Db, Api, frameworks, …)
  • Easily testable

Thanks for reading !! I hope you enjoyed and learned about SOLID Principles Concept in javascript. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe us on this blog and and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

If you have any comments, questions, or think I missed something, feel free to leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Exit mobile version