Preface
What we are going to explain in this chapter is the third of the five principles of S.O.L.I.D JavaScript language implementation, Liskov Substitution Principle LSP (The Liskov Substitution Principle).
English original text: http://freshbrewedcode.com/derekgreer/2011/12/31/solid-javascript-the-liskov-substitution-principle/
Copy code
Description of the opening and closing principle Is:
Subtypes must be substitutable for their base types.
A derived type must be substitutable for its base type.
Copy code
In object-oriented programming, inheritance provides a mechanism for subclasses to share the code of a base class. This is achieved by encapsulating common data and behavior in the base type, and then the type To declare a more detailed subtype, in order to apply the Liskov Substitution Principle, the inherited subtype needs to be semantically equivalent to the desired behavior in the base type.
For better understanding, please refer to the following code:
function Vehicle(my) {
var my = my || {};
my.speed = 0;
my.running = false;
this. speed = function() {
return my.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";
}
};
}
We define a Vehicle function in the above code, and its constructor provides some basic operations for the vehicle object. Let’s think about what if the current function is currently running in the service In the customer's product environment, if it is now necessary to add a new constructor to speed up the movement of the vehicle. After thinking about it, we wrote the following code:
function FastVehicle (my) {
var my = my || {};
var that = new Vehicle(my);
that.accelerate = function() {
my.speed = 3 ;
};
return that;
}
We have tested it in the browser console, all functions are as expected, no problem, FastVehicle The speed is increased by 3 times, and the method inherited from him works as we expected. After that, we started to deploy this new version of the class library to the production environment, but we received a new constructor that caused the existing code to be unable to support execution. The following code snippet reveals this 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 above code, we see that the exception thrown is "The vehicle is still moving!" This is because the author who wrote this code always believed that the numbers of acceleration and deceleration are the same. . But FastVehicle code and Vehicle code are not completely replaceable. Therefore, FastVehicle violates the Liskov substitution principle.
At this point, you may be thinking: "However, the client cannot always assume that vehicles are done according to such rules", the Liskov Substitution Principle (LSP) hinders (Translator's Note: That is Code that prevents implementation of LSP) is not based on what we think inherited subclasses should do to ensure updated code in behavior, but whether such updates can be implemented within current expectations.
In the case of the above code, to solve this incompatibility problem requires a little redesign in the vehicle class library or client calling code, or both.
Reduce LSP obstruction
So, how do we avoid LSP obstruction? Unfortunately, this is not always possible. We have a few strategies here for how we deal with this.
Contracts
One strategy to deal with LSP excessive obstruction is to use contracts. Contract lists come in two forms: executable specifications and error handling. In the executable specifications, a detailed class library The contract also includes a set of automated tests, and error handling is handled directly in the code, such as in preconditions, postconditions, constant checks, etc. You can view this technology from Bertrand Miller's masterpiece "Contract Design". Although automated testing and contract design are beyond the scope of this article, I still recommend the following when we use them:
Check out using Test-Driven Development to guide the design of your code
Feel free to use contract design techniques when designing reusable class libraries
For the code you want to maintain and implement yourself, using contract design tends to add a lot of unnecessary code. If you want to control input, adding tests is very useful. It is essential that, if you are a class library author using contract design, you should be aware of incorrect usage and allow your users to use it as a testing tool.
Avoid inheritance
Another test to avoid LSP hindrance is: if possible, try not to inherit. In Gamma's 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 composition over class inheritance
Copy code
Some books discuss that the only role that composition is better than inheritance is static typing. For class-based languages (i.e., behavior that can be changed at runtime), one issue related to JavaScript is coupling. When using inheritance, inherited subtypes are coupled to their base types, which means that changes to the type will Affects inherited subtypes. Composition tends to make objects smaller and easier to maintain in both static and dynamic languages.
It’s about behavior, not inheritance
So far, we’ve discussed the Liskov substitution principle with inheritance context, which dictates JavaScript’s object-oriented reality. However, the essence of Liskov Substitution Principle (LSP) is not really about inheritance, but about behavioral compatibility. JavaScript is a dynamic language. The contract behavior of an object is not determined by the type of the object, but by the expected function of the object. The Liskov substitution principle was originally conceived as a principle guide for inheritance, equivalent to the implicit interface in object design.
For example, let’s take a look at a rectangle type from Robert C. Martin’s masterpiece "Agile Software Development Principles, Patterns, and Practices":
Rectangle Example
Consider us There is a program that uses a rectangular object like the following:
var rectangle = {
length: 0,
width: 0
};
[code]
After that, the program needs a square, because a square is a length (length) and a width ( width) are the same special rectangle, so we think of creating a square instead of the rectangle. We added length and width properties to match the declaration of a rectangle, but we feel that using the property's getters/setters we can synchronize the length and width saves to ensure that a square is declared:
[code]
var square = {};
(function() {
var length = 0, width = 0;
// Note that the defineProperty method is a new feature of version 262-5
Object.defineProperty(square, " length", {
get: function() { return length; },
set: function(value) { length = width = value; }
});
Object.defineProperty(square, "width", {
get: function() { return width; },
set: function(value) { length = width = value; }
});
})();
Unfortunately, we found a problem when we used a square instead of a rectangle to execute the code. One of the methods to calculate the area of a rectangle is as follows:
var g = function(rectangle) {
rectangle.length = 3;
rectangle.width = 4;
write(rectangle .length);
write(rectangle.width);
write(rectangle.length * rectangle.width);
};
When this method is called, the result is 16 instead of the expected 12. Our square square object violates the LSP principle. The length and width properties of square imply that it is not 100% compatible with rectangles, but we do not always imply this explicitly. To solve this problem, we can redesign a shape object to implement the program. Based on the concept of polygon, we declare rectangle and square, relevant. Anyway, our purpose is to say that the Liskov Substitution Principle is not just inheritance, but any method (in which the behavior can be another behavior).
Summary
The Liskov Substitution Principle (LSP) expresses not the inheritance relationship, but any method (as long as the behavior of the method can reflect the behavior of another).