Inheritance is a double-edged sword.
Through the previous sections, we should have a better understanding of inheritance, but before we say that inheritance is actually a double-edged sword, why do you say so? On the one hand, inheritance is very powerful, on the other hand because of the destructive power of inheritance is also very strong.
The power of inheritance is relatively easy to understand, embodied in:
- Subclasses can reuse the parent class code, do not write any code can have the properties and functions of the parent class, but only need to increase the unique properties and behavior.
- Subclasses can override the behavior of the parent class, and can also be handled uniformly by polymorphic implementations.
- By adding properties and behaviors to the parent class, you can automatically add properties and behaviors to all subclasses.
Inheritance is widely used in various Java APIs, frameworks, and class libraries, on the one hand, they use inheritance extensively, on the other hand, they design good framework structure, provide a lot of base class and basic common code. Users can use inheritance, rewrite the appropriate method for customization, it is easy and convenient to implement powerful features.
But why is inheritance destructive? The main reason is that inheritance can destroy encapsulation, and encapsulation can be said to be the first principle of programming, on the other hand, inheritance may not reflect the "is-a" relationship. Let us explain in detail below.
Inheritance Destruction Encapsulation
What is encapsulation? encapsulation is the hiding of implementation details. Users only need to focus on how to use it, without having to focus on how the interior is implemented. Implementation details can be modified at any time without affecting the user. Functions are encapsulated, and classes are also encapsulated. Through encapsulation, the problem can be considered and solved at a higher level. It can be said that encapsulation is the first principle of programming, there is no encapsulation, the code is everywhere the implementation of the details of the dependency, the construction and maintenance of complex programs is unimaginable.
Inheritance can break encapsulation because there may be dependencies between the child class and the parent class for implementation details. subclasses often have to focus on the implementation details of the parent class when inheriting the parent class, while the parent class modifies its internal implementation, and if the subclass is not considered, it often affects subclasses.
We have some examples to illustrate. These examples are mainly used for demonstration purposes, and can basically ignore their practical significance.
How the package was destroyed
Let's look at a simple example, which is the base class code:
Public classBase {Private Static Final intMax_num = 1000; Private int[] arr =New int[Max_num]; Private intcount; Public voidAddintNumber ) { if(count<max_num) {Arr[count++] =Number ; } } Public voidAddAll (int[] numbers) { for(intnum:numbers) {Add (num); } }}
Base provides two methods add and addall to add input numbers to an internal array. For the user, add and AddAll is able to add numbers, specifically how to add, should not care.
Here is the subclass code:
Public classChildextendsBase {Private Longsum; @Override Public voidAddintNumber ) { Super. Add (number); Sum+=Number ; } @Override Public voidAddAll (int[] numbers) { Super. AddAll (numbers); for(inti=0;i<numbers.length;i++) {sum+=Numbers[i]; } } Public Longgetsum () {returnsum; }}
Subclasses override the base class's add and AddAll methods, summarize numbers while adding numbers, store the sum of numbers into the instance variable, and provide a method getsum get the value of sum.
The code for using child is as follows:
Public Static void Main (string[] args) { new Child (); C.addall (newint[]{1,2,3}); System.out.println (C.getsum ());}
Using AddAll to add the 1+2+3=6, the expected output is the actual output?
12
The actual output is 12. Why is it? Viewing the code is not difficult to see, the same number was summed two times. The AddAll method of the subclass first invokes the AddAll method of the parent class, and the AddAll method of the parent class is added by the Add method, and because of the dynamic binding, the subclass's Add method executes, and the add of the subclass does the summary operation.
As you can see, if the subclass does not know the implementation details of the base class method, it cannot be extended correctly. knowing the error, now we modify the subclass implementation, modifying the AddAll method to:
@Override Public void addall (int[] numbers) { super. AddAll (numbers);}
In other words, the AddAll method no longer repeats the summary. In this case, the program can output the correct result 6.
However, base class base decided to modify the implementation of the AddAll method to the following code:
Public void addall (int[] numbers) { for(int num:numbers) { if (count<max_num) { Arr[count+ +] = NUM ; } }}
In other words, it is no longer added by calling the Add method, which is the implementation detail of the base class. However, after modifying the inner details of the base class, the program using the subclass above is wrong , and the output is changed from the correct value of 6 to 0.
From this example, it can be seen that the child class and the parent class is a detail dependent, the subclass extends the parent class, just know what the parent class can do is not enough, but also need to know how the parent class is done, and the implementation details of the parent class can not be arbitrarily modified, otherwise it may affect the subclass.
More specifically, subclasses need to know the dependencies between the overridden methods of the parent class, in the above example, the relationship between the Add and AddAll methods , and this dependency, the parent class cannot be arbitrarily changed.
But even if the dependency does not change, the encapsulation can be destroyed.
In the above example, we first change the AddAll method back, this time, we add a method in base class base clear, the function of this method is to empty all the added numbers, the code is as follows:
Public void Clear () { for (int i=0;i<count;i++) { arr[i]=0; } = 0;}
The base class adds a method that does not need to tell the subclass that the child class does not know that the base class has added such a method, but because of the inheritance relationship, the children class automatically has such a method! Therefore, the child class may use the child class in this way:
Public Static void Main (string[] args) { new Child (); C.addall (newint[]{1,2,3}); C.clear (); C.addall (newint[]{1,2,3}); System.out.println (C.getsum ());}
Add once, then call clear to clear, add again, and finally output sum, the expected result is 6, but the actual output? is 12. Why is it? Because child does not rewrite the clear method, it needs to add the following code to reset its internal sum value:
@Override Public void Clear () { Super. Clear (); this. sum = 0;}
Above, it can be seen that the parent class cannot arbitrarily increase the public method, because adding to the parent class is adding to all subclasses, and the subclass may have to override the method to ensure the correctness of the method.
To summarize, for subclasses, there is no security through inheritance, the parent class modifies the internal implementation details, its functionality can be destroyed, and for the base class, the subclass inherits and overrides the method, and may lose the freedom to modify the internal implementation arbitrarily.
Inheritance does not reflect "is-a" relationships
The inheritance relationship is designed to reflect the "is-a" relationship, the subclass is one of the parent classes, the subclass object belongs to the parent class, and the properties and behaviors of the parent class must also apply to the subclass. Just like oranges are fruits, fruit has properties and behaviors, and oranges are bound to have them.
But in reality, it is difficult to design an inheritance relationship that conforms to the "is-a" relationship completely. For example, most birds can fly, and may want to add a method to the birds fly () means flying, but some birds will not fly, such as penguins.
In a "is-a" relationship, when overriding a method, the subclass should not change the expected behavior of the parent class, but there is no way to constrain it. For example, in the case of birds, you may have added the fly () method to the parent class, for penguins, you may think that penguins will not fly, but can walk and swim, in the Penguin Fly () method, the realization of walking or swimming logic.
Inheritance should be used as a "is-a" relationship, but Java does not have the means to constrain, the parent class has properties and behaviors, subclasses do not necessarily apply, subclasses can also override the method, implementing a behavior that is not exactly the same as the parent class expected.
However, a program that uses a parent class reference to manipulate a subclass object treats the object as a parent class object, expecting the object to conform to the attributes and behavior declared in the parent class. If not, what is the result? confusion.
How to deal with the double-sided nature of inheritance?
Inheritance is both powerful and destructive.
- Avoid using inheritance
- Proper use of inheritance
Let's first look at how to avoid inheritance, there are three ways:
- Using the final keyword
- Prioritize using combinations rather than inheritance
- Using interfaces
Use final to avoid inheritance
In the previous section, we mentioned the final and final methods, the final method cannot be overridden, the final class cannot be inherited, and we do not explain why we need them. With the above introduction, we should be able to understand some of the reasons.
By adding the final modifier to the method, the parent class retains the freedom to arbitrarily modify the internal implementation of the method, and the program that uses this method can also ensure that its behavior conforms to the parent class declaration.
To add the final modifier to a class, the parent class retains the freedom to modify the implementation of the class at will, and the user can use it with ease, without worrying about the variable referenced by a parent class, and actually pointing to a subclass object that does not conform to the expected behavior at all.
Prioritize using combinations rather than inheritance
Using a combination can ward off the influence of the parent class on the child class, thus protecting the subclass and should be used preferentially. or the above example, we use a combination to rewrite the class, the code is as follows:
Public classChild {Privatebase Base; Private Longsum; PublicChild () {base=NewBase (); } Public voidAddintNumber ) {base.add (number); Sum+=Number ; } Public voidAddAll (int[] numbers) {Base.addall (numbers); for(inti=0;i<numbers.length;i++) {sum+=Numbers[i]; } } Public Longgetsum () {returnsum; }}
In this way, subclasses do not need to focus on how the base class is implemented, the base class modifies implementation details, increases the public method, and does not affect subclasses.
However, the problem with composition is that the subclass object cannot be treated as a base class object and is uniformly processed. The workaround is to use the interface.
Using interfaces
About the interface we do not introduce, left to the next section.
Proper use of inheritance
If you want to use inheritance, how do you use it correctly? There are probably three main scenarios for using inheritance:
- The base class is written by someone else, we write subclasses.
- We write the base class, others may write subclasses.
- The base class and subclass are all written by us.
In the first scenario, the base class is primarily Java APIs, other frameworks or classes in the class library, in which case we implement custom behavior primarily by extending the base class, in which case we need to be aware that:
- The overriding method does not change the expected behavior.
- Read the documentation to understand the implementation mechanisms of overridable methods, especially the call relationships between methods.
- In the case of a base class modification, read its modification description and modify the subclass accordingly.
In the second scenario, we write the base class for others, in which case it is important to note that:
- Use inheritance to reflect a true "is-a" relationship, placing only the truly public parts into the base class.
- Add the final modifier to the public method that you do not want to be overridden.
- Write a document explaining the implementation mechanism of an overridable method, providing guidance for subclasses and telling subclasses how to rewrite them.
- Write the modification description when the base class modifies a subclass that might affect the child class.
In the third scenario, we write both the base class and the subclass, about the base class, the note is similar to the second scenario, about the subclass, the note is similar to the first scenario, but the program is controlled by us and requires that it be properly relaxed.
Summary
In this section, we describe why inheritance is a double-edged sword, while inheritance is powerful, but inheritance can destroy encapsulation, while encapsulation can be said to be the first principle of programming, and inheritance may be misused, without reflecting the true "is-a" relationship.
We also describe how to deal with inherited double-sided, on the one hand, to avoid inheritance, use final to avoid, first use the combination, the use of interfaces. If you want to use inheritance, we also describe the considerations under three scenarios where inheritance is used.
This section mentions a concept, interface, what exactly is an interface?
----------------
To be continued, check out the latest articles, please pay attention to the public number "old Horse Programming" (Scan the QR code below), from the introduction to advanced, in layman's words, Lao Ma and you explore the nature of Java programming and computer technology. Write attentively, original articles, and keep all copyrights.
Logic of the computer program (18)-Why inheritance is a double-edged sword.