Object Inheritance
10.1 Introduction
The preceding two chapters introduced you to the use and creation of simple objects. Now it is time to show how objects can be created from other objects. This chapter covers inheritance, method overriding, and various applications of objects.
10.2 Inheritance
At this point in the book, your programming style is starting to become more sophisticated. No longer are you writing code where one long file encompasses everything you do. Now you are using objects, and you are also beginning to program in an object-oriented manner. Object-oriented programming (OOP) is a powerful form of programming that is currently extremely popular and the backbone for many new languages, including Ruby.
Gem of Wisdom
Object inheritance is one of the most powerful parts of object-oriented programming. Inheritance enables hierarchical decomposition of structures into logically encapsulated units.
Thus far, we have talked about creating simple objects that are independent of one another. However, one of the most powerful abilities OOP has is the ability to define relationships between objects. The first of these relationships is known as inheritance. To understand the concept of inheritance, imagine a ball (any spherical object that could pass for a ball will do). Perhaps you are thinking of a baseball, a tennis ball, or a ping-pong ball; it does not matter which because all those balls have a large degree of similarity among them, despite being different types of balls or even different objects. If we needed to accomplish some task and asked you for a ball, they could all work despite their differences. Inheritance allows us to define these types of relationships with Ruby objects. This ends up saving the programmer significant time and code because she or he need not redefine parts of the objects that are similar.
Let’s return to our bank account example from the preceding chapter. It defined what is essentially a checking account, and our bank is no longer happy with just this one type of account; now it wants to expand to include savings accounts. The first thing to notice are the similarities between a savings account and a checking account: they both maintain a balance and can have money withdrawn from them and deposited to them. The class that defines the similarities in the relationship is referred to as the parent class, or the superclass.
The main differences between the two bank accounts are that you
cannot withdraw beyond the minimum balance from a savings account (we will
touch on this in the next section) and that a savings account generates
interest. The class that defines the differences in the relationship is
referred to as the child class or the subclass. Now
we will use inheritance to define this SavingsAccount
class (see Example 10-1).
SavingsAccount version 1
1
require_relative
'../chapter_09/account_5.rb'
2
3
class
SavingsAccount
<
Account
4
def
initialize
(
balance
,
name
,
phone_number
,
interest
,
minimum
)
5
super
(
balance
,
name
,
phone_number
)
6
@interest
=
interest
7
@minimum
=
minimum
8
end
9
10
def
accumulate_interest
11
@balance
+=
@balance
*
@interest
12
end
13
end
Note that we use the require_relative
command instead of the require
command. require
loads files that are installed as Ruby
libraries or files for which the full path to the file is given. require_relative
is used to load files without
specifying the full path to the file; it looks for files in a location
relative to the file require_relative
is used in.
The first thing to note in the code provided in Example 10-1 is the <
symbol (on line 3). This is the symbol used
to define inheritance in Ruby. In this case, the parent class is the Account
class, which is predefined via
line 1, and the child class is the SavingsAccount
class. See the account
class in Example 9-9.
The next thing to look over is the constructor of the SavingsAccount
class, expressed by the initialize
method on line 4. Immediately, in
line 5, this method calls a method named super()
, which is the equivalent of calling the
initialize
method for the superclass
Account
. After this, we initialize the
instance variables @interest
and
@minimum
.
It is important to note that these pieces of data distinguish a SavingsAccount
from a CheckingAccount
and the subclass from the
superclass.
Finally, there is the accumulate_interest
method, which is just a
simple interest calculation.
However, thanks to inheritance, the SavingsAccount
class can do more than just
accumulate interest. It also inherits all the data and methods from the
Account
class. Table 10-1 is a summary of everything
inherited by the SavingsAccount
class.
Data | Methods |
---|---|
|
|
|
|
|
|
display |
If we create an instance of the SavingsAccount
class:
account
=
SavingsAccount
.
new
(
200
.
00
,
"Reynolds"
,
9694905555
,
0
.
015
,
150
)
we can then call any of the following methods:
account.deposit(amount)
account.withdraw(amount)
account.transfer(amount, targetAccount)
account.accumulate_interest
account.display
This explicitly shows the power of inheritance. Although we never
defined four of the five methods shown for the SavingsAccount
class, we can use them because
they are inherited from the parent class, Account
. The SavingsAccount
class consists of only 11 lines
of code, but it has as much functionality as 41 lines of code (the number
of lines of code in Example 10-1 plus
Example 9-9).
If we were to return to the transfer(amount, targetAccount)
method from the
preceding chapter we would see that
it was designed to transfer money from one account to another. At the time we created
the method, we had not designed a SavingsAccount
and were content with it
working only on Account
objects.
However, it will work on SavingsAccount
objects, because a SavingsAccount
is an
Account
; it has enough similarity for a
transfer between accounts to be possible. This is another powerful ability granted by inheritance, and
it is known as polymorphism. Polymorphism, however, does not work both
ways. With the transfer(amount,
targetAccount)
method example, the polymorphism is from the subclass to the
superclass. Polymorphism will not work when you are trying to morph from a
superclass to a subclass, because the subclass has abilities the
superclass does not. To express this in an example, imagine trying to call
the accumulate_interest()
method from
an Account
object; it won’t work
because only SavingsAccount
objects,
not Account
objects, have the accumulate_interest()
method.
10.3 Basic Method Overriding
When extending a class, it is sometimes convenient to alter methods
that already exist in the class’s superclass. For example, both the
checking and saving accounts need a method for withdrawing money. However,
the methods are only the same on the outside. Unlike the regular checking
account, the savings account needs to check if the balance would fall
below the minimum allowed. To achieve this, the SavingsAccount
class will need to override the
withdraw
method by defining its own
withdraw functionality, as shown in Example 10-2. Overriding is accomplished by
using the same name in the local class. The local definition always
supersedes the parent definition.
SavingsAccount version 2
1
require_relative
'../chapter_09/account_5.rb'
2
3
class
SavingsAccount
<
Account
4
def
initialize
(
balance
,
name
,
phone_number
,
interest
,
minimum
)
5
super
(
balance
,
name
,
phone_number
)
6
@interest
=
interest
7
@minimum
=
minimum
8
end
9
10
def
accumulate_interest
11
@balance
+=
@balance
*
@interest
12
end
13
14
def
withdraw
(
amount
)
15
if
(
@balance
-
amount
>=
@minimum
)
16
@balance
-=
amount
17
else
18
puts
"Balance cannot drop below: "
+
@minimum
.
to_s
19
end
20
end
21
end
Instead of calling on the withdraw
method that belongs to Account
, the SavingsAccount
class will use the new withdraw
method that overrode it. As a result,
any instances of SavingsAccount
will
not be able to fall below their minimum account balances. This powerful
property of OOP has its problems. It implies that the writer of a subclass
be fully cognizant of the methods and instance variables of the
superclass.
10.4 Accessing the Superclass
In many cases, the overriding methods will have similar
functionality to the methods they override. It is counterproductive to the
concept of inheritance to just rewrite the same methods again with
slightly altered code. Inheritance exists to make code reuse as easy as
possible. As such, it provides a way to avoid rewriting the superclass
method. Simply insert the word super
with all the parameters that would be used to call the superclass method
bearing the same name wherever you would like the superclass’s method,
just like the initialize
method.
Applying this to our new SavingsAccount
class, we get the code in Example 10-3.
SavingsAccount version 3
1
require_relative
'../chapter_09/account_5.rb'
2
3
class
SavingsAccount
<
Account
4
def
initialize
(
balance
,
name
,
phone_number
,
interest
,
minimum
)
5
super
(
balance
,
name
,
phone_number
)
6
@interest
=
interest
7
@minimum
=
minimum
8
end
9
10
def
accumulate_interest
11
@balance
+=
@balance
*
@interest
12
end
13
14
def
withdraw
(
amount
)
15
if
(
@balance
-
amount
>=
@minimum
)
16
super
(
amount
)
17
else
18
puts
"Balance cannot drop below: "
+
@minimum
.
to_s
19
end
20
end
21
end
In our example, obviously the benefits seem minimal. However, for
complex programs, the advantages of using predefined classes are
tremendous. Not only are we saving ourselves the time of rewriting the
class, but we are also making code maintenance easier. If the withdraw
method needs to be updated, we can
update it in the Account
class. Any
subclasses that use it as their superclass will be updated accordingly,
wherever they have called super
.
10.5 Applications
Inheritance is a way to form new classes that borrow attributes and behaviors of previously defined classes. After learning about inheritance and method overriding, you are probably wondering when you will ever need to use them. What is the big deal about inheritance? Why can’t we just make a bunch of different classes? To put it simply, it saves significant unnecessary coding by eliminating code duplication, and it simplifies software testing and maintenance since functionality and local data are isolated. Here are several other examples for which you can use inheritance.
10.5.1 Person Database
A contractor is looking for a way to keep track of all the people
in his organization. He has full-time and part-time employees, student
interns, and volunteers. In this example, you can make a class called
Person
. This class can have the
individual’s name, address, phone number, email address, and weekly
hours worked. You can have a method called email
that emails all the employees to remind
them to turn in their time sheets. Then you can have subclasses called
Full
, Part
, Intern
, and Volunteer
. For the Full
subclass, you can include variables like
hourly wage and overtime pay. For a behavior you can have a process_payment
method to deposit money into
the employee’s bank account. A student Intern
would have different variables. Maybe
you want to keep track of who has the highest grade-point average or
test scores to see which one is the top intern. Thus, you would create
some variables for the aforementioned categories. For members of the
Volunteer
subclass, you probably
would not want to send them an email about turning in their time sheets
to get paid, so you should make a custom method that asks them how many
hours they volunteered. You can create an email
method that overrides the previous
one.
10.5.2 Grocery Store
You go to a small grocery store and overhear the owner complaining
about keeping track of his food. He orders food every week, but his
employees have no idea what to do with the food when it comes in. You
can create a database program with a class called Food
. This class can have variables like the
name of the item, the price, and the location where it is stored. You
can have a method called PrintLabel
to create price labels to stick on the food. Possible subclasses to
consider are Fruit
, Meat
, and Grain
. Most of the foods will have traits in
common, so you won’t need to worry about creating too many variables.
The PrintLabel
method will create
tiny stickers you can put on the food, but what if you had a Fruit
like raisins or grapes? There is no
possible way you can print labels to stick on each individual fruit. You
will probably want to use method overriding to print bigger labels you
can stick on the shelf near the respective fruit.
10.5.3 Video Games
You have been hired to create a role-playing video game called
“Pirates and Ninjas of the Pacific,” where players can choose to play as
ninjas or pirates. How are you going to keep track of all the players’
characters? You can create a class called Player
with variables for name, health points,
and experience. You should also include methods for walking and fighting
so that the player can move around and kill monsters by fighting with
them. Since pirates and ninjas obviously have different skills, you will
probably want to create subclasses called Pirate
and Ninja
. Ninjas generally fight using
hand-to-hand combat; so you would not need to change their Fight
method. Pirates, on the other hand,
generally lack hand-to-hand combat skills and use guns instead. What’s
the solution? Override their Fight
method with something that allows them to shoot guns. But ninjas don’t
always just fight with their hands, so you could create additional
methods like throw
and jump
.
Looking at this and the preceding examples, you can get the general idea for when classes, inheritance, and method overriding can be useful. These techniques are used in every field of computer science. You can implement inheritance whether you are programming for a contractor, a grocer, or a video game company. As an exercise, try to come up with three of your own examples for which you can use classes, inheritance, and method overriding.
10.6 Summary
We have introduced the notion of object inheritance and shown some examples.
10.6.1 Key Concepts
One of the most powerful tools in object-oriented programming (OOP) is that objects can be created from other objects and use the resources of the parent object. This is known as inheritance. In this relationship, the parent class or superclass defines the relationship with the child class or subclass.
Subclasses inherit both data and methods from their parent class.
A key point to keep in mind when working with inheritance is that polymorphism often takes place, which in some cases can lead to a need for method overriding.
10.6.2 Key Definitions
Inheritance: When the relationship between two classes is defined.
Superclass: The class that defines the object for the relationship.
Subclass: The class for which the relationship is defined.
Method overriding: When the method for a parent class is redefined for a child class.
Polymorphism: Allows different data types to be handled by a unified interface.
10.7 Exercises
The class shown in Example 10-4 is used to keep track of the inventory for a company. It allows the user to assign a name and a manufacturer to each item. Write a class
ItemNumber
that inherits fromItem
that lets the user input a number of items owned for each item. Create an instance of theItem
class and then an instance of theItemNumber
class.Code for Exercise 1
1
class
Item
2
def
initialize
(
item
,
maker
)
3
@item
=
item
4
@maker
=
maker
5
end
6
7
def
display
8
puts
"Item ==> "
+
@item
9
puts
"Maker ==> "
+
@maker
10
end
11
end
Explain polymorphism.
Define OOP.
A university has three kinds of students: full-time students, part-time students, and off-campus students. Each student has the following information: name, address, phone number, Social Security number, student ID, and GPA. The full-time students should take at least 12 credits per semester. The part-time students should take less than 12 credits per semester. The off-campus students have no limit on credits per semester. The tuition fee for full-time students is $8,500 for up to 18 credits per semester and $600 for every credit over 18 credits. The tuition fee for part-time students is $750 per credit. The tuition fee for off-campus students is $520 per credit. Write a program that lists student information and calculates their tuition fees.
Write a program for the grocery store example described in Section 10.5.2, “Grocery Store.”