Liskov replacement principles, interfaces, contracts, types, and natural correspondence (1)
Smilemac
The meaning and usage of public inheritance often makes people confused, and many people even think that the public inherit should be prohibited, and this paper tries to discuss this issue from the Liskov replacement principle (LSP, Lisk Substitude Princi).
Liskov Replacement Principle (LSP, Liskov Substitution Principle) is expressed in the definition of subtype:
If each object O1 of the type S has a type T object O2, the program P defined for any type T is replaced with O1, and the behavior of P remains the same, then S is T is one. Subtype.
We know that there is a relationship of ISA in C , A is A b, that is, a subtype of B, that is, in C publicly derived expression type. Then the Liskov replacement principle expresses this in C , if the subclass is a publicly derived, then all subclats of the parent class or parent class, the object they have formed should be able to replace each other, after replacing, replace The context remains logically complete and consistent.
Under rough, this principle is obvious, but if necessary, it will find that a rule so is so difficult to guarantee. A well-known example is: If there is a rectangle and a square in your program, you may want to be implemented as a square of the square as a rectangle, because our mathematical knowledge tells us that the square is also a rectangle, but if You think so, you have violated the LSP, because your program is assumed when using a rectangular object, but if it is changed to a square, because the length of the square is related, the program is initially The assumption cannot be established, and the context logic of the natural call program cannot be guaranteed.
The LSP is closely related to Design by Contract, and according to the expression of Bertrand Meyer, when a method is defined in the subclass, the front condition must be weaker than the parent class, while the post-condition must be stronger than the parent class. Combined with the LSP, it means that only the pre-condition of the subclass is a supercoming of the parent class, and the post condition is a subset of the parent class, the subclass is a subtype of the parent class type. Here, the so-called subset supercharge is from the concept of the concept.
Consider a class of pre-conditions should include some of them:
1. status set
2. behavior set
3. " The status set of the object relying before the behavior
4. 5. External environments depending before each behavior.
And the rear conditions of a class should include some of the following:
1. Output status set after each behavior
2. Output Data Set after each behavior
3. External environment after each behavior.
Formal representation is for a five-component σ = (S, B, I, O, C), where:
S - State Set
B - Behavior Set
I - Input Data Set
O - Output Data Set
C - context of outside
Contract is such a relationship R
R = σ x σ
And σ is a class.
In the first square example, after the action of the side length in the square, the object's feature set is not the subset of the output status set of the parent-rectangular homogenous action, so it violates the LSP, but also violates its and the calling environment. Between Contract.
We often say that if you want to interface, what is the interface of the class? The interface of the class is the parallel set of the front and rear conditions of the class, in other words, the interface and contract is actually a concept. Here, the interface is a generalized concept, its role is to express a type, therefore, the interface is not necessarily a base class, and it is not necessarily an abstract class. It can be a specific class or have its own implementation code. .
In this way, LSP, Design By Contract, and various design methods such as the interface are uniform. On this basis, we found that types, contracts, and interfaces are actually three equivalents. They all express the semantics of the concept of different levels. Types are suitable for definition of concepts and think, contract systemization define types, while interfaces are decomposed into different subsets. When designing, we have a variety of concepts defined as types, and then think about these types of relationships on the basis of the contract, and the types are further refined, and then the behavior status is divided in the way. An example of the square above:
Suppose the problem domain is like this: In a function, it is incorporated into a square or rectangle, then enlarges 1 times, calculates the output. The design process is as follows: (just for the problem, do not make a complete design)
Types are obviously two, rectangular and square.
Rectangular contracts are as follows:
1.
b) program
(1) Enlarge all graphs in the container by 1x.
(2) The calculation area returns.
2. State set: (long, width)
3. Behavioral Set: Initialization status, zoom (magnification means that the shape remains unchanged).
4. Pre-condition:
a) initialization status:
(1) input length or wide;
(2) status can be modified
b) zoom
(1) Enter the magnification
(2) status can be modified
5. Back conditions:
a) initialization status:
(1) status modification
b) zoom
(1) State zoom specified in the specified multiple (2) The proportion of long and wide is unchanged.
The contract is as follows:
1. External environment
c) a rectangular or square container
d) program
(1) Create a number of rectangles and squares put into the container;
(2) Enlarge all the graphs in the container by double.
2. State Set: (long, width) / or side length.
3. Behavioral Set: Initialization status, zoom
4. Pre-condition:
e) initialization status:
(1) input length or wide;
(2) status can be modified
f) zoom
(1) Enter the magnification
(2) status can be modified
5. Back conditions:
g) Initialization status: (1) Status modification
(2) long and width always keep equal.
h) zoom
(1) status zoom specified multiple
(2) The proportion of long and wide is unchanged.
It can be seen that other contracts are compatible with other contracts, in addition to the "initialization status", other contracts, where the active behavior set is a rectangular supercoming. That is, when the operation is not performed, the square is a rectangle. Take a closer look, we can give more accurate definitions, that is, the square is a rectangle of a given shape. So, we can have the following design
Class fixed_rentangle {
protected:
Float m_flength;
FLOAT M_FWIFTH;
PUBLIC:
Fixed_rentangle (Float FL, Float FW)
: M_Flength (FL)
, M_FWIFTH (FW)
{};
Void zoom (Float F)
{
m_flength = m_flength * f;
m_fwifth = m_fwifth * f;
}
}
Class Rentangle: Public Fixed_rentangle {
PUBLIC:
Rentangle (Float FL, Float FW)
: Fixed_rentangle (FL, FW)
{}
}
Class Square: public fixed_rentangle {
PUBLIC:
Square (Float F)
: Fixed_rentangle (f, f)
{}
}
int main ()
{
/ / In order to simplify, it is assumed that only one square and a rectangle are created.
Rentangle_fixed_shape * array_rent [2];
Array_rent [0] = New Square (10);
Array_rent [0] = New Rentangle (15, 10);
For (int i = 0; i <2; i ) array_rent [i] -> zoom (2);
}
This example is just to illustrate the problem, so it is relatively simple, but another point I want to say is that the above analysis is relatively stable and the relationship between the two types of squares is relatively stable. Because it is obtained. The relationship between the relationship is more in line with the essence of things, and this is a design method I will refer to it, and the article is Tiancheng, and the hand is even. Object-oriented benefits do not need you to design, you only need to know, try to understand the relationship between an object in the problem domain, and then reflect them in the program, and this article explains how to understand the object Blood relationship.
LSP
Another design philosophy hidden inheritance is an object-oriented hierarchy model: different levels of modules in the program should see the type of different levels, the higher the data of the data processed by the higher the level, the higher the abstraction of the data processed.
LSP
Very complete reflects this idea.