Seven Basic Principles of Object Oriented (javaScript)

Keywords: Javascript Programming Fragment less

Object-oriented programming has its own characteristics and principles. If you have some understanding of object-oriented, the three characteristics of object-oriented are encapsulation, inheritance and polymorphism. If you don't know much about these three concepts, please refer to them. Three basic features of object-oriented (javaScript)

Single responsibility

If there are too many methods in a class or a method when we write a program, it's a disaster for the readability of the code, for us. So in order to solve this problem, there is a single responsibility.

What is a single responsibility?

Single responsibility Also known as the single function principle, one of the five basic principles of object-oriented (SOLID). It specifies that a class should have only one cause for change. (Excerpt from Baidu Encyclopedia)

As mentioned above, there should be only one reason for a class to change. In other words, a class should have a single function and only do things related to it. In the process of class design, we should design according to our responsibilities, keep each other orthogonal, and do not interfere with each other.

Benefits of a single responsibility

  1. Class complexity is reduced, and what responsibilities to implement are clearly defined
  2. Readability improves, complexity decreases, and of course readability improves
  3. Maintainability improves, readability improves, and of course it's easier to maintain.
  4. Change is indispensable to reduce the risk caused by change. If the single responsibility of the interface is well done, one interface modification only affects the corresponding implementation class, but has no effect on other interfaces, which is very helpful to the expansibility and maintainability of the system.

Example

class ShoppinCar {
    constructor(){
        this.goods = [];
    }
    addGoods(good){
        this.goods = [good];
    }
    getGoodsList(){
        return this.goods;
    }
}
class Settlement {
    constructor(){
        this.result = 0; 
    }
    calculatePrice(list,key){
        let allPrice = 0;
        list.forEach((el) => {
            allPrice += el[key];
        })
        this.result = allPrice;
    }
    getAllPrice(){
        return this.result;
    }
}

With the above code, the ShoppinCar class has two methods, addGoods and getGoodsList, to add items and get lists of items, respectively. There are two methods in the Settlement class, calculatePrice and getAllPrice, which do the work of calculating the price and getting the total price respectively. ShoppinCar and Settlement are both doing their own thing. Adding goods and calculating prices are business-dependent, but they use two classes in the code, and they do their own thing. Any of these class changes will not change the other class.

Open and Close Principle

If the method exposed in a class is changed, it will have great consequences, which may lead to large-scale paralysis of other businesses that depend on the method and do not need to change. To solve this problem, you can write a separate method if it is interdependent with other methods in this class.

Solution:

  1. copy the dependent code into the new class.
  2. Refer to methods in old classes in new classes.

Neither approach is the best solution.

The first method leads to a lot of code duplication, and the second method leads to class interdependence.

What is the Open-Close Principle

Open and Close Principle "Objects in software (classes, modules, functions, etc.) should be open to extensions, but closed to modifications," which means that an entity is allowed to change its behavior without changing its source code. (Excerpt from Baidu Encyclopedia)

Open-close principle is open to extension and close to modification, which does not mean that no modification is made. The change of the underlying module must be coupled with the high-level module, otherwise it is an isolated and meaningless code fragment. The principle of opening and closing is one of the most basic principles. The other six principles are the concrete forms of the principle of opening and closing. They are the tools and methods to guide the design. The principle of opening and closing is the spiritual leader.

Benefits of Open-Close Principle

  1. Open-Close Principle for Unit Testing
  2. Open-close principle can improve reusability
  3. Open-close principle can improve maintainability
  4. Requirements of Object-Oriented Development

Example

class Drag {
    down(){
        //  ...
    }   
    move(){
        //  ...
        // There is no restriction on dragging. You can drag at will.
    }   
    up(){
        //  ...
    }  
}
class LimitDrag extends Drag {
    move(){
        //  ...
        //  Rewrite the method to restrict drag
    }
}

move method is rewritten in LimitDrag. If modified, it can satisfy two requirements, one is restricted drag, the other is unrestricted drag. Any change to the other can still work properly.

Richter substitution

When using other people's components, every developer only needs to know the exposed interface of the component, that is, the set of all its behaviors. As for how to realize the internal, it is impossible to know, and it is unnecessary to know. Therefore, for users, it can only achieve their expectations through interfaces. If the behavior provided by component interfaces does not match the expectations of users, errors will occur. The Richter substitution principle is to avoid inconsistent behavior between derived classes and classes in design.

What is Richter Replacement

Richter's replacement principle OCP, as the high-level principle of OO, advocates the use of "Abstraction" and "Polymorphism" to change the static structure into dynamic structure in the design and maintain the closeness of the design. "Abstraction" is the function provided by language. Polymorphism is realized by inheritance semantics. (Excerpt from Baidu Encyclopedia)

The Benefits of Richter Replacement

  1. Code sharing reduces the workload of creating classes. Each subclass has methods and attributes of its parent class.
  2. Improving code reuse
  3. Subclasses can resemble the parent class, but they are different from the parent class.
  4. Improving the extensibility of the code is enough to implement the method of the parent class. Many open source frameworks extend their interfaces by inheriting their parent classes.
  5. Enhancing openness of products or projects

Example

//  Abstract guns
class AbstractGun {
    shoot(){
        throw "Abstract methods cannot be called";
    }
}
//  Rifle
class Rifle extends AbstractGun {
    shoot(){
        console.log("Rifle Shooting...");
    }
}
//  Sniper rifle
class AUG extends Rifle {
    zoomOut(){
        console.log("Observed through a magnifying glass");
    }
    shoot(){
        console.log("AUG Shooting...");
    }
}
//  Rank-and-file soldiers
class Soldier {
    constructor(){
        this.gun = null;
    }
    setGun(gun){
        this.gun = gun;
    }
    killEnemy(){
        if(!this.gun){
            throw "I need a gun.";
            return;
        }
        console.log("The soldiers began to shoot....");
        this.gun.shoot();
    }
}
//  Sniper
class Snipper extends Soldier {
    killEnemy(aug){
        if(!this.gun){
            throw "I need a gun.";
            return;
        }
        this.gun.zoomOut();
        this.gun.shoot();
    }
}
let soldier = new Soldier();
soldier.setGun(new Rifle());
soldier.killEnemy();

let snipper = new Snipper();
//  Distribution of sniper guns
snipper.setGun(new AUG());
snipper.killEnemy();

snipper.setGun(new Rifle());
// snipper.killEnemy();  //  this.gun.zoomOut is not a function

From the above code, we can see that the relationship between the child class and the parent class, the child class method must be equal to or greater than the parent class method. The parent class that the child class can appear may not appear, but the child class can appear where the parent class appears.

Dependency Inversion Principle

If there are too many dependencies between methods and classes, there will be poor code readability and maintainability. The principle of inversion of dependence can solve these problems well.

What is dependency inversion

Dependence Inversion Principle Programs rely on abstract interfaces, not on concrete implementations. Simply put, it requires programming abstractions, not implementations, which reduces the coupling between customers and implementation modules. (Excerpt from Baidu Encyclopedia)

  1. High-level modules should not rely on low-level modules, and both should rely on their abstraction.
  2. Abstraction should not depend on details
  3. Details should depend on abstraction

Dependence on inverted benefits

  1. Specific implementation classes are isolated by relying on interfaces
  2. Changes in the lower level do not lead to changes in the higher level.
  3. It improves the fault tolerance, expansibility and maintainability of the code.

Example

//  Abstract guns
class AbstractGun {
    shoot(){
        throw "Abstract methods cannot be called";
    }
}
//  Rifle
class Rifle extends AbstractGun {
    shoot(){
        console.log("Rifle Shooting...");
    }
}
//  Sniper rifle
class AUG extends AbstractGun {
    shoot(){
        console.log("AUG Shooting...");
    }
}

As can be seen from the above code, shoot ing of rifles and sniper guns all depends on AbstractGun's Abstract guns, and the above programming satisfies the principle of dependence inversion.

Interface isolation

What is interface isolation

Interface isolation Clients should not rely on interfaces that they do not need; dependencies on one class to another should be based on the smallest interfaces. (Excerpt from Baidu Encyclopedia)

The principle of interface isolation and the principle of single responsibility have different perspectives. The principle of single responsibility requires that the responsibilities of classes and interfaces are single and focus on responsibilities, which is the division of business logic. The principle of interface isolation requires as few methods of interface as possible.

Interface isolation benefits

  1. Avoiding interface contamination
  2. Increasing flexibility
  3. Providing customized services
  4. Achieving high cohesion

Example

function mix(...mixins) {
  class Mix {}
  for (let mixin of mixins) {
    copyProperties(Mix, mixin);
    copyProperties(Mix.prototype, mixin.prototype);
  }
  return Mix;
}
function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if ( key !== "constructor"&& key !== "prototype"&& key !== "name") {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}
class Behavior {
    eat(){
        throw "Abstract methods cannot be used";
    }   
    call(){
        throw "Abstract methods cannot be used";
    }
}
class Action {
    climbTree(){
        throw "Abstract methods cannot be used";
    }
}
class Dog extends Behavior{
    eat(food){
        console.log(`The dog is eating. ${food}`);
    }
    hungry(){
        console.log("Wang Wang Wang,I am hungry")
    }
}
const CatMin = mix(Behavior,Action);
class Cat extends CatMin{
    eat(food){
        console.log(`The cat is eating. ${food}`);
    }
    hungry(){
        console.log("cat,I am hungry")
    }
    climbTree(){
        console.log("It's fun to climb trees.~")
    }
}
let dog = new Dog();
dog.eat("Bone");
dog.hungry();
let cat = new Cat();
cat.eat("fish");
cat.hungry();
cat.climbTree();

Everyone must analyze the above code carefully. There are two abstract classes, which correspond to different behaviors. Cat and Dog classes have the same behavior, but Cat has its own separate behavior. Cat inherits its method by using abstraction (i.e. interface), and uses interface isolation to make it complete its own work.

LoD

LoD Least Knowledge Principle (LKP) means that an object should know as little as possible about other objects and not talk to strangers. LoD. (excerpted from Baidu Encyclopedia)

The idea of Dimiter's rule is decoupling between classes and weak coupling. Only after weak coupling can the reuse rate of classes be improved. A class should have the least knowledge of other objects. Generally speaking, the less a class knows about the class it depends on, the better. Because the closer the relationship between classes and classes, the greater the coupling degree. When one class changes, the greater the impact on the other class.

Benefits of Dimiter's Law

  1. Reducing coupling between objects

Example

class ISystem {
    close(){
        throw "Abstract methods cannot be used";
    }
}
class System extends ISystem{
    saveCurrentTask(){
        console.log("saveCurrentTask")
    }
    closeService(){
        console.log("closeService")
    }
    closeScreen(){
        console.log("closeScreen")
    }
    closePower(){
        console.log("closePower")
    }
    close(){
        this.saveCurrentTask();
        this.closeService();
        this.closeScreen();
        this.closePower();
    }
}
class IContainer{
    sendCloseCommand(){
        throw "Abstract methods cannot be used";
    }
}
class Container extends IContainer{
    constructor(){
        super()
        this.system = new System();
    }
    sendCloseCommand(){
        this.system.close();
    }
}
class Person extends IContainer{
    constructor(){
        super();
        this.container = new Container();
    }
    clickCloseButton(){
       this.container.sendCloseCommand();
    }
}
let person = new Person();
person.clickCloseButton();

In the code above, Container acts as a medium. Its calling class does not know how to implement it internally. The user triggers the button. Container notifies the computer of the message and the computer executes the corresponding command.

Combination/aggregation Reuse Principle

Aggregation denotes a weak'ownership'relationship, which shows that A objects can contain B objects, but B objects are not part of A objects.

Composition is a strong'possession'relationship, which reflects the strict relationship between part and whole, and the life cycle of part and whole is the same.

Composition/aggregation: By obtaining references to other objects, dynamically defined at run time, i.e. preserving properties of other objects in an object, this approach requires that the object has a well-defined interface, and that interface does not change very often, and the object can only be accessed through the interface, so that we can and It does not destroy encapsulation, so as long as the type is identical, the runtime can also replace another object with one object.

Composition/aggregation of preferential objects will help you keep each class encapsulated and focused on a single task, so that class and class inheritance levels remain small and unlikely to grow into uncontrollable behemoths.

Benefits of Composition/Polymerization Reuse Principle

  1. New implementations are easier because most of the functions of superclasses can be automatically entered into subclasses through inheritance relationships.
  2. It is easier to modify or extend the implementation inherited from it.

Example

function mix(...mixins) {
  class Mix {}
  for (let mixin of mixins) {
    copyProperties(Mix, mixin);
    copyProperties(Mix.prototype, mixin.prototype);
  }
  return Mix;
}
function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if ( key !== "constructor"&& key !== "prototype"&& key !== "name") {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}
class Savings {
    saveMoney(){
        console.log("Save money");
    }
    withdrawMoney(){
        console.log("Withdraw money");
    }
}
class Credit {
    overdraft(){
        console.log("Make an overdraft")
    }
}
const CarMin = mix(Savings,Credit);
class UserCar extends CarMin {
    constructor(num,carUserName){
        super();
        console.log()
        this.carNum = num;
        this.carUserName = carUserName;
    }
    getCarNum(){
        return this.carNum;
    }
    getCarUserName(){
        return this.carUserName;
    }
}
let myCar = new UserCar(123456798,"Aaron");
console.log(myCar.getCarNum());
console.log(myCar.getCarUserName());
myCar.saveMoney();
myCar.withdrawMoney();
myCar.overdraft();

summary

These principles are fully embodied in the design pattern, which implements these principles, thus achieving code reuse and enhancing system scalability. So design patterns are regarded as classics by many people. We can slowly understand these design principles by studying design patterns.

Posted by wshost on Sat, 07 Sep 2019 05:33:03 -0700