Java programming ideology (version 4) Inheritance

Source: Internet
Author: User
Tags export class
Document directory
  • 1.7 interchangeable objects accompanied by Polymorphism
  • 1.8 single inheritance Structure

The concept of object itself is a very convenient tool that allows you to encapsulate data and functions together through concepts, so you can give a proper representation of the concept of the problem space, instead of using the underlying machine language. These concepts are represented by the keyword class, which forms the basic unit in programming languages.

Unfortunately, there is still a lot of trouble to do this: after creating a class, you still have to create a new class even if the new class has similar functions. If we can copy an existing class, and then create a new class by adding and modifying the copy, it will be much better. This can be achieved through inheritance, but there are exceptions when the source class (called the base class, superclass, or parent class) changes, the modified "copy" (called the export class, inheritance class, or subclass) also reflects these changes ().

(The arrow in this UML diagram points to the base class from the export class, as you will see later, there will usually be more than one export class .)

The type not only describes the constraints acting on an object set, but also the relationship with other types. Two types can have the same features and behaviors, but one type may have more features than the other, you can also process more messages (or process messages in different ways ). Inheritance uses the concepts of the base type and the export type to represent the similarity between these types. A base type contains the features and behaviors shared by all its export types. You can create a base type to represent the core concepts of some objects in the system, and export other types from the base type to indicate different methods that can be implemented by this core.

Taking the garbage collector as an example, it is used to classify scattered garbage. "Garbage" is a base type. Each piece of garbage has features such as weight and value, which can be chopped, melted, or decomposed. On this basis, you can add additional features (such as bottle color) or behavior (such as aluminum can be crushed, iron can be magnetized) to export a more specific type of garbage. In addition, some behaviors may be different (for example, the value of paper depends on its type and status ). You can use inheritance to construct a type hierarchy to represent a type of problem to be solved.

The second example is a classic geometric example, which may be used in computer-aided design systems or game simulation systems. The base class is a geometric shape. Each geometric shape has dimensions, colors, and positions. At the same time, each geometric shape can be drawn, erased, moved, and colored. On this basis, you can export (inherit) specific geometric shapes-circular, square, triangle, etc.-each has additional characteristics and behavior, for example, some shapes can be flipped. Some behavior may be different, such as calculating the area of the geometric shape. Type hierarchies also reflect the similarity and difference between geometric shapes ().

It is helpful to convert a solution into a problem in the same term, because there is no need to create many intermediate models between the Problem description and solution description. By using objects, type hierarchies become the main model. Therefore, you can directly describe the system from the real world to the system with code. In fact, one of the difficulties for people who use object-oriented design is that it is too simple from the beginning to the end. This simplicity may be difficult at the beginning for a well-trained and good-looking mind for complex solutions.

When the existing type is inherited, a new type is created. This new type includes not only all members of the existing type (although the private member is hidden and inaccessible), but also copies the interface of the base class. That is to say, all messages that can be sent to the base class object can also be sent to the export class object. Because the type of the message sent to the class knows the type of the class, this means that the export class has the same type as the base class. In the previous example, "a circle is also a geometric shape ". The Type equivalence generated by inheritance is an important threshold for understanding the connotation of object-oriented programming methods.

Because the base class and the export class have the same basic interface, there must be some specific implementations of this interface. That is to say, when an object receives a specific message, some code must be executed. If you simply inherit a class without doing anything else, the methods in the base class interface will be directly inherited into the export class. This means that the object of the export class not only has the same type as the base class, but also has the same behavior. This is meaningless.

There are two ways to make the base class and the export class different. The first method is very direct: Add a new method directly in the export class. These new methods are not part of the base class interface. This means that the base class cannot directly meet all your needs, so you must add more methods. This simple and basic method of inheritance is sometimes a perfect solution to the problem. However, you should carefully consider whether there is a possibility that the base class also needs these additional methods. This design discovery and iteration process often occurs in Object-Oriented Programming ().

Although inheritance may sometimes mean adding new methods to an interface (especially in Java where the extends keyword represents inheritance), this is not always the case. The second and more important way to make the differences between the export class and the base class is to change the behavior of the existing base class method, which is called overriding ().

To override a method, you can directly create a new definition of the method in the export class. You can say: "At this time, I am using the same interface method, but I want to do something different in the new type ."

1.6.1 The relationship between "yes" and "like"

There may be some debate about inheritance: Should inheritance only cover the methods of the base class (instead of the new methods not added to the base class? If this is done, it means that the export class and the base class are of the same type, because they have the same interface. An export class object can be used to completely replace a base class object. This can be regarded as a pure replacement, which is often referred to as the replacement principle. In a sense, this is an ideal way to handle inheritance. In this case, the relationship between the base class and the export class is often referred to as the is-a (a) relationship, because "A circle is a geometric shape ". To determine whether to inherit is to determine whether to use is-a to describe the relationship between classes and make them meaningful.

Sometimes new interface elements must be added to the export type, which expands the interface. This new type can still Replace the base class, but this replacement is not perfect because the base class cannot access the newly added method. In this case, we can describe the is-like-a (like a) relationship. The new type has interfaces of the old type, but it also contains other methods, so they cannot be said to be identical. Take air conditioners as an example. Assume that the controllers of all air-conditioning devices have been installed in the House. That is to say, the House has an interface for you to control the air-conditioning devices. Imagine if the air conditioner breaks down and you replace it with a heat pump that can both be cooled and heated, then the heat pump is-like-, but it can do more. Because the house's control system is designed to only control air-conditioning equipment, it can only communicate with the cooling part of the new object. Although the interface of the new object has been extended, the existing system does not know anything except the original interface.

Of course, after reading this design, we will obviously find that the basis category of the cooling system is not general enough and should be renamed as a "Temperature Control System" so that it can include the heating function, in this way, we can apply the substitution principle. This figure shows what may happen during design in the real world.

When you see the replacement principle, it is easy to think that this method (purely alternative) is the only feasible method, and in fact, it is good to design it in this way. However, you often find that you must add new methods to the export class interface. As long as you carefully examine them, the use of the two methods should be quite obvious.

1.7 interchangeable objects accompanied by Polymorphism

When processing type hierarchies, you often want to treat an object as its base class instead of its specific type. This allows people to write code that does not depend on a specific type. In the case of ry, Methods Operate on generalized shapes, regardless of whether they are circular, square, triangle, or other undefined shapes. All geometric shapes can be drawn, erased, and moved. Therefore, these methods directly send messages to a geometric object without worrying about how the object will process messages.

Such code is not affected by adding a new type, and adding a new type is the most common way to extend an object-oriented program to handle new situations. For example, you can export a new sub-type "pentagram" from "ry" without modifying the method for processing generalized ry shapes. The ability to easily scale the design by exporting new child types is one of the basic ways to encapsulate changes. This capability can greatly improve our design and reduce the cost of software maintenance.

However, when trying to treat the exported type object as its generalized base type object (think of the circular shape as a geometric shape, think of the bicycle as a vehicle, and think of the circular shape as a bird ), there is still a problem. If a method needs to let the generalized geometric shape draw itself, let the generalized Transportation Tool run, or let the generalized birds move, then it is impossible for the compiler to know which piece of code should be executed during compilation. This is the key: when such a message is sent, the programmer does not want to know which piece of code will be executed; the drawing method can be equivalent to a circle, square, or triangle, objects execute the appropriate code based on their specific types.

If you do not need to know which part of the code will be executed, when you add a new child type, you do not need to change the method to call it, and it will be able to execute different codes. Therefore, the compiler cannot accurately understand which code will be executed. What should it do? For example, in the following figure, the birdcontroller object only processes generalized bird objects without knowing their exact types. From the perspective of birdcontroller, this is very convenient because no special code is required to determine the exact type or behavior of the bird object to be processed. When the move () method is called, even if you ignore the specific type of bird, it will produce the correct behavior (goose) walking, flying or swimming, penguin (Penguin) walking or swimming). How does this happen?

The answer to this question is also the most important recipe for Object-Oriented Programming: the compiler cannot produce function calls in the traditional sense. Function calls produced by a non-Object-Oriented Compiler can cause so-called pre-binding. You may have never heard of this term and may have never thought of other methods of function calls. This means that the compiler will generate a call to a specific function name, and resolve the call to the absolute address of the code to be executed at runtime. However, in OOP, the program cannot determine the Code address until it runs. Therefore, when a message is sent to a generalized object, other mechanisms must be used.

To solve this problem, the object-oriented programming language uses the concept of binding later. When a message is sent to an object, the called Code cannot be determined until it is run. The compiler ensures the existence of the called method and performs type checks on the call parameters and return values (languages that cannot provide such guarantees are called weak types ), but you do not know the exact code to be executed.

To perform later binding, Java uses a small segment of special code to replace the absolute address call. This Code uses the information stored in the object to calculate the address of the method body (this process will be detailed in chapter 8th ). In this way, according to the content of this short piece of code, each object can have different behavior performances. When a message is sent to an object, the object can know what to do with the message.

In some languages, you must explicitly declare that you want a method to be flexible in binding attributes later (C ++ is implemented using the virtual keyword ). In these languages, methods are not dynamically bound by default. In Java, dynamic binding is the default action and no additional keywords need to be added for polymorphism.

Let's take a look at the geometric shape example. The entire class family (all of the classes are based on the same consistent interface) has been illustrated before this chapter. To illustrate polymorphism, We need to write a piece of code that ignores the specific details of the type and only interacts with the base class. This code is separated from the specific type information (Decoupled), which makes code writing easier and easier to understand. In addition, if a new type such as hexagon (hexagonal) is added through the Inheritance Mechanism) the processing of new types is as good as that of existing types. Because of this, this program can be called extensible.

If you use Java to write a method (you will soon learn how to write it ):

This method can be used to talk to any shape, so it is independent of the specific type of any object it wants to draw and erase. If the other part of the program uses the dosomething () method:

Calls to dosomething () are automatically and correctly processed regardless of the object type.

This is an amazing trick. Take a look at the following line of code:

What will happen when the circle is passed into the method that expects to receive the shape. Since circle can be viewed as a shape by dosomething (), that is, any message that dosomething () can send to shape can be received by circle, this is completely secure and logical.

The process of viewing the export class as its base class is called upcasting ). The name cast is inspired by the molding action of the model casting. The word up comes from the typical layout of the inherited graph: Usually the base class is on the top, the export class is dispersed in the lower part. Therefore, to transform to a base class is to move up in the inheritance diagram, that is, "upward transformation "().

An Object-Oriented Program will certainly include an upward transformation somewhere, because this is the key to freeing itself from the need to know the exact type. Let's look at the code in dosomething:

Note that this code does not mean "if it is circle, please do this; if it is square, please do that ......". If you write the code that checks all the actual possible types of shapes, the code will be messy and you need to modify the code after each new type of shape is added. The expression here only means "you are a shape. I know you can use erase () and draw () on your own, so do it, but pay attention to the correctness of the details ."

Dosomething () code is impressive because, somehow, it always does what to do. The Code executed by calling the draw () method of circle is different from the code executed by calling the draw () method of square or line, and when draw () when a message is sent to an anonymous shape, the correct behavior is also generated based on the actual type of the shape. This is amazing because, as mentioned above, when the Java compiler compiles dosomething () code, it cannot exactly know the exact type of dosomething () to process. Therefore, we usually expect that the compilation result is to call the erase () and draw () versions of the base class shape, rather than the corresponding version of the specific circle, square, or line. It is precisely because of polymorphism that things can always be correctly handled. The compiler and the running system process relevant details. All you need to know right away is that things will happen, and more importantly, how to design them. When a message is sent to an object, the object knows the correct behavior even if it involves an upward transformation.

1.8 single inheritance Structure

In Oop, the question that has become very eye-catching since the launch of C ++ is whether all classes ultimately inherit from a single base class. In Java (in fact, it also includes all OOP languages except C ++), the answer is yes. The name of the ultimate base class is object. It turns out that a single inheritance structure brings many benefits.

All objects in a single inheritance structure have a shared interface, so they are all of the same basic types. Another structure (provided by C ++) cannot ensure that all objects belong to the same basic type. From the perspective of backward compatibility, this method can better adapt to the C model, but it is limited. However, when a complete object-oriented program design is required, you must build your own inheritance system so that it can provide built-in convenience for other OOP languages. In addition, some incompatible interfaces will always be used in any new class libraries, which requires effort (possibly through multi-inheritance) to integrate the new interfaces into your design. Is it worthwhile to exchange C ++ for additional flexibility? If needed-if you invest a lot in C, doing so is very valuable. If it is just starting from scratch, a choice like Java usually has a higher productivity.

A single inheritance structure ensures that all objects have certain functions. Therefore, you know that you can perform some basic operations on each object in your system. All objects can be easily created on the stack, and parameter passing is greatly simplified.

A single inheritance structure makes the implementation of the garbage collector much easier, and the garbage collector is one of the important improvements in Java compared to C ++. Because all objects are guaranteed to have their type information, it is not deadlocked because the object type cannot be determined. This is especially important for system-level operations (such as Exception Handling) and gives programming more flexibility.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.