📄 chapter01.html
字号:
also change the member objects at runtime, to dynamically change the behavior of
your program. Inheritance, which is described next, does not have this
flexibility since the compiler must place compile-time restrictions on classes
created with inheritance.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Because inheritance is so important in
object-oriented programming it is often highly emphasized, and the new
programmer can get the idea that inheritance should be used everywhere. This can
result in awkward and overly-complicated designs. Instead, you should first look
to composition when creating new classes, since it is simpler and more flexible.
If you take this approach, your designs will stay cleaner. Once you’ve had
some experience, it will be reasonably obvious when you need
inheritance.</FONT><A NAME="_Toc375545192"></A><A NAME="_Toc408018389"></A><A NAME="_Toc472654686"></A><BR></P></DIV>
<A NAME="Heading25"></A><FONT FACE = "Verdana"><H2 ALIGN="LEFT">
Inheritance:<BR>reusing the interface</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">By itself, the idea of an object is a
convenient tool. It allows you to package data and functionality together by
<I>concept</I>, so you can represent an appropriate problem-space idea rather
than being forced to use the idioms of the underlying machine. These concepts
are expressed as fundamental units in the programming language by using the
<A NAME="Index115"></A><A NAME="Index116"></A><B>class</B>
keyword.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">It seems a pity, however, to go to all
the trouble to create a class and then be forced to create a brand new one that
might have similar functionality. It’s nicer if we can take the existing
class, clone it, and then make additions and modifications to the clone. This is
effectively what you get with <A NAME="Index117"></A><I>inheritance</I>, with
the exception that if the original class (called the <I>base</I> or <I>super</I>
or <I>parent</I> class) is changed, the modified “clone” (called the
<I>derived </I>or <I>inherited</I> or <I>sub</I> or <I>child</I><B> </B>class)
also reflects those changes.</FONT><BR></P></DIV>
<DIV ALIGN="CENTER"><FONT FACE="Georgia"><IMG SRC="TIC2Vo05.gif"></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">(The arrow in the above UML diagram
points from the derived class to the base class. As you will see, there can be
more than one derived class.)</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">A type does more than describe the
constraints on a set of objects; it also has a relationship with other types.
Two types can have characteristics and behaviors in common, but one type may
contain more characteristics than another and may also handle more messages (or
handle them differently). Inheritance expresses this similarity between types
using the concept of <A NAME="Index118"></A><A NAME="Index119"></A>base types
and <A NAME="Index120"></A><A NAME="Index121"></A>derived types. A base type
contains all of the characteristics and behaviors that are shared among the
types derived from it. You create a base type to represent the core of your
ideas about some objects in your system. From the base type, you derive other
types to express the different ways that this core can be
realized.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">For example, a trash-recycling machine
sorts pieces of trash. The base type is “trash,” and each piece of
trash has a weight, a value, and so on, and can be shredded, melted, or
decomposed. From this, more specific types of trash are derived that may have
additional characteristics (a bottle has a color) or behaviors (an aluminum can
may be crushed, a steel can is magnetic). In addition, some behaviors may be
different (the value of paper depends on its type and condition). Using
inheritance, you can build a type hierarchy that expresses the problem
you’re trying to solve in terms of its types.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">A second example is the classic
“<A NAME="Index122"></A>shape” example, perhaps used in a
computer-aided design system or game simulation. The base type is
“shape,” and each shape has a size, a color, a position, and so on.
Each shape can be drawn, erased, moved, colored, etc. From this, specific types
of shapes are derived (inherited): circle, square, triangle, and so on, each of
which may have additional characteristics and behaviors. Certain shapes can be
flipped, for example. Some behaviors may be different, such as when you want to
calculate the area of a shape. The type hierarchy embodies both the similarities
and differences between the shapes.</FONT><BR></P></DIV>
<DIV ALIGN="CENTER"><FONT FACE="Georgia"><IMG SRC="TIC2Vo06.gif"></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Casting the solution in the same terms as
the problem is tremendously beneficial because you don’t need a lot of
intermediate models to get from a description of the problem to a description of
the solution. With objects, the type hierarchy is the primary model, so you go
directly from the description of the system in the real world to the description
of the system in code. Indeed, one of the difficulties people have with
object-oriented design is that it’s too simple to get from the beginning
to the end. A mind trained to look for complex solutions is often stumped by
this simplicity at first.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">When you inherit from an existing type,
you create a new type. This new type contains not only all the members of the
existing type (although the <B>private</B> ones are hidden away and
inaccessible), but more importantly it duplicates the interface of the base
class. That is, all the messages you can send to objects of the base class you
can also send to objects of the derived class. Since we know the type of a class
by the messages we can send to it, this means that the derived class <I>is the
same type as the base class</I>. In the previous example, “a circle is a
shape.” This type equivalence via inheritance is one of the fundamental
gateways in understanding the meaning of object-oriented
programming.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Since both the base class and derived
class have the same interface, there must be some implementation to go along
with that interface. That is, there must be some code to execute when an object
receives a particular message. If you simply inherit a class and don’t do
anything else, the methods from the base-class interface come right along into
the derived class. That means objects of the derived class have not only the
same type, they also have the same behavior, which isn’t particularly
interesting.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">You have two ways to differentiate your
new derived class from the original base class. The first is quite
straightforward: You simply add brand new functions to the derived class. These
new functions are not part of the base class interface. This means that the base
class simply didn’t do as much as you wanted it to, so you added more
functions. This simple and primitive use for
<A NAME="Index123"></A><A NAME="Index124"></A>inheritance is, at times, the
perfect solution to your problem. However, you should look closely for the
possibility that your base class might also need these additional functions.
This process of discovery and iteration of your design happens regularly in
object-oriented programming.</FONT><BR></P></DIV>
<DIV ALIGN="CENTER"><FONT FACE="Georgia"><IMG SRC="TIC2Vo07.gif"></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Although inheritance may sometimes imply
that you are going to add new functions to the interface, that’s not
necessarily true. The second and more important way to differentiate your new
class is to <I>change</I> the behavior of an existing base-class function. This
is referred to as
<A NAME="Index125"></A><A NAME="Index126"></A><I>overriding</I> that
function.</FONT><BR></P></DIV>
<DIV ALIGN="CENTER"><FONT FACE="Georgia"><IMG SRC="TIC2Vo08.gif"></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">To override a function, you simply create
a new definition for the function in the derived class. You’re saying,
“I’m using the same interface function here, but I want it to do
something different for my new
type.”</FONT><A NAME="_Toc375545194"></A><A NAME="_Toc408018391"></A><A NAME="_Toc472654687"></A><BR></P></DIV>
<A NAME="Heading26"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Is-a vs. is-like-a relationships<BR><A NAME="Index127"></A></H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">There’s a certain debate that can
occur about inheritance: Should inheritance override <I>only</I> base-class
functions (and not add new member functions that aren’t in the base
class)? This would mean that the derived type is <I>exactly</I> the same type as
the base class since it has exactly the same interface. As a result, you can
exactly substitute an object of the derived class for an object of the base
class. This can be thought of as <A NAME="Index128"></A><I>pure
substitution</I>, and it’s often referred to as the
<A NAME="Index129"></A><I>substitution principle</I>. In a sense, this is the
ideal way to treat inheritance. We often refer to the relationship between the
base class and derived classes in this case as an <I>is-a</I> relationship,
because you can say “a circle <I>is a</I> shape.” A test for
inheritance is to determine whether you can state the is-a relationship about
the classes and have it make sense.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">There are times when you must add new
interface elements to a derived type, thus extending the interface and creating
a new type. The new type can still be substituted for the base type, but the
substitution isn’t perfect because your new functions are not accessible
from the base type. This can be described as an <I>is-like-a</I> relationship;
the new type has the interface of the old type but it also contains other
functions, so you can’t really say it’s exactly the same. For
example, consider an air conditioner. Suppose your house is wired with all the
controls for cooling; that is, it has an interface that allows you to control
cooling. Imagine that the air conditioner breaks down and you replace it with a
heat pump, which can both heat and cool. The heat pump <I>is-like-an</I> air
conditioner, but it can do more. Because the control system of your house is
designed only to control cooling, it is restricted to communication with the
cooling part of the new object. The interface of the new object has been
extended, and the existing system doesn’t know about anything except the
original interface.</FONT><BR></P></DIV>
<DIV ALIGN="CENTER"><FONT FACE="Georgia"><IMG SRC="TIC2Vo09.gif"></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Of course, once you see this design it
becomes clear that the base class “cooling system” is not general
enough, and should be renamed to “temperature control system” so
that it can also include heating – at which point the substitution
principle will work. However, the diagram above is an example of what can happen
in design and in the real world. </FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">When you see the substitution principle
it’s easy to feel like this approach (pure substitution) is the only way
to do things, and in fact it <I>is</I> nice if your design works out that way.
But you’ll find that there are times when it’s equally clear that
you must add new functions to the interface of a derived class. With inspection
both cases should be reasonably
obvious.</FONT><A NAME="_Toc375545195"></A><A NAME="_Toc408018392"></A><A NAME="_Toc472654688"></A><BR></P></DIV>
<A NAME="Heading27"></A><FONT FACE = "Verdana"><H2 ALIGN="LEFT">
Interchangeable objects <BR>with polymorphism<BR><A NAME="Index130"></A></H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">When dealing with type hierarchies, you
often want to treat an object not as the specific type that it is but instead as
its base type. This allows you to write code that doesn’t depend on
specific types. In the shape example, functions manipulate generic shapes
without respect to whether they’re circles, squares, triangles, and so on.
All shapes can be drawn, erased, and moved, so these functions simply send a
message to a shape object; they don’t worry about how the object copes
with the message.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Such code is unaffected by the addition
of new types, and adding new types is the most common way to extend an
object-oriented program to handle new situations. For example, you can derive a
new subtype of shape called pentagon<I> </I>without modifying the functions that
deal only with generic shapes. This ability to extend a program easily by
deriving new subtypes is important because it greatly improves designs while
reducing the cost of software maintenance.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">There’s a problem, however, with
attempting to treat derived-type objects as their generic base types (circles as
shapes, bicycles as vehicles, cormorants as birds, etc.). If a function is going
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -