5 SOLID principles with JavaScript. How to make your code SOLID
Tags: javascript, beginners, codenewbie, webdev
Published: 2020-09-19 12:51:23

### Hi 👋! I'm Denis.

SOLID principles are strictly related to **design patterns**. It's important to know design patterns because it's a hot topic for an interview. If you know them, you'll easily understand more sophisticated programming paradigms, architectural patterns, and language features such as *reactive programming*, *flux architecture (Redux)*, *generators in JavaScript*, etc.

## What are SOLID principles?
**SOLID** stands for

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

These 5 principles will guide you on how to write better code. Though they come from object-oriented programming. I know it's very daring to call JavaScript an object-oriented language :) Regardless, I promise that if you understand these principles, then when you design your next solutions, you will definitely ask yourself "Hey, am I violating the Single-responsibility principle?".

So, let's begin

## **S — Single responsibility principle**
It's probably the easiest principle, and at the same time, the most misunderstood one. 
>**A module should be responsible for only one actor. As a consequence, it has only one reason to change**

#### Example
Let's take a look at the following code:

```js
class TodoList {
  constructor() {
    this.items = []
  }

  addItem(text) {
    this.items.push(text)
  }

  removeItem(index) {
    this.items = items.splice(index, 1)
  }

  toString() {
    return this.items.toString()
  }

  save(filename) {
    fs.writeFileSync(filename, this.toString())
  }

  load(filename) {
    // Some implementation
  }
}
```
Ooops. Even though from first glance, this class seems to be fine, it violates the Single responsibility principle. We added second responsibility to our TodoList class which is the management of our database.

Let's fix the code so that it complies with the "S" principle.

```js
class TodoList {
  constructor() {
    this.items = []
  }

  addItem(text) {
    this.items.push(text)
  }

  removeItem(index) {
    this.items = items.splice(index, 1)
  }

  toString() {
    return this.items.toString()
  }
}

class DatabaseManager {
  saveToFile(data, filename) {
    fs.writeFileSync(filename, data.toString())
  }

  loadFromFile(filename) {
    // Some implementation
  }
}
```

Thus our code has become more scalable. Of course, it's not so obvious when we're looking at small solutions. When applied to a complex architecture, this principle takes on much more meaning.

## **O — Open closed principle**

> Modules should be open for extension but closed for modification

That means that if you want to extend a module's behavior, you won't need to modify the existing code of that module.

#### Example
```js
class Coder {
  constructor(fullName, language, hobby, education, workplace, position) {
    this.fullName = fullName
    this.language = language
    this.hobby = hobby
    this.education = education
    this.workplace = workplace
    this.position = position
  }
}

class CoderFilter {
  filterByName(coders, fullName) {
    return coders.filter(coder => coder.fullName === fullName)
  }

  filterBySize(coders, language) {
    return coders.filter(coder => coder.language === language)
  }

  filterByHobby(coders, hobby) {
    return coders.filter(coder => coder.hobby === hobby)
  }
}

```

The problem with `CoderFilter` is that if we want to filter by any other new property we have to change `CodeFilter`'s code. Let's solve this problem by creating a `filterByProp` function.
```js
const filterByProp = (array, propName, value) =>
  array.filter(element => element[propName] === value)
```

## **L — Liskov substitution principle**

A principle with the most confusing name. What does it mean?

> If you have a function, that works for a base type, it should work for a derived type

Let's go with a classic example

#### Example
```js
class Rectangle {
  constructor(width, height) {
    this._width = width
    this._height = height
  }

  get width() {
    return this._width
  }
  get height() {
    return this._height
  }

  set width(value) {
    this._width = value
  }
  set height(value) {
    this._height = value
  }

  getArea() {
    return this._width * this._height
  }
}

class Square extends Rectangle {
  constructor(size) {
    super(size, size)
  }
}

const square = new Square(2)
square.width = 3
console.log(square.getArea())
```

Guess what will be printed to the console. If your answer is `6`, you are right. Of course, the desired answer is 9. Here we can see a classic violation of the Liskov substitution principle.

By the way, to fix the issue you can define `Square` this way:
```js
class Square extends Rectangle {
  constructor(size) {
    super(size, size)
  }

  set width(value) {
    this._width = this._height = value
  }

  set height(value) {
    this._width = this._height = value
  }
}
```

## **I — Interface segregation principle**
> Clients should not be forced to depend upon interfaces that they do not use

There are no interfaces in JavaScript. There is a way to mimic their behavior, but I don't think there's much sense. Let's better adapt the principle to the js world.

#### Example
Let's define an "abstract" `Phone` class which will play role of the interface in our case:

```js
class Phone {
  constructor() {
    if (this.constructor.name === 'Phone')
      throw new Error('Phone class is absctract')
  }

  call(number) {}

  takePhoto() {}

  connectToWifi() {}
}
```

Can we use it to define an iPhone?
```js
class IPhone extends Phone {
  call(number) {
    // Implementation
  }

  takePhoto() {
    // Implementation
  }

  connectToWifi() {
    // Implementation
  }
}
```

Okay, but for an old Nokia 3310 this interface will violate the "I" principle

```js
class Nokia3310 extends Phone {
  call(number) {
    // Implementation
  }

  takePhoto() {
    // Argh, I don't have a camera
  }

  connectToWifi() {
    // Argh, I don't know what wifi is
  }
}
```

## **D — Dependency Inversion principle**
> High-level modules should not depend on low-level modules

Let's take a look at the following example:

#### Example

```js
class FileSystem {
  writeToFile(data) {
    // Implementation
  }
}

class ExternalDB {
  writeToDatabase(data) {
    // Implementation
  }
}

class LocalPersistance {
  push(data) {
    // Implementation
  }
}

class PersistanceManager {
  saveData(db, data) {
    if (db instanceof FileSystem) {
      db.writeToFile(data)
    }

    if (db instanceof ExternalDB) {
      db.writeToDatabase(data)
    }

    if (db instanceof LocalPersistance) {
      db.push(data)
    }
  }
}
```

In this case, a high-level module `PersistanceManager` depends on the low-level modules, which are `FileSystem`, `ExternalDB`, and `LocalPersistance`.

To avoid the issue in this simple case we should probably do something like this:

```js
class FileSystem {
  save(data) {
    // Implementation
  }
}

class ExternalDB {
  save(data) {
    // Implementation
  }
}

class LocalPersistance {
  save(data) {
    // Implementation
  }
}

class PersistanceManager {
  saveData(db, data) {
    db.save(data)
  }
}
```

Of course, this is an oversimplified example, but you've got the point.

## Conclusion
The value of SOLID principles is not obvious. But if you ask yourself "Am I violating SOLID principles" when you design your architecture, I promise that the quality and scalability of your code will be much better.

Thanks a lot for reading!
Feel free to follow me here on DEV.to and also on Twitter ([@DenisVeleaev](https://twitter.com/DenisVeleaev))

Peace!