JavaScript – S.O.L.I.D – The Five Principles of Object Oriented Design – Part 3

In this article, we will learn about the Javascript 5 SOLID Principles. We will discuss about the other remaining SOLID principle Liskov substitution Principle, Interface segregation principle, Dependency Inversion principle . This is Part 3 for Javascript Object oriented design SOLID principles.

solid.png

So Let’s begin.
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 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})
}



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 you 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.

You can create another interface, maybe manageShapeInterface, and implement it on both the flat and solid shapes, this is way you 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 you 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.


Conclusion


In this article (Part-3), We have discussed about the other remaining SOLID principles Liskov substitution Principle, Interface segregation principle, Dependency Inversion principle“.

In previous article (Part-1 and Part-2), We have also discussed  about the first  two SOLID principles Single responsibility principle, Open closed principle


Thanks for reading ! I hope you enjoyed and learned about Javascript Object oriented design SOLID principles concepts. Reading is one thing, but the only way to master it is to do it yourself.

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!!😊😊😊


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s