DEV Community

loading...
Cover image for Solid Principles in Javascript

Solid Principles in Javascript

francescoxx profile image Francesco Ciulla ・4 min read

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
  • flexible

as much as possible

here come the S.O.L.I.D. principles:

  • S Single Responsibility

  • O: Open/Close

  • L: Liskov Substitution

  • I: Interface Segregation

  • D: Dependency Inversion

Let's see them one by one, with a sime javascript example for each one


S - Single Responsibility Principle

Any function must be responsible for doing only ONE thing.

Example : Let's say we want to validate a form, then create a user in a Postgres Database

NO

/* A function with such a name is a symptom of ignoring the Single Responsibility Principle
*  Validation and Specific implementation of the user creation is strongly coupled.
*  That's not good
*/ 
validateAndCreatePostgresUser = (name, password, email) => {   

  //Call an external function to validate the user form
  const isFormValid = testForm(name, password, email); 

  //Form is Valid
  if(isFormValid){
    User.Create(name, password, email) //Specific implementation of the user creation!
  }
}

YES

//Only Validate
validateRequest = (req) => {

  //Call an external function to validate the user form
  const isFormValid = testForm(name, password, email); 

  //Form is Valid
  if(isFormValid){
    createUser(req); // The user creation will be implemented in another function
  }
}

//Only Create User in the Database
createUser = (req) => User.Create(req.name, req.password, req.email)

This seems a pretty little change, but think about strongly coupling all the methods, than you have to change the Database for any reason...


O - Open-Closed Principle

Software systems must be allowed to change their behavior by adding new code rather than changing the existing code.

Open for extension, but Closed to modification

If we have something like this:

const roles = ["ADMIN", "USER"]
checkRole = (user) => {
  if(roles.includes(user.role)){
    return true; 
  }else{
    return false
  }
}

//Test role
checkRole("ADMIN"); //true
checkRole("Foo"); //false

And we want to add a superuser, for any reason, instead of modifying the existing code (or maybe we just can't modify it),

we could do it in another function.


//UNTOUCHABLE CODE!!!
const roles = ["ADMIN", "USER"]
checkRole = (user) => {
  if(roles.includes(user.role)){
    return true; 
  }else{
    return false
  }
}
//UNTOUCHABLE CODE!!!

//We can define a function to add a new role with this function
addRole(role){
  roles.push(role)
}

//Call the functuon with the new role to add to the existing ones
addRole("SUPERUSER");

//Test role
checkRole("ADMIN"); //true
checkRole("Foo"); //false
checkRole("SUPERUSER"); //true

L - Liskov Substitution Principle

Build software systems from interchangeable parts.

Those parts must adhere to a contract that allows those parts to be substituted one for another for the base class"

class Job {
  constructor(customer) {
    this.customer = customer;
    this.calculateFee = function () {
      console.log("calculate price"); //Add price logic
    };
  }
  Simple(customer) {
    this.calculateFee(customer);
  }
  Pro(customer) {
    this.calculateFee(customer);
    console.log("Add pro services"); //additional functionalities
  }
}



const a = new Job("Francesco");
a.Simple(); 
//Output:
//calculate price


a.Pro();
//Output: 
//calculate price 
//Add pro services...

I - Interface Segregation Principle

Must prevent classes from relying on modules or functions that they dont need.

We don't have interfaces in Javascript, but let's try with an example

NO

//Validate in any case
class User {
  constructor(username, password) {
    this.username = username;
    this.password = password;
    this.initiateUser();
  }
  initiateUser() {
    this.username = this.username;
    this.validateUser()
  }

  validateUser = (user, pass) => {
    console.log("validating...");
  }
}
const user = new User("Francesco", "123456");
console.log(user);
// validating...
// User {
//   validateUser: [Function: validateUser],
//   username: 'Francesco',
//   password: '123456'
// }

YES

//ISP: Validate only if it is necessary
class UserISP {
  constructor(username, password, validate) {
    this.username = username;
    this.password = password;
    this.validate = validate;

    if (validate) {
      this.initiateUser(username, password);
    } else {
      console.log("no validation required");
    }
  }

  initiateUser() {
    this.validateUser(this.username, this.password);
  }

  validateUser = (username, password) => {
    console.log("validating...");
  }
}

//User with validation required
console.log(new UserISP("Francesco", "123456", true));
// validating...
// UserISP {
//   validateUser: [Function: validateUser],
//   username: 'Francesco',
//   password: '123456',
//   validate: true
// }


//User with no validation required
console.log(new UserISP("guest", "guest", false));
// no validation required
// UserISP {
//   validateUser: [Function: validateUser],
//   username: 'guest',
//   password: 'guest',
//   validate: false
// }

D - Dependency Inversion Principle

Abstractions must not depend on details.

Details must depend on abstractions.

NO

//The Http Request depends on the setState function, which is a detail
http.get("http://address/api/examples", (res) => {
 this.setState({
  key1: res.value1,
  key2: res.value2,
  key3: res.value3
 });
});

YES

//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);

In Conclusion...

The main goal of the SOLID principles is that any software should tolerate change and should be easy to understand.

The S.O.L.I.D. principles can be very useful to 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

Discussion

pic
Editor guide
Collapse
dimpiax profile image
Dmytro Pylypenko

Hey, in your example of S, good to see clear example of responsibilities conjunction, where you follow also D principle:

// process
validateRequest = (req) => isValidForm(req) && createUser(req)

//Call an external function to validate the user form
isValidForm = (req) => testForm(req.name, req.password, req.email)

//Only Create User in the Database
createUser = (req) => User.Create(req.name, req.password, req.email)

What is the purpose of example in I:

initiateUser() {
  this.username = this.username; // this?
  this.password = this.password; // this?
  this.validateUser(this.username, this.password);
}
Collapse
francescoxx profile image
Francesco Ciulla Author

Dear Dmytro,
Thank you for noticing, the example in "I" has been revised.
But the intent was to just show the purpose of the principle, not the concrete implementation, which is lacking of course! As the validation method is just loggining a string :)

Collapse
paulosandsten profile image
Paulo Sandsten

When reading about the SRP, I see a conflict in how it is being described here versus how uncle Bob describes it in his book, "Clean Architecture"

"Of all the SOLID principles, the Single Responsibility Principle (SRP) might be the least well understood. That's likely because it has a particular inappropriate name. It is too easy for programmers to hear the name and then assume that it means that every module should do just one thing.

Make no mistake, there is a principle like that. A function should do one, and only one, thing. We use that principle when we are refactoring large functions into smaller functions; we use it at the lowest levels. But is is not one of the SOLID principles – it is not the SRP" - Uncle Bob

In the book mentioned, Uncle Bob defines it:
"A module should be responsible to one, and only one, actor"

With an actor, he mentions that it refers to one or more people who requires a change.

I really like that you are writing about SOLID and sharing. Thank you for that. I would like to hear more about your reasoning here regarding SRP.

Cheers.

Collapse
freshi profile image
Wachirajob

Nice post with good examples.

Collapse
francescoxx profile image
Collapse
jochemstoel profile image
Jochem Stoel

Hey, this was nice to read. Good for my ego. Did you come up with this SOLID JavaScript or is this a common term on the web?

Collapse
francescoxx profile image
Francesco Ciulla Author

A fairly widespread concept. Thanks for the feedback

Collapse
jochemstoel profile image
Jochem Stoel

I had never heard of this SOLID thing but arrived at exactly the same conclusions and methods myself so that is always nice to discover.

Collapse
damxipo profile image
Damian Cipolat

Nice examples, but I think the "L" of lisvok is useless if you don't work in JS using OOP.

Collapse
francescoxx profile image
Francesco Ciulla Author

Thank you. The important part is to grasp the concept.