📜 ⬆️ ⬇️

Pure javascript.Classes

Translation of the book by Ryan McDermott clean-code-javascript

Table of contents:




The principle of sole responsibility (SRP)


As it is written in the clean code, “There must be only one reason for changing the class”. It is tempting to shove everything into one class, like in a travel case. The problem is that your class will not be consensually connected, and you will often change it for every sneeze. It is very important to minimize changes in the classroom. When you make changes to a class with great functionality, it is hard to track the effects of your changes.

Poorly:
')
class UserSettings { constructor(user) { this.user = user; } changeSettings(settings) { if (this.verifyCredentials()) { // ... } } verifyCredentials() { // ... } } 

Good:

 class UserAuth { constructor(user) { this.user = user; } verifyCredentials() { // ... } } class UserSettings { constructor(user) { this.user = user; this.auth = new UserAuth(user); } changeSettings(settings) { if (this.auth.verifyCredentials()) { // ... } } } 

The principle of openness / closeness (OCP)


As stated by Bertrand Meyer, “entities (classes, modules, functions, etc.) should be open for extension, but closed for modification” (software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification). What does it mean? This means that you must give the opportunity to extend the functionality of the entity without changing the existing code.

Poorly:

 class AjaxAdapter extends Adapter { constructor() { super(); this.name = 'ajaxAdapter'; } } class NodeAdapter extends Adapter { constructor() { super(); this.name = 'nodeAdapter'; } } class HttpRequester { constructor(adapter) { this.adapter = adapter; } fetch(url) { if (this.adapter.name === 'ajaxAdapter') { return makeAjaxCall(url).then((response) => { //   }); } else if (this.adapter.name === 'httpNodeAdapter') { return makeHttpCall(url).then((response) => { //   }); } } } function makeAjaxCall(url) { //      } function makeHttpCall(url) { //      } 

Good:

 class AjaxAdapter extends Adapter { constructor() { super(); this.name = 'ajaxAdapter'; } request(url) { //      } } class NodeAdapter extends Adapter { constructor() { super(); this.name = 'nodeAdapter'; } request(url) { //      } } class HttpRequester { constructor(adapter) { this.adapter = adapter; } fetch(url) { return this.adapter.request(url).then((response) => { //   }); } } 

Barbara Liskov substitution principle


This is a scary term for a very simple concept.

Definition:

“Let q (x) be a property true for objects of some type T. Then q (y) must also be true for objects of type S, where S is a subtype of type T.” Wikipedia The definition is even worse than the name.

The bottom line is that if you have a parent and child classes, then they can be interchanged without errors. This can still be confusing, so let's look at a classic example of a rectangle area. Mathematically, a square is a rectangle, but if you solve this problem with the help of inheritance, then you will have problems. More details about the principle can be read here .

Poorly:

 class Rectangle { constructor() { this.width = 0; this.height = 0; } setColor(color) { // ... } render(area) { // ... } setWidth(width) { this.width = width; } setHeight(height) { this.height = height; } getArea() { return this.width * this.height; } } class Square extends Rectangle { setWidth(width) { this.width = width; this.height = width; } setHeight(height) { this.width = height; this.height = height; } } function renderLargeRectangles(rectangles) { rectangles.forEach((rectangle) => { rectangle.setWidth(4); rectangle.setHeight(5); const area = rectangle.getArea(); // BAD: Will return 25 for Square. Should be 20. rectangle.render(area); }); } const rectangles = [new Rectangle(), new Rectangle(), new Square()]; renderLargeRectangles(rectangles); 

Good:

 class Shape { setColor(color) { // ... } render(area) { // ... } } class Rectangle extends Shape { constructor() { super(); this.width = 0; this.height = 0; } setWidth(width) { this.width = width; } setHeight(height) { this.height = height; } getArea() { return this.width * this.height; } } class Square extends Shape { constructor() { super(); this.length = 0; } setLength(length) { this.length = length; } getArea() { return this.length * this.length; } } function renderLargeShapes(shapes) { shapes.forEach((shape) => { switch (shape.constructor.name) { case 'Square': shape.setLength(5); break; case 'Rectangle': shape.setWidth(4); shape.setHeight(5); } const area = shape.getArea(); shape.render(area); }); } const shapes = [new Rectangle(), new Rectangle(), new Square()]; renderLargeShapes(shapes); 

Interface Separation Principle (ISP)


In javascript, there are no intefaces, so this principle cannot be fully utilized. Nevertheless, it is important to use it, even in the absence of a javascript type system.

The ISP states that “Users should not depend on classes that they do not use” (). Interfaces are conditional conventions in JavaScript due to implicit typing. A good example in javascript can be classes with large configs. Do not force users of your class to enter a bunch of configs. They, as a rule, will not use them all. You will not have a “bold interface” if you make them optional.

Poorly:

 class DOMTraverser { constructor(settings) { this.settings = settings; this.setup(); } setup() { this.rootNode = this.settings.rootNode; this.animationModule.setup(); } traverse() { // ... } } const $ = new DOMTraverser({ rootNode: document.getElementsByTagName('body'), animationModule() {} //       . // ... }); 

Good:

 class DOMTraverser { constructor(settings) { this.settings = settings; this.options = settings.options; this.setup(); } setup() { this.rootNode = this.settings.rootNode; this.setupOptions(); } setupOptions() { if (this.options.animationModule) { // ... } } traverse() { // ... } } const $ = new DOMTraverser({ rootNode: document.getElementsByTagName('body'), options: { animationModule() {} } }); 

Dependency Inversion Principle (DIP)


This principle says two important things:

1. Top level modules should not be dependent on lower level modules. Both must depend on abstractions.
2. Abstractions should not be details. Details must be in child classes.

At first it is difficult to understand this principle. But if you worked with Angular.js, you saw the implementation of this principle as Dependency Injection (DI). Despite the fact that they are not identical concepts, DIP makes it possible to distinguish high-level modules from the details of low-level modules and their installation. He can do it through DI. This principle reduces communication between modules. If your modules are closely related, they are hard to refactor.

Abstractions are the implicit conventions that represent interfaces in JavaScript. That is, the methods and properties that the object / class provides to another object / class. In the example below, each instance of the InventoryTracker class will have a requestItems method.

Poorly:

 class InventoryRequester { constructor() { this.REQ_METHODS = ['HTTP']; } requestItem(item) { // ... } } class InventoryTracker { constructor(items) { this.items = items; //  ,        . //    requestItems        this.requester = new InventoryRequester(); } requestItems() { this.items.forEach((item) => { this.requester.requestItem(item); }); } } const inventoryTracker = new InventoryTracker(['apples', 'bananas']); inventoryTracker.requestItems(); 

Good:

 class InventoryTracker { constructor(items, requester) { this.items = items; this.requester = requester; } requestItems() { this.items.forEach((item) => { this.requester.requestItem(item); }); } } class InventoryRequesterV1 { constructor() { this.REQ_METHODS = ['HTTP']; } requestItem(item) { // ... } } class InventoryRequesterV2 { constructor() { this.REQ_METHODS = ['WS']; } requestItem(item) { // ... } } //      ,    //      ,     const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2()); inventoryTracker.requestItems(); 

Prefer classes (ES2015 / ES6) over simple functions (ES5)


With the help of classical (ES5) classes, it is difficult to implement readable inheritance, construction and definition of methods. If you need inheritance, do not hesitate to use (ES2015 / ES6) classes. However, give preference to small functions rather than classes, until there is a need for larger and more complex objects.

Poorly:

 const Animal = function(age) { if (!(this instanceof Animal)) { throw new Error('Instantiate Animal with `new`'); } this.age = age; }; Animal.prototype.move = function move() {}; const Mammal = function(age, furColor) { if (!(this instanceof Mammal)) { throw new Error('Instantiate Mammal with `new`'); } Animal.call(this, age); this.furColor = furColor; }; Mammal.prototype = Object.create(Animal.prototype); Mammal.prototype.constructor = Mammal; Mammal.prototype.liveBirth = function liveBirth() {}; const Human = function(age, furColor, languageSpoken) { if (!(this instanceof Human)) { throw new Error('Instantiate Human with `new`'); } Mammal.call(this, age, furColor); this.languageSpoken = languageSpoken; }; Human.prototype = Object.create(Mammal.prototype); Human.prototype.constructor = Human; Human.prototype.speak = function speak() {}; 

Good:

 class Animal { constructor(age) { this.age = age; } move() { /* ... */ } } class Mammal extends Animal { constructor(age, furColor) { super(age); this.furColor = furColor; } liveBirth() { /* ... */ } } class Human extends Mammal { constructor(age, furColor, languageSpoken) { super(age, furColor); this.languageSpoken = languageSpoken; } speak() { /* ... */ } } 

Use the chain method


This pattern is very useful in javascript. It is used by many libraries, such as jQuery and Lodash. This makes your code expressive and not verbose. Using this pattern, you will see how much your code will become cleaner. Just return this at the end of your methods and you can call them in a chain.

Poorly:

 class Car { constructor() { this.make = 'Honda'; this.model = 'Accord'; this.color = 'white'; } setMake(make) { this.make = make; } setModel(model) { this.model = model; } setColor(color) { this.color = color; } save() { console.log(this.make, this.model, this.color); } } const car = new Car(); car.setColor('pink'); car.setMake('Ford'); car.setModel('F-150'); car.save(); 

Good:

 class Car { constructor() { this.make = 'Honda'; this.model = 'Accord'; this.color = 'white'; } setMake(make) { this.make = make; //  this     return this; } setModel(model) { this.model = model; //  this     return this; } setColor(color) { this.color = color; //  this     return this; } save() { console.log(this.make, this.model, this.color); //  this     return this; } } const car = new Car() .setColor('pink') .setMake('Ford') .setModel('F-150') .save(); 

Present composition over inheritance


As stated in the book Design Patterns from the Gang of Four, preference should be given to composition over inheritance, where you can. There are many reasons to use inheritance and many reasons to use composition. If your brain instinctively sees inheritance, try presenting a solution to your problem using composition.

When to use inheritance? It depends on the specific problem. Here is a list of cases where inheritance makes more sense than composition:

  1. When inheritance is an “is” relationship, not “has” (Human-> Animal vs. User-> UserDetails)
  2. You can reuse the class (People can move like all animals).
  3. You want, by making changes to the parent class, to change the child classes
    (Change in calorie consumption of all animals when they move).

Poorly:

 class Employee { constructor(name, email) { this.name = name; this.email = email; } // ... } //     .      . class EmployeeTaxData extends Employee { constructor(ssn, salary) { super(); this.ssn = ssn; this.salary = salary; } // ... } 

Good:

 class EmployeeTaxData { constructor(ssn, salary) { this.ssn = ssn; this.salary = salary; } // ... } class Employee { constructor(name, email) { this.name = name; this.email = email; } setTaxData(ssn, salary) { this.taxData = new EmployeeTaxData(ssn, salary); } // ... } 

Source: https://habr.com/ru/post/319804/


All Articles