In-depth understanding of the JavaScript series (8): S.O.L.I.D Five Principles of the Richter replacement principle LSP

Source: Internet
Author: User


In this chapter, we are going to explain the 3rd S.O.L.I.D of the Five principles of JavaScript, the Richter replacement principle LSP (the Liskov Substitution Principle).

English Original:

The opening and closing principle is described as:

subtypes must is substitutable for their base types. a derived type must be able to replace its base type. 

In object-oriented programming, inheritance provides a mechanism for subclasses and share the code of the base class, which is implemented by encapsulating common data and behavior in the base type, and then declaring more detailed subtypes, in order to apply the Richter substitution principle, the inheriting subtype needs to be semantically equivalent to the expected behavior in the matrix type.

For a better understanding, please refer to the following code:

functionVehicle (my) {varmy = My | |    {};    my.speed = 0; My.running =false; This. Speed =function() {returnMy.speed; }; This. Start =function() {my.running =true; }; This. Stop =function() {my.running =false; }; This. Accelerate =function() {my.speed++; }; This. decelerate =function() {my.speed--; }, This. State =function() {if(!my.running) {return"Parked"; }Else if(my.running && my.speed) {return"Moving"; }Else if(my.running) {return"Idle"; }    };}

The code above defines a vehicle function whose constructors provide some basic operations for the vehicle object, and let's consider if the current function is currently running on the service customer's product environment, if you need to add a new constructor now to implement the vehicle to speed up the move. After thinking, we wrote the following code:

function  Fastvehicle (my) {var  my = My | |    {}; var  that = new  Vehicle (my);    That.accelerate = function  () {my.speed + = 3;    }; return  that;} 

In the browser console we have all tested, all the functions are our expectations, no problem, fastvehicle speed increased 3 times times faster, and inherited his method is also in accordance with our expected work. After that, we started to deploy this new version of the class library to the product environment, but we received a new constructor that caused the existing code to not support execution, and the following code snippet revealed the problem:

var  Maneuver = function  (vehicle) {Write (    Vehicle.state ());    Vehicle.start ();    Write (Vehicle.state ());    Vehicle.accelerate ();    Write (Vehicle.state ());    Write (Vehicle.speed ());    Vehicle.decelerate ();    Write (Vehicle.speed ()); if  (Vehicle.state ()! = "Idle")    {throw  "The vehicle is still moving!";    } vehicle.stop (); Write (Vehicle.state ());}; 

According to the code above, we see that the exception thrown was "the vehicle is still moving!", because the author of this code always thought that the number of acceleration (accelerate) and deceleration (decelerate) was the same. But Fastvehicle's code and vehicle's code are not completely replaceable. As a result, fastvehicle violated the Richter scale replacement principle.

At this point, you may think: "However, the client cannot always assume that vehicle is doing this in accordance with this rule", the hindrance of the Richter replacement principle (LSP) (Translator note: Code that hinders the implementation of LSP) is not based on what we think the inheriting subclass should be in the behavior to ensure that the code is updated, But whether such updates can be implemented in the current expectation.

The above code case, to solve this incompatibility problem needs to be in the Vehicle class library or the client call code to do a little redesign, or both to change.

Reduce LSP obstruction

So, how do we avoid LSP obstruction? Unfortunately, it is not always possible to do so. We have a few strategies here that we deal with this matter.

Contract (contracts)

One strategy for dealing with LSP excesses is the use of contracts, which have 2 forms: Execution Manual (executable specifications) and error handling, and in the execution manual, a detailed class library contract also includes a set of automated tests, and error handling is handled directly in the code. , such as pre-conditions, post-conditions, constant checks, and so on, can be viewed from the Bertrand Miller's masterpiece, "Contract design". Although automated testing and contract design is not within the scope of this text, I recommend the following when we use it:

    1. Check the use of test-driven development (Test-driven development) to guide your code design
    2. Use contract design techniques when designing reusable class libraries

For your own code to maintain and implement, the use of contract design tends to add a lot of unnecessary code, if you want to control the input, add testing is very necessary, if you are a class library author, using contract design, you should pay attention to the incorrect use of the method and let your users make it as a test tool.

Avoid inheritance

Another test to avoid LSP obstruction is: if possible, try not to inherit, in the Gamma masterpiece "Design patterns–elements of reusable object-orineted software", we can see the following suggestions:

Favor Object composition over class inheritance try to use object combinations instead of class inheritance

Some books discuss that the only effect of composition over inheritance is a static type, a class-based language (for example, a behavior that can be changed at run time), and a JavaScript-related problem is coupling, and when inheritance is used, the inherited subtypes and their base types are coupled together, That is, the change in type affects the inherited subtype. The combination tends to be more object-oriented and more prone to static and dynamic language-language maintenance.

Behavior-related, not inheritance

So far, we have discussed the principle of the Richter substitution in the context of inheritance, indicating the object-oriented reality of JavaScript. However, the essence of the Richter replacement principle (LSP) is not really about inheritance, but behavior compatibility. JavaScript is a dynamic language, and the contractual behavior of an object is not determined by the type of the object, but by the function the object expects. The initial conception of the Richter replacement principle is a guideline for inheritance, equivalent to an implicit interface in object design.

For example, let's take a look at Robert C. Martin's masterpiece, "Agile Software development principles, patterns, and practices" in a rectangular type:

Rectangle Example

Consider that we have a program that uses a rectangle object such as the following:

var rectangle = {    length:0,    width:0};

Later, the program needs a square, because the square is a long (length) and width (width) are the same as a special rectangle, so we think to create a square instead of a rectangle. We have added the length and width properties to match the declaration of the rectangle, but we feel that using the getters/setters of the property generally allows us to keep length and width in sync, ensuring that a square is declared:

varSquare = {};(function() {varLength = 0, width = 0;//Note that the DefineProperty mode is a new feature of version 262-5Object.defineproperty (square, "length", {get:function() {returnLength }, set:function(value)    {length = width = value;}    }); Object.defineproperty (square, "width", {get:function() {returnWidth }, set:function(value)    {length = width = value;} });}) ();

Unfortunately, when we use a square instead of a rectangle to execute the code, we find the problem, one of the ways to calculate the rectangular area is as follows:

var function (Rectangle) {    rectangle.length = 3;    Rectangle.width = 4;    Write (rectangle.length);    Write (rectangle.width);    Write (Rectangle.length * rectangle.width);};

When the method is called, the result is 16, not the expected 12, our square square object violates the LSP principle, and the length and width properties of square imply that it is not compatible with rectangle 100%, but we are not always so explicit. To solve this problem, we can redesign a Shape object to implement the program, according to the concept of polygons, we declare rectangle and square,relevant. Anyway, our goal is to say that the Richter substitution principle is not just inheritance, but any method (where the behavior can be otherwise).


The Richter substitution principle (LSP) does not mean an inherited relationship, but any method (as long as the behavior of the method can be experienced by another behavior).

Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.

In-depth understanding of the JavaScript series (8): S.O.L.I.D Five Principles of the Richter replacement principle LSP

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.