Tải bản đầy đủ - 0trang
Chapter 4. Object-Oriented Programming in C #
Review of Object-Oriented Concepts
In this preliminary section we review the fundamentals of object-oriented
programming. If you are an experienced C++ or Java programmer, you may skim
through this section as a refresher and begin your careful reading with the next
section, where we introduce the case study.
Objects have both a real-world and a software meaning. The object model
describes a relationship between them.
Objects in the Real World
The term object has an intuitive real-world meaning. There are concrete, tangible
objects, such as a ball, an automobile, and an airplane. There are also more
abstract objects that have a definite intellectual meaning, such as a committee, a
patent, or an insurance contract.
Objects have both attributes (or characteristics) and operations that can be
performed upon them. A ball has a size, a weight, a color, and so on. Operations
may be performed on the ball, such as throw, catch, and drop.
There can be various types of relationships among classes of objects. One, for
example, is a specialization relationship, such as an automobile is a special kind
of vehicle. Another is a whole/part relationship, such as an automobile consists of
an engine, a chassis, wheels, and other parts.
Objects can also be used in programs. Objects are useful in programming because
you can set up a software model of a real-world system. Software objects abstract
the parts of objects in the real world that are relevant to the problem being solved.
The model can then be implemented as software using a programming language.
A software system implemented in this way tends to be more faithful to the real
system, and it can be changed more readily when the real system is changed.
There are formal languages for describing object models. The most popular
language is UML (Unified Modeling Language), which is a synthesis of several
earlier modeling languages. Formal modeling languages are beyond the scope of
this book, but we will find that informal models are useful.
Reusable Software Components
Another advantage of objects in software is that they can facilitate reusable
software components. Hardware has long enjoyed significant benefits from
reusable hardware components. For example, computers can be created from
power supplies, printed circuit boards, and other components. Printed circuit
boards in turn can be created from chips. The same chip can be reused in many
different computers, and new hardware designs do not have to be done from
With appropriate software technology, similar reuse is feasible in software
systems. Objects provide the foundation for software reuse.
Objects in Software
An object is a software entity containing data (state) and related functions
(behavior) as a self-contained module. For example, a HotelBroker may contain
a list of hotels (the state) and provide operations to add a hotel and make a
An abstraction captures the essential features of a real-world object, suppressing
unnecessary details. All instances of an abstraction share these common features.
Abstraction helps us deal with complexity. For example, consider the problem of
booking a reservation. There are many different kinds of things you might want to
reserve, such as a hotel, an airplane flight, or a conference room. Such
"reservables" have many differences, but they have certain essentials in common.,
such as a capacity.
The implementation of an abstraction should be hidden from the rest of the
system, or encapsulated. For example, the list of hotels may be contained in
several different kinds of data structures, such as an array, a collection, or a
database. The rest of the system should not need to know the details of the
A class groups all objects with common behavior and common structure. A class
allows creation of new objects of the same type. An object is an instance of some
class. We refer to the process of creating an individual object as instantiation.
Classes can be related in various ways, such as by inheritance and by
Inheritance is a key feature of the object-oriented programming paradigm. You
abstract out common features of your classes and put them in a highlevel base
class. You can add or change features in more specialized derived classes, which
"inherit" the standard behavior from the base class. Inheritance facilitates code
reuse and extensibility.
Consider Reservable as a base class, with derived classes Hotel and Flight. All
reservables share some characteristics, such as a capacity. Different kinds of
reservables differ in other respects. For example, a hotel has a city and a name,
while a flight has an origin and a destination. Figure 4-1 illustrates the
relationship among these different kinds of reservables.
Figure 4-1. Inheritance relationship among different reservable
Sometimes a class is not meant to be instantiated, but only to provide a template
for derived classes. The Reservable class is an example—it is too abstract to
actually instantiate. Only specific kinds of reservable classes, such as Hotel and
Flight, may actually be instantiated. We call a class such as Reservable that
cannot be instantiated an abstract class. A class that can be instantiated is called a
Relationships Among Classes
Classes may be related to each other in various ways.
The inheritance (IS-A) relationship specifies how one class is a special
case of another class. A Hotel (subclass or derived class) is a special kind
of Reservable (superclass or base class).
The composition (HAS-A) relationship specifies how one class (the
whole) is made up of other classes (the parts). A HotelBroker (whole) has
a list of Hotel objects.
A weaker kind of relationship (USES-A) can be identified when one class
merely makes use of some other class when carrying out its
Consider the problem of generating a payroll for various categories of employees.
Different kinds of employees may have pay calculated in a different manner. A
salaried employee receives a fixed salary. A wage employee is paid according to
the number of hours worked. A sales employee is paid according to the
commissions earned on sales that were made.
A traditional approach is to maintain a type field in an employee structure and to
perform processing in a switch statement, with cases for each type. Such use of
switch statements is error prone and requires much maintenance when adding a
new employee type.
An alternative is to localize the intelligence to calculate pay in each employee
class, which will support its own GetPay method. Generic payroll code can then
be written that will handle different types of employees and will not have to be
modified to support an additional employee type. Provide a GetPay method in
the base class and an override of this method in each derived class. Call GetPay
through an object reference to a general Employee object. Depending on the
actual employee class referred to, the appropriate GetPay method will be called.
The ability for the same method call to result in different behavior depending on
the object through which the method is invoked is referred to as polymorphism.
Polymorphism can greatly simplify complex systems and is an important part of
the object-oriented paradigm.
You should not try to coerce your design so that you can take advantage of
polymorphism. We will see in our Acme Travel Agency case study that we have
three different abstract base classes, but we do not need polymorphism to achieve
quite general behavior. On the other hand, the .NET Framework classes use
polymorphism heavily, as we shall see beginning in Chapter 5. Later in this
chapter we will provide a small example of polymorphism using an employee
class hierarchy, as outlined above.
Acme Travel Agency Case Study: Design
The Acme Travel Agency provides various services, including the booking of hotel, plane,
and car rental reservations. We will use this simple theme of booking reservations to
illustrate various features of .NET throughout the book. In this chapter we design the
architecture of a general system for booking different kinds of reservations. We illustrate
the reservation system with an implementation of a hotel broker system that supports the
following basic features:
Add a hotel to the list of hotels
Show all the hotels
Show all the hotels in a particular city
Reserve a hotel room for a range of dates
Show all the reservations
Show all the reservations for a particular customer
The system also maintains a list of customers. Customers may register by giving their
name and email address, and they will be assigned a customer ID. The following features
are supported in the basic customer management subsystem:
Register as a customer
Change the email address of a customer
Show a single customer or all the customers
In this chapter various lists, such as hotels, reservations, and customers, will be maintained
as arrays. In the next chapter we will use .NET collections in place of arrays, and we will
implement more features, such as the ability to delete a hotel, cancel a reservation, and the
like. In later chapters we will extend the case study in various ways, such as providing a
graphical user interface, storing all data in a database, deploying as a Web application, and
The code for our case study is in the CaseStudy folder for this chapter.
Designing the Abstractions
Bearing in mind that eventually we want to implement not only a hotel reservation system,
but also a system for other kinds of reservations, including plane and car rental, it behooves
us at the beginning to look for appropriate abstractions. The more functionality we are able
to put in base classes, the less work we will have to do in order to implement a particular
kind of reservation system. On the other hand, having more functionality in the base
classes can reduce the range of problems to which they are applicable. Good design is a
Another attribute of good abstractions is that they will survive major changes in
implementation. As we shall see later in this book, our C# abstractions of the hotel
reservation system remain intact as we implement the system on an SQL Server database.
These abstractions will be represented in C# by abstract classes, defined in the file
Broker.cs in the CaseStudy folder for this chapter.
Our first abstraction is the thing we are looking to reserve. We will denote this abstraction
as simply Reservable. The basic issue in reservations is resource usage. There are a
limited number of reservable resources. Hence the key attribute of a Reservable is
capacity. For example, a hotel may have 100 rooms. A flight may have 250 seats. We will
also want a unique identifier for a Reservable, which we will denote by unitid. (The
shorter name unitid is used in preference to the longer, more awkward name reservableid.
Later we will see other uses of the terminology "unit." For example, the method to add a
reservable is called AddUnit.)
For our applications we are going to introduce an additional attribute, cost. There is a room
rate for a hotel, a ticket cost for a flight, and so on. Note that this attribute may not be
applicable to all things that are being reserved. For example, a conference room within a
company may not have a cost assigned to it. However, our applications are for commercial
customers, so we choose to include cost in our model.
Because our case study is designed to illustrate concepts in C# and .NET, we
will choose many simplifications in our design, so that we do not become
bogged down in too detailed coding. For example, in real life a hotel has several
different kinds of rooms, each having a different rate. Similarly, an airplane
flight will have different classes of seats. Here the situation in real life is even
more complicated, because the price of a seat may vary wildly depending on
when the reservation was made, travel restrictions, and so on. To make life
simple for us, we are assuming that each instance of a particular reservable will
have the same cost.
In C# we will represent a Reservable by an abstract class.
public abstract class Reservable
static private int nextid = 0;
protected int unitid;
internal protected int capacity;
internal protected decimal cost;
public Reservable(int capacity, decimal cost)
this.capacity = capacity;
this.cost = cost;
unitid = nextid++;
A constructor allows us to specify the capacity and cost when the object is created. The
unitid is autogenerated by a static variable. This id starts out at 0, because it is also going
to be used in our implementation as an index in a two-dimensional array to track the
number of customers having a reservation at a given reservable on a given date.
We will discuss the role of the private, internal, and protected access control specifiers
When a customer books a reservation of a reservable, a record of the reservation will be
made. The Reservation class holds the information that will be stored.
public abstract class Reservation
public int ReservationId;
public int UnitId;
public DateTime Date;
public int NumberDays;
static private int nextReservationId = 1;
ReservationId = nextReservationId++;
The ReservationId is autogenerated. The UnitId identifies the reservable that was booked.
Date is the starting date of the reservation, and NumberDays specifies the number of days
for which the reservation was made.
Our third abstraction, Broker, models a broker of any kind of reservable, and is also
represented by an abstract class. It maintains a list of reservables, represented by the array
units, and a list of reservations, represented by the array reservations. The twodimensional array numCust keeps track of the number of customers having a reservation
at a given reservable on a given day.
public abstract class Broker
private int MaxDay;
private const int MAXRESERVATION = 10;
private static int nextReservation = 0;
private static int nextUnit = 0;
private int[,] numCust;
protected Reservation reservations;
protected Reservable units;
public Broker(int MaxDay, int MaxUnit)
this.MaxDay = MaxDay;
numCust = new int[MaxDay, MaxUnit];
units = new Reservable[MaxUnit];
reservations = new Reservation[MAXRESERVATION];
A simple structure is used for returning the result from making a reservation.
public struct ReservationResult
public int ReservationId;
public decimal ReservationCost;
public decimal Rate;
public string Comment;
The Rate is the cost for one day, and ReservationCost is the total cost, which is equal to
the number of days multiplied by the cost for one day. The ReservationId is returned as -1
if there was a problem, and an explanation of the problem is provided in the Comment
field. This structure is created so that result information can be passed in distributed
scenarios, such as Web Services, where you cannot throw exceptions.
Base Class Logic
The base class Broker not only represents the abstraction of a broker of any kind of
reservable. It also contains general logic for booking reservations and maintaining a list of
reservations. Our ability to capture this logic abstractly gives the power to this base class
and will make implementing reservations in a derived class relatively simple.
The core method of the Broker class is Reserve.
protected ReservationResult Reserve(Reservation res)
int unitid = res.UnitId;
DateTime dt = res.Date;
int numDays = res.NumberDays;
ReservationResult result = new ReservationResult();
// Check if dates are within supported range
int day = dt.DayOfYear - 1;
if (day + numDays > MaxDay)
result.ReservationId = -1;
result.Comment = "Dates out of range";
// Check if rooms are available for all dates
for (int i = day; i < day + numDays; i++)
if (numCust[i, unitid] >= units[unitid].capacity)
result.ReservationId = -1;
result.Comment = "Room not available";
// Reserve a room for requested dates
for (int i = day; i < day + numDays; i++)
numCust[i, unitid] += 1;
// Add reservation to reservation list and
// return result
result.ReservationId = res.ReservationId;
result.ReservationCost = units[unitid].cost * numDays;
result.Rate = units[unitid].cost;
result.Comment = "OK";
The Reserve method is designed to implement booking several different kinds of
reservations. Thus the Reservation object, which will be stored in the list of reservations,
is created in a more specialized class derived from Broker and is passed as a parameter to
Reserve. For example, a HotelBroker will book a HotelReservation, and so on. The
UnitId, Date, and NumberDays fields are extracted from the Reservation object, and a
ReservationResult object is created to be returned.
protected ReservationResult Reserve(Reservation res)