In this chapter, we will explain the 3rd Implementation of the five principles S.O.L. I. D In the JavaScript language, and the LSP principle LSP (TheLiskovSubstitutionPrinciple ). Preface
In this chapter, we will explain 3rd of The Five Principles of S.O.L. I. D In JavaScript language implementation, and The Principle of LSP (The Liskov Substitution Principle ).
Http://freshbrewedcode.com/derekgreer/2011/12/31/solid-javascript-the-liskov-substitution-principle/.
The opening/closing principle is described as follows:
Subtypes must be substitutable for their base types.
The derived type must replace its base type.
In object-oriented programming, inheritance provides a mechanism for sub-classes and sharing the code of the base class, which is achieved by encapsulating common data and behavior in the base type, then we have declared more detailed child types with the types. In order to apply the lining replacement principle, the inheritance child types must be semantically equivalent to the expected behavior in the base types.
For better understanding, see the following code:
The Code is as follows:
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 ";
}
};
}
In the above Code, we define a Vehicle function. Its constructor provides some basic operations for the vehicle object. Let's think if the current function is currently running in the product environment of the service customer, if you need to add a new constructor to accelerate the moving vehicle. After thinking, we wrote the following code:
The Code is as follows:
Function FastVehicle (my ){
Var my = my | {};
Var that = new Vehicle (my );
That. accelerate = function (){
My. speed + = 3;
};
Return that;
}
We have tested all the functions in the browser console. All the functions are our expectation and there is no problem. The FastVehicle speed is increased by three times, and the method to inherit from it is also based on our expectation. After that, we began to deploy this new version of the Class Library to the product environment, but we received a new constructor, which made the existing code unable to support execution. The following code segment reveals this problem:
The Code is as follows:
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 ());
};
Based on The above code, we can see that The thrown exception is "The vehicle is still moving !", This is because the author who wrote this code always thinks that the numbers of accelerate and decelerate are the same. However, the FastVehicle code and Vehicle Code cannot be completely replaced. Therefore, FastVehicle violates the Lee's replacement principle.
At this point, you may think: "However, the client cannot always assume that vehicle is based on such a rule: is to impede the implementation of LSP code) it is not based on what we think to inherit the subclass should ensure that the Code is updated in the behavior, but whether such an update can be implemented in the current expectation.
To solve this incompatibility problem, you need to re-design the vehicle class library or the client call code or change both of them.
Reduce LSP obstruction
So, how can we avoid LSP interference? Unfortunately, it is not always possible. Here we have several strategies to deal with this issue.
Contract (Contracts)
One strategy to deal with LSP's excessive obstruction is to use a contract. The contract list has two forms: executable specifications and error handling. In the statement of execution, the contract of a detailed class library also includes a set of automated tests, and error handling is directly handled in the Code, such as in the pre-condition, post-condition, constant check, etc, you can view this technology from Bertrand Miller's masterpiece contract design. Although automated testing and contract design are not within the scope of this article, we recommend the following when using them:
Check Test-Driven Development to guide your code design.
Contract Design technology is available when reusable class libraries are designed
For the code you want to maintain and implement, the use of contract design tends to add a lot of unnecessary code. If you want to control the input, it is necessary to add a test, if you are the author of the class library and use the contract design, you should pay attention to incorrect usage and make your users use it as a test tool.
Avoid inheritance
Another test that prevents LSP interference is: if possible, do not inherit from it. In the Design Patterns-Elements of Reusable Object-Orineted Software masterpiece of Gamma, we can see the following suggestions:
Favor object composition over class inheritance
Try to use object combination instead of class inheritance
Some books discuss the unique role of a combination that is better than inheritance in static types. A class-based language (for example, it can change behavior at runtime) is related to JavaScript coupling, when inheritance is used, the inheritance subtypes are coupled with their basic types, that is, the change of the inheritance subtypes will affect the inheritance subtypes. The combination tends to make objects smaller, making it easier to maintain static and dynamic language.
Behavior-related, not inheritance
Up to now, we have discussed the Lee replacement principle, including the inherited context, to indicate the object-oriented JavaScript. However, the essence of LSP is not really related to inheritance, but behavior compatibility. JavaScript is a dynamic language. The contractual behavior of an object is not determined by the object type, but by the expected functions of the object. The initial conception of the Rys replacement principle is an inherited principle guide, which is equivalent to the implicit interface in Object design.
For example, let's take a look at a rectangular type in Robert C. Martin's masterpiece agile software development principles, models, and practices:
Rectangle example
Consider that we have a program that uses the following rectangular object:
The Code is as follows:
Var rectangle = {
Length: 0,
Width: 0
};
[Code]
After that, the program needs a square. Because a square is a special rectangle with the same length and width, we feel that creating a square replaces a rectangle. We have added the length and width attributes to match the rectangle declaration, but we think that using the getters/setters attribute, we can keep the length and width in sync to ensure that the Declaration is a square:
[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, when we use a square instead of a rectangle to execute code, we find the problem. One of the methods for calculating the rectangle area is as follows:
The Code 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 object violates the LSP principle, the length and width attributes of square imply that it is not compatible with the rectangle 100%, but we do not always make such a clear suggestion. To solve this problem, we can redesign a shape object to implement the Program. Based on the polygon concept, we declare rectangle, square, and relevant. In any case, our purpose is to say that the principle of "Let's Replace" is not just inheritance, but any method (the behavior can be another action ).
Summary
The LSP expression does not mean an inherited relationship, but any method (as long as the behavior of this method can understand another behavior ).