Tải bản đầy đủ (.pdf) (22 trang)

Inheritance — Is That All I Get

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (406.19 KB, 22 trang )

Chapter 12
Inheritance — Is That All I Get?
In This Chapter

Defining one class in terms of another, more fundamental class

Differentiating between “is a” and “has a”

Changing the class of an object

Constructing static or class members

Including constructors in an inheritance hierarchy

Invoking the base class constructor specifically
O
bject-oriented programming is based on three principles: the ability to
control access (encapsulation), the ability to inherit from other classes,
and the ability to respond appropriately (polymorphism).
Inheritance is a common concept. I am a human, except when I first wake up.
I inherit certain properties from the class
Human
, such as my ability to con-
verse, more or less, and my dependence on air, food, and carbohydrate-based
beverages with lots of caffeine. The class
Human
inherits its dependencies on
air, water, and nourishment from the class
Mammal
, which inherits from the
class


Animal
.
The ability to pass down properties is a powerful one. It enables you to
describe things in an economical way. For example, if my son asks, “What’s
a duck?” I can say, “It’s a bird that goes quack.” Despite what you may think,
that answer conveys a considerable amount of information. My son knows
what a bird is, and now he knows all those same things about a duck plus
the duck’s additional property of “quackness.”
Object-oriented languages express this inheritance relationship by allowing
one class to inherit from another. This feature enables object-oriented lan-
guages to generate a model that’s closer to the real world than the model
generated by languages that don’t support inheritance.
19_597043 ch12.qxd 9/20/05 2:12 PM Page 251
Inheriting a Class
In the following
InheritanceExample
program, the class
SubClass
inherits
from the class
BaseClass
:
// InheritanceExample - provide the simplest possible
// demonstration of inheritance
using System;
namespace InheritanceExample
{
public class BaseClass
{
public int nDataMember;

public void SomeMethod()
{
Console.WriteLine(“SomeMethod()”);
}
}
public class SubClass : BaseClass
{
public void SomeOtherMethod()
{
Console.WriteLine(“SomeOtherMethod()”);
}
}
public class Program
{
public static void Main(string[] args)
{
// create a base class object
Console.WriteLine(“Exercising a base class object:”);
BaseClass bc = new BaseClass();
bc.nDataMember = 1;
bc.SomeMethod();
// now create a subclass element
Console.WriteLine(“Exercising a subclass object:”);
SubClass sc = new SubClass();
sc.nDataMember = 2;
sc.SomeMethod();
sc.SomeOtherMethod();
// wait for user to acknowledge the results
Console.WriteLine(“Press Enter to terminate...”);
Console.Read();

}
}
}
The class
BaseClass
is defined with a data member and a simple method,
SomeMethod()
.
Main()
creates and exercises the
BaseClass
object
bc
.
252
Part IV: Object-Oriented Programming
19_597043 ch12.qxd 9/20/05 2:12 PM Page 252
The class
SubClass
inherits from that class by placing the name of the class,
BaseClass
, after a colon in the class definition.
SubClass
gets all the mem-
bers of
BaseClass
as its own, plus any members that it may add to the pile.
Main()
demonstrates that
SubClass

now has a data member,
nDataMember
,
and a member function,
SomeMethod()
, to join the brand-new member of the
family, little method
SomeOtherMethod()
— and what a joy it is, too.
The program produces the following expected output — actually, I’m sort of
surprised whenever one of my programs works as expected:
Exercising a base class object:
SomeMethod()
Exercising a subclass object:
SomeMethod()
SomeOtherMethod()
Press Enter to terminate...
Why Do You Need Inheritance?
Inheritance serves several important functions. You may think that inheri-
tance reduces the amount of typing. In a way it does — you don’t need to
repeat the properties of a
Person
when you’re describing a
Student
class.
253
Chapter 12: Inheritance — Is That All I Get?
Inheritance is amazing
To make sense of our surroundings, humans
build extensive taxonomies. For example, Fido is

a special case of dog, which is a special case of
canine, which is a special case of mammal —
and so it goes. This ability to classify things
shapes our understanding of the world.
In an object-oriented language like C#, you say
that the class
Student
inherits from the class
Person
. You also say that
Person
is a base
class of
Student
, and
Student
is a subclass
of
Person
. Finally, you say that a
Student
IS_A
Person
. (Using all caps is a common way
of expressing this unique relationship — I didn’t
make this up.)
Notice that the IS_A property is not reflexive:
Although
Student
IS_A

Person
, the reverse
is not true. A
Person
IS_NOT_A
Student
.
A statement like this always refers to the gen-
eral case. It could be that a particular
Person
is, in fact, a
Student
— lots of people who are
members of the class
Person
are not members
of the class
Student
. In addition, the class
Student
has properties it does not share with
the class
Person
. For example,
Student
has
a grade point average, but the ordinary
Person
quite happily does not.
The inheritance property is transitive. For exam-

ple, if I define a new class
GraduateStudent
as a subclass of
Student
,
GraduateStudent
is also a
Person
. It must be that way: If a
GraduateStudent
IS_A
Student
and a
Student
IS_A
Person
, a
GraduateStudent
IS_A
Person
. Q.E.D.
19_597043 ch12.qxd 9/20/05 2:12 PM Page 253
A more important, related issue is that major buzzword, reuse. Software sci-
entists have known for some time that starting from scratch with each new
project and rebuilding the same software components doesn’t make much
sense.
Compare the situation in software development to that of other industries.
How many car manufacturers start by building their own wrenches and screw-
drivers before they construct a car? And even if they did, how many would
start over completely, building all new tools for the next model? Practitioners

in other industries have found that starting with existing screws, bolts, nuts,
and even larger off-the-shelf components such as motors and compressors
makes more sense than starting from scratch.
Inheritance enables you to tweak existing software components. You can
adapt existing classes to new applications without making internal modifica-
tions. The existing class is inherited into — extended by — a new subclass
that contains the necessary additions and modifications. If someone else
wrote the base class, you may not be able to modify it, so inheritance can
save the day.
This capability carries with it a third benefit of inheritance. Suppose you
inherit from some existing class. Later, you find that the base class has a bug
you must correct. If you’ve modified the class to reuse it, you must manually
check for, correct, and retest the bug in each application separately. If you’ve
inherited the class without changes, you can generally stick the updated
class into the other application without much hassle.
But the biggest benefit of inheritance is that it describes the way life is.
Things inherit properties from each other. There’s no getting around it.
Basta! — as my Italian grandmother would say.
A More Involved Example — Inheriting
from a BankAccount Class
A bank maintains several types of accounts. One type, the savings account,
has all the properties of a simple bank account plus the ability to accumulate
interest. The following
SimpleSavingsAccount
program models this rela-
tionship in C#.
To those faint of heart, you may want to steady yourself. This listing is a little
on the long side; however, the pieces are fairly well divided. The version of
this program on the CD includes some modifications from the next section of
this chapter, so it’s a bit different from this listing.

254
Part IV: Object-Oriented Programming
19_597043 ch12.qxd 9/20/05 2:12 PM Page 254
// SimpleSavingsAccount - implement a SavingsAccount as a form of
// BankAccount; don’t use any virtual methods
// (Chapter 13 explains virtual methods)
using System;
namespace SimpleSavingsAccount
{
// BankAccount - simulate a bank account each of which
// carries an account ID (which is assigned
// upon creation) and a balance
public class BankAccount // the base class
{
// bank accounts start at 1000 and increase sequentially from there
public static int nNextAccountNumber = 1000;
// maintain the account number and balance for each object
public int nAccountNumber;
public decimal mBalance;
// Init - initialize a bank account with the next account ID and the
// specified initial balance (default to zero)
public void InitBankAccount()
{
InitBankAccount(0);
}
public void InitBankAccount(decimal mInitialBalance)
{
nAccountNumber = ++nNextAccountNumber;
mBalance = mInitialBalance;
}

// Balance property
public decimal Balance
{
get { return mBalance;}
}
// Deposit - any positive deposit is allowed
public void Deposit(decimal mAmount)
{
if (mAmount > 0)
{
mBalance += mAmount;
}
}
// Withdraw - you can withdraw any amount up to the
// balance; return the amount withdrawn
public decimal Withdraw(decimal mWithdrawal)
{
if (Balance <= mWithdrawal) // use Balance property
{
mWithdrawal = Balance;
}
mBalance -= mWithdrawal;
return mWithdrawal;
}
// ToString - stringify the account
255
Chapter 12: Inheritance — Is That All I Get?
19_597043 ch12.qxd 9/20/05 2:12 PM Page 255
public string ToBankAccountString()
{

return String.Format(“{0} - {1:C}”,
nAccountNumber, Balance);
}
}
// SavingsAccount - a bank account that draws interest
public class SavingsAccount : BankAccount // the subclass
{
public decimal mInterestRate;
// InitSavingsAccount - input the rate expressed as a
// rate between 0 and 100
public void InitSavingsAccount(decimal mInterestRate)
{
InitSavingsAccount(0, mInterestRate);
}
public void InitSavingsAccount(decimal mInitial, decimal mInterestRate)
{
InitBankAccount(mInitial);
this.mInterestRate = mInterestRate / 100;
}
// AccumulateInterest - invoke once per period
public void AccumulateInterest()
{
mBalance = Balance + (decimal)(Balance * mInterestRate);
}
// ToString - stringify the account
public string ToSavingsAccountString()
{
return String.Format(“{0} ({1}%)”,
ToBankAccountString(), mInterestRate * 100);
}

}
public class Program
{
public static void Main(string[] args)
{
// create a bank account and display it
BankAccount ba = new BankAccount();
ba.InitBankAccount(100);
ba.Deposit(100);
Console.WriteLine(“Account {0}”, ba.ToBankAccountString());
// now a savings account
SavingsAccount sa = new SavingsAccount();
sa.InitSavingsAccount(100, 12.5M);
sa.AccumulateInterest();
Console.WriteLine(“Account {0}”, sa.ToSavingsAccountString());
// wait for user to acknowledge the results
Console.WriteLine(“Press Enter to terminate...”);
Console.Read();
}
}
}
256
Part IV: Object-Oriented Programming
19_597043 ch12.qxd 9/20/05 2:12 PM Page 256
The
BankAccount
class is not unlike some of those appearing in other chap-
ters of this book. It begins with an overloaded initialization function
InitBankAccount()
: one for accounts that start out with an initial balance

and another for which an initial balance of zero will just have to do. Notice
that this version of
BankAccount
doesn’t take advantage of the latest and
greatest constructor advances you see in the final version of the class in
Chapter 11. By the end of this chapter, that will all be cleaned up, and you’ll
see why I chose to drop back ten yards here.
The
Balance
property allows others to read the balance without giving them
the ability to modify it. The
Deposit()
method accepts any positive deposit.
Withdraw()
lets you take out as much as you want, as long as you have
enough in your account — my bank’s nice, but it’s not that nice.
ToBank
AccountString()
creates a
string
that describes the account.
The
SavingsAccount
class inherits all that good stuff from
BankAccount
. To
that, it adds an interest rate and the ability to accumulate interest at regular
intervals.
Main()
does about as little as it can. It creates a

BankAccount
, displays the
account, creates a
SavingsAccount
, accumulates one period of interest, and
displays the result, with the interest rate in parentheses, as follows:
Account 1001 - $200.00
Account 1002 - $112.50 (12.500%)
Press Enter to terminate...
Notice that the
InitSavingsAccount()
method invokes
InitBank
Account()
. This initializes the bank account–specific data members.
The
InitSavingsAccount()
method could have initialized these members
directly; however, it is better practice to allow the
BankAccount
to initialize
its own members. A class should be responsible for itself.
IS_A versus HAS_A — I’m So Confused
The relationship between
SavingsAccount
and
BankAccount
is the funda-
mental IS_A relationship seen with inheritance. In the following sections, I
show you why and then show you what the alternative, the HAS_A relation-

ship, would look like.
The IS_A relationship
The IS_A relationship between
SavingsAccount
and
BankAccount
is demon-
strated by the following modification to the class
Program
in the
Simple
SavingsAccount
program from the preceding section:
257
Chapter 12: Inheritance — Is That All I Get?
19_597043 ch12.qxd 9/20/05 2:12 PM Page 257
public class Program
{
// We add this:
// DirectDeposit - deposit my paycheck automatically
public static void DirectDeposit(BankAccount ba, decimal mPay)
{
ba.Deposit(mPay);
}
public static void Main(string[] args)
{
// create a bank account and display it
BankAccount ba = new BankAccount();
ba.InitBankAccount(100);
DirectDeposit(ba, 100);

Console.WriteLine(“Account {0}”, ba.ToBankAccountString());
// now a savings account
SavingsAccount sa = new SavingsAccount();
sa.InitSavingsAccount(12.5M);
DirectDeposit(sa, 100);
sa.AccumulateInterest();
Console.WriteLine(“Account {0}”, sa.ToSavingsAccountString());
// wait for user to acknowledge the results
Console.WriteLine(“Press Enter to terminate...”);
Console.Read();
}
}
In effect, nothing has changed. The only real difference is that all deposits are
now being made through the local function
DirectDeposit()
, which isn’t
part of class
BankAccount
. The arguments to this function are the bank
account and the amount to deposit.
Notice (here comes the good part) that
Main()
could pass either a bank
account or a savings account to
DirectDeposit()
because a
Savings
Account
IS_A
BankAccount

and is accorded all the rights and privileges
thereto. Because
SavingsAccount
IS_A
BankAccount
, you can assign a
SavingsAccount
to a
BankAccount
-type variable or method argument.
Gaining access to BankAccount
through containment
The class
SavingsAccount
could have gained access to the members of
BankAccount
in a different way, as shown in the following code, where the
key line is shown in boldface:
// SavingsAccount - a bank account that draws interest
public class SavingsAccount_ // notice the underscore: this is not
// the SavingsAccount class.
{
258
Part IV: Object-Oriented Programming
19_597043 ch12.qxd 9/20/05 2:12 PM Page 258

×