write(vehicle.state()); >write(vehicle.speed());
vehicle.decelerate(); if (vehicle.state() != "idle") { throw "車両はまだ動いています!" vehicle.stop(); ;
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).