Upload
mas728405
View
221
Download
0
Embed Size (px)
Citation preview
8/7/2019 tgsn 2 oop
1/64
Hiding Data within Object-Oriented Programmingy July 29, 2004
y By Matt Weisfeld
y Bio
y Send Email
y More Articles
Introduction
This is the seventh installment in a series of articles about fundamental object-oriented (OO)
concepts. The material presented in these articles is based on material from the second edition of my
book, The Object-Oriented Thought Process, 2nd
edition. The Object-Oriented Thought Process is
intended for anyone who needs to understand the basic object-oriented concepts before jumping into
the code. Click here to start at the beginning of the series.
Now that we have covered the conceptual basics of classes and objects, we can start to explore
specific concepts in more detail. Remember that there are three criteria that are applied to object-
oriented languages: They have to implement encapsulation, inheritance, and polymorphism. Ofcourse, these are not the only important terms, but they are a great place to start a discussion.
In the previous article, this article, and several of the ones that follow, we will focus on a single
concept and explore how it fits in to the object-oriented model. We will also begin to get much more
involved with code. In keeping with the code examples used in the previous articles, Java will be the
language used to implement the concepts in code. One of the reasons that I like to use Java is
because you can download the Java compiler for personal use at the Sun Microsystems Web
site http://java.sun.com/. You can download the J2SE 1.4.2 SDK (software development kit) to
compile and execute these applications and will provide the code listings for all examples in this
article. I have the SDK 1.4.0 loaded on my machine. I will provide figures and the output for these
examples. See the previous article in this series for detailed descriptions for compiling and running all
the code examples in this series.
Checking Account Example
Recall that in the previous article, we created a class diagram that is represented in the following UML
diagram; see Figure 1.
y Post a comment
y Email Article
y Print Article
y Share Articles
Figure 1: UML Diagram for Checking Account Class
8/7/2019 tgsn 2 oop
2/64
This class is designed to illustrate the concept of encapsulation. This class encapsulates both the
data (attributes) and methods (behaviors). Encapsulation is a fundamental concept of object-oriented
designall objects contain both attributes and behavior. The UML class diagram is composed of only
three parts: the class name, the attributes, and the behaviors. This class diagram maps directly to the
code in Listing 1.
class CheckingAccount {
private double balance = 0;
public void setBalance(double bal) {
balance = bal;
};
public double getBalance(){
return balance;
};
}
Listing 1: CheckingAccount.java
This direct mapping from class diagram to code (and back) is another important issue when designing
classes. This mapping not only helps with the design of the class, but also with the maintenance of
the class. Multiple class diagrams are used to design an object-oriented system and make up what is
termed an object model. Object models will be discussed at length in later articles.
Once a system is complete, the documentation pertaining to the design is obviously very important.
The deployment of a new application marks the beginning of the maintenance phase, which will
continue for the lifetime of the application and contain many, many bug fixes, updates, andenhancements to the system. While the object-model is a great design tool, it is also a great
maintenance tool.
However, as with all documentation, an object model that is not updated as the application changes is
at best useless, and at worst misleading. Anyone who has used flowcharts knows exactly what the
problem is. For example, consider an application that is deployed with flow charts included in the final
documentation. If a bug is identified and fixed in the code, unless the change in logic is retrofitted to
the flow chart, the flow chart is out of date and does not represent the current application. In the
future, when another programmer, or perhaps even the original programmer, revisits the
documentation, it can be misleading. This may lead to a major problem. The same problem can occur
when classes change and the class diagram is not updated properly to reflect the changes.
Why have we taken this interlude about updating documentation at this point? Because there now are
tools to manage much of the documentation process and I believe that anyone wanting to learn
object-oriented design must integrate the knowledge of these tools into their thought process as early
as possible. In short, the process of creating class diagrams and mapping them directly to code (and
back) is tracked, and somewhat controlled, by software tools. In future articles, we will get into much
more detail about the tools themselves. Right now, just be aware that the class diagrams and the
code are tightly coupled.
8/7/2019 tgsn 2 oop
3/64
Note: Some of the products that can help with the process are Rational Rose, owned by IBM and
Visio, owned by Microsoft.
Data Hiding
Returning to the actual CheckingAccount example, we can see that while the class contains both
attributes and behavior, not all of the class is accessible to other class. For example, consider again
the balance attribute. Note that balance is defined as private.
private double balance = 0;
We proved in the last article that attempting to access this attribute directly from an application would
produce an error. The application that produces this error is shown in Listing 2.
class Encapsulation {
public static void main(String args[]) {
System.out.println("Starting myEncapsulation...");
CheckingAccount myAccount = new CheckingAccount();
myAccount.balance = 40.00;
System.out.println("Balance = " + myAccount.getBalance());
}
}
Listing 2: Encapsulation.java
The offending line is the where this main application attempts to set balance directly.
myAccount.balance = 40.00;
This line violates the rule of data hiding. As we saw in last month's article, the compiler does not allow
this; however, it fails to set balance to 40 only because the access was declared asprivate. It is
interesting to note that the Java language, just as C++, C#, and other languages, allows for the
attribute to be declared aspublic. In this case, the main application would indeed be allowed to
directly set the value ofbalance. This then would break the object-oriented concept of data hiding and
would not be considered a proper object-oriented design.
This is one area where the importance of the design comes in. If you abide by the rule that all
attributes areprivate, all attributes of an object are hidden, thus the term data hiding. This is so
important because the compiler now can enforce the data hiding rule. If you declare all of a class's
attributes as private, a rogue developer cannot directly access the attributes from an application.
Basically, you get this protection checking for free.
Whereas the class's attributes are hidden, the methods in this example are designated aspublic.
8/7/2019 tgsn 2 oop
4/64
public void setBalance(double bal) {
balance = bal;
};
public double getBalance(){
return balance;
};
Object Oriented Programming Concepts
3.1 Introduction
The use of Object Oriented (OO) design and Object Oriented Programming (OOP) are becoming
increasingly popular. Thus, it is useful to have an introductory understanding of OOP and some of
the
programming features of OO languages. You can develop OO software in any high level language,
like
C or Pascal. However, newer languages such as Ada, C++, and F90 have enhanced features that make
OOP much more natural, practical, and maintainable. C++ appeared before F90 and currently, is
probably the most popular OOP language, yet F90 was clearly designed to have almost all of the
abilities of
C++ . However, rather than study the new standards many authors simply refer to the two decades
old
F77 standard and declare that Fortran can not be used for OOP. Here we will overcome that
misinformed
point of view.
Modern OO languages provide the programmer with three capabilities that improve and simplify
the design of such programs: encapsulation, inheritance, and polymorphism (or generic
functionality).
Related topics involve objects, classes, and data hiding. An object combines various classical data
types
into a set that denes a new variable type, or structure. A class unies the new entity types and
supporting
8/7/2019 tgsn 2 oop
5/64
data that represents its state with routines (functions and subroutines) that access and/or modify
those
data. Every object created from a class, by providing the necessary data, is called an instance of the
class. In older languages like C and F77, the data and functions are separate entities. An OO language
provides a way to couple or encapsulate the data and its functions into a unied entity. This is a
more
natural way to model real-world entities which have both data and functionality. The encapsulation
is
done with a module block in F90, and with a class block in C++. This encapsulation also includes
a mechanism whereby some or all of the data and supporting routines can be hidden from the user.
The
accessibility of the specications and routines of a class is usually controlled by optional public and
private qualiers. Data hiding allows one the means to protect information in one part of a
program
from access, and especially from being changed in other parts of the program. In C++ the default is
that data and functions are private unless declared public, while F90 makes the opposite choice
for
its default protection mode. In a F90 module it is the contains statement that, among other
things,
couples the data, specications, and operators before it to the functions and subroutines that follow
it.
Class hierarchies can be visualized when we realize that we can employ one or more previously
dened classes (of data and functionality) to organize additional classes. Functionality programmed
into
the earlier classes may not need to be re-coded to be usable in the later classes. This mechanism is
called
inheritance. For example, if we have dened an Employee class, thena Manager class would
inherit all of the data and functionality of an employee. We would then only be required to add only
the totally new data and functions needed for a manager. We may also need a mechanism to re-
dene
specic Employee class functions that differ for a Manager class. By using the concept of a class
8/7/2019 tgsn 2 oop
6/64
hierarchy, less programming effort is required to create the nal enhanced program. In F90 the
earlier
class is brought into the later class hierarchy by the use statement followed by the name of the
module
statement block that dened the class.
Polymorphism allows different classes of objects that share some common functionality to be used
in
code that requires only that common functionality. In other words, routines having the same generic
name
c 2001 J.E. Akin 33are interpreted differently depending on the class of the objects presented as
arguments to the routines.
This is useful in class hierarchies where a small number of meaningful function names can be used to
manipulate different, but related object classes. The above concepts are those essential to objectoriented
design and OOP. In the later sections we will demonstrate by example additional F90
implementations
of these concepts.
3.2 Encapsulation, Inheritance, and Polymorphism
We often need to use existing classes to dene new classes. The two ways to do this are called
composition
and inheritance. We will use both methods in a series of examples. Consider a geometry program
that uses two different classes: class Circle and class Rectangle, as represented graphically in
Figs. 3.1 and 3.2. and as partially implemented in F90 as shown in Fig. 3.3. Each class shown has the
data types and specications to dene the object and the functionality to compute their respective
areas
(lines 322). The operator % is employed to select specic components of a dened type. Within the
geometry (main) program a single routine, compute area, is invoked (lines 38 and 44) to return the
area for any of the dened geometry classes. That is, a generic function name is used for all classes
of its arguments and it, in turn, branches to the corresponding functionality supplied with the
argument
class. To accomplish this branching the geometry program rst brings in the functionality of the
desired
8/7/2019 tgsn 2 oop
7/64
classes via a use statement for each class module (lines 25 and 26). Those modules are coupled
to
the generic function by an interface block which has the generic function name compute area
(lines
28, 29). There is included a module procedure list which gives one class routine name for each ofthe
classes of argument(s) that the generic function is designed to accept. The ability of a function to
respond
differently when supplied with arguments that are objects of different types is called polymorphism.
In this example we have employed different names, rectangular area and circle area, in their
respective class modules, but that is not necessary. The use statement allows one to rename the
class
routines and/or to bring in only selected members of the functionality.
Circle Class
radius
make_Circle
real
real pi
Circle
real
Circle
Circle_Area
Circle
Figure 3.1: Representation of a Circle Class
Another terminology used in OOP is that of constructors and destructors for objects. An intrinsic
constructor is a system function that is automatically invoked when an object is declared with all of
its
possible components in the dened order (see lines 37 and 43). In C++, and F90 the intrinsic
constructor
has the same name as the type of the object. One is illustrated in the statement
8/7/2019 tgsn 2 oop
8/64
four sides = Rectangle (2.1,4.3)
where previously we declared
type (Rectangle) :: four sides
which, in turn, was coupled to the class Rectangle which had two components, base and height,
dened in that order, respectively. The intrinsic constructor in the example statement sets
component
c 2001 J.E. Akin 34Figure 3.2: Representation of a Rectangle Class
[ 1] ! Areas of shapes of different classes, using different
[ 2] ! function names in each class
[ 3] module class Rectangle ! define the first object class
[ 4] implicit none
[ 5] type Rectangle
[ 6] real :: base, height ; end type Rectangle
[ 7] contains ! Computation of area for rectangles.
[ 8] function rectangle area ( r ) result ( area )
[ 9] type ( Rectangle ), intent(in) :: r
[10] real :: area
[11] area = r%base * r%height ; end function rectangle area
[12] end module class Rectangle
[13]
[14] module class Circle ! define the second object class
[15] real :: pi = 3.1415926535897931d0 ! a circle constant
[16] type Circle
[17] real :: radius ; end type Circle
[18] contains ! Computation of area for circles.
[19] function circle area ( c ) result ( area )
[20] type ( Circle ), intent(in) :: c
[21] real :: area
8/7/2019 tgsn 2 oop
9/64
[22] area = pi * c%radius**2 ; end function circle area
[23] end module class Circle
[24]
[25] program geometry ! for both types in a single function
[26] use class Circle
[27] implicit none
[28] use class Rectangle
[29] ! Interface to generic routine to compute area for any type
[30] interface compute area
[31] module procedure rectangle area, circle area ; end interface
[32]
[33] ! Declare a set geometric objects.
[34] type ( Rectangle ) :: four sides
[35] type ( Circle ) :: two sides ! inside, outside
[36] real :: area = 0.0 ! the result
[37]
[38] ! Initialize a rectangle and compute its area.
[39] four sides = Rectangle ( 2.1, 4.3 ) ! implicit constructor
[40] area = compute area ( four sides ) ! generic function
[41] write ( 6,100 ) four sides, area ! implicit components list
[42] 100 format ("Area of ",f3.1," by ",f3.1," rectangle is ",f5.2)
[43]
[44] ! Initialize a circle and compute its area.
[45] two sides = Circle ( 5.4 ) ! implicit constructor
[46] area = compute area ( two sides ) ! generic function
[47] write ( 6,200 ) two sides, area
[48] 200 format ("Area of circle with ",f3.1," radius is ",f9.5 )
8/7/2019 tgsn 2 oop
10/64
[49] end program geometry ! Running gives:
[50] ! Area of 2.1 by 4.3 rectangle is 9.03
[51] ! Area of circle with 5.4 radius is 91.60885
Figure 3.3: Multiple Geometric Shape Classes
base = 2.1 and component height = 4.3 for that instance, four sides, of the type Rectangle.
This intrinsic construction is possible because all the expected components of the type were
supplied. If
all the components are not supplied, then the object cannot be constructed unless the functionality
of the
c 2001 J.E. Akin 35[ 1] function make Rectangle (bottom, side) result (name)
[ 2] ! Constructor for a Rectangle type
[ 3] implicit none
[ 4] real, optional, intent(in) :: bottom, side
[ 5] type (Rectangle) :: name
[ 6] name = Rectangle (1.,1.) ! default to unit square
[ 7] if ( present(bottom) ) then ! default to square
[ 8] name = Rectangle (bottom, bottom) ; end if
[ 9] if ( present(side) ) name = Rectangle (bottom, side) ! intrinsic
[10] end function make Rectangle
[11] . . .
[12] type ( Rectangle ) :: four sides, square, unit sq
[13] ! Test manual constructors
[14] four sides = make Rectangle (2.1,4.3) ! manual constructor, 1
[15] area = compute area ( four sides) ! generic function
[16] write ( 6,100 ) four sides, area
[17] ! Make a square
[18] square = make Rectangle (2.1) ! manual constructor, 2
[19] area = compute area ( square) ! generic function
[20] write ( 6,100 ) square, area
8/7/2019 tgsn 2 oop
11/64
[21] ! "Default constructor", here a unit square
[22] unit sq = make Rectangle () ! manual constructor, 3
[23] area = compute area (unit sq) ! generic function
[24] write ( 6,100 ) unit sq, area
[25] . . .
[26] ! Running gives:
[27] ! Area of 2.1 by 4.3 rectangle is 9.03
[28] ! Area of 2.1 by 2.1 rectangle is 4.41
[29] ! Area of 1.0 by 1.0 rectangle is 1.00
Figure 3.4: A Manual Constructor for Rectangles
class is expanded by the programmer to accept a different number of arguments.
Assume that we want a special member of the Rectangle class, a square, to be constructed if the
height is omitted. That is, we would use height = base in that case. Or, we may want to construct a
unit square if both are omitted so that the constructor defaults to base = height = 1. Such a manual
constructor, named make Rectangle, is illustrated in Fig. 3.4 (see lines 5, 6). It illustrates some
additional features of F90. Note that the last two arguments were declared to have the additional
type
attributes of optional (line 3), and that an associated logical function present is utilized (lines 6
and 8)
to determine if the calling program supplied the argument in question. That gure also shows the
results
of the area computations for the corresponding variables square and unit sq dened if the
manual
constructor is called with one or no optional arguments (line 5), respectively.
In the next section we will illustrate the concept of data hiding by using the private attribute. The
reader is warned that the intrinsic constructor can not be employed if any of its arguments have
been
hidden. In that case a manual constructor must be provided to deal with any hidden components.
Since
data hiding is so common it is probably best to plan on prividing a manual constructor.
8/7/2019 tgsn 2 oop
12/64
3.2.1 Example Date, Person, and Student Classes
Beforemoving to some mathematical exampleswe will introduce the concept of data hiding and
combine
a series of classes to illustrate composition and inheritancey
First, consider a simple class to dene dates .
and to print them in a pretty fashion, as shown in Figs. 3.5 and 3.6. While other modules will have
access to the Date class they will not be given access to the number of components it contains (3),
nor their names (month, day, year), nor their types (integers) because they are declared private in
the
dening module (lines 5 and 6). The compiler will not allow external access to data and/or routines
declared as private. The module, class Date, is presented as a source include le in Fig. 3.6, and
in the future will be reference by the le name class Date.f90. Since we have chosen to hide all
the user dened components we must decide what functionality we will provide to the users, who
may
have only executable access. The supporting documentation would have to name the public routines
and
describe their arguments and return results. The default intrinsic constructor would be available only
to y
those that know full details about the components of the data type, and if those components arepublic.
These examples mimic those given in Chapter 11 and 8 of the J.R. Hubbard book Programming with
C++, McGraw-Hill,
1994, and usually use the same data for verication.
c 2001 J.E. Akin 36Date Class
month
Date_
integer
integer day
Date
Date
Date
8/7/2019 tgsn 2 oop
13/64
Print_Date
Date
integer year
Date Read_Date
Date Set_Date
Figure 3.5: Graphical Representation of a Date Class
The intrinsic constructor, Date (lines 14 and 34), requires all the components be supplied, but it does
no error or consistency checks. My practice is to also dene a public constructor whose name is
the
same as the intrinsic constructor except for an appended underscore, that is, Date . Its sole purpose
is to
do data checking and invoke the intrinsic constructor, Date. If the function Date (line 10) is declared
public it can be used outside the module class Date to invoke the intrinsic constructor, even if the
components of the data type being constructed are all private. In this examplewe have provided
another
manual constructor to set a date, set Date (line 31),with a variable number of optional arguments.
Also
supplied are two subroutines to read and print dates, read Date (line 27) and print Date (line 16),
respectively.
A sample main program that employs this class is given in Fig. 3.7, which contains sample outputs
as comments. This program uses the default constructor as well as all three programs in the public
class
functionality. Note that the denition of the class was copied in via an include (line 1) statement
and
activated with the use statement (line 4).
Now we will employ the class Date within a class Person which will use it to set the date of
birth (DOB) and date of death (DOD) in addition to the other Person components of name,
nationality, and sex. As shown in Fig. 3.8, we have made all the type components private, but
make all the
supporting functionality public, as represented graphically in Fig. 3.8. The functionality shown
provides
8/7/2019 tgsn 2 oop
14/64
a manual constructor, make Person, routines to set the DOB or DOD, and those for the printing of
most components. The source code for the new Person class is given in Fig. 3.9. Note that the
manual
constructor (line 12) utilizes optional arguments and initializes all components in case they are not
supplied to the constructor. The Date public function from the class Date is inherited to initialize
the DOB and DOD (lines 18, 57, and 62). That function member from the previous module was
activated with the combination of the include and use statements. Of course, the include could
have
been omitted if the compile statement included the path name to that source. A sample main
program
for testing the class Person is in Fig. 3.10 along with comments containing its output. It utilizes the
constructors Date (line 7), Person (line10), and make Person (line 24).
Next, we want to use the previous two classes to dene a class Student which adds something
else special to the general class Person. The student person will have additional private
components for an identication number, the expected date ofmatriculation (DOM), the total
course credit hours
earned (credits), and the overall grade point average (GPA), as represented in Fig. 3.11. The source
lines
for the type denition and selected public functionality are given in Fig. 3.12. There the constructors
are make Student (line 19) and Student (line 47). A testing main program with sample output is
illustrated in Fig. 3.13. Since there are various ways to utilize the various constructors three alternate
methods have been included as comments to indicate some of the programmers options. The rst
two
include statements (lines 1, 2) are actually redundant because the third include automatically brings
in those rst two classes.
c 2001 J.E. Akin 37[ 1] module class Date ! filename: class Date.f90
[ 2] implicit none
[ 3] public :: Date ! and everything not "private"
[ 4]
[ 5] type Date
[ 6] private
8/7/2019 tgsn 2 oop
15/64
[ 7] integer :: month, day, year ; end type Date
[ 8]
[ 9] contains ! encapsulated functionality
[10]
[11] function Date (m, d, y) result (x) ! public constructor
[12] integer, intent(in) :: m, d, y ! month, day, year
[13] type (Date) :: x ! from intrinsic constructor
[14] if ( m < 1 .or. d < 1 ) stop Invalid components, Date
[15] x = Date (m, d, y) ; end function Date
[16]
[17] subroutine print Date (x) ! check and pretty print a date
[18] type (Date), intent(in) :: x
[19] character (len=*),parameter :: month Name(12) = &
[20] (/ "January ", "February ", "March ", "April ",&
[21] "May ", "June ", "July ", "August ",&
[22] "September", "October ", "November ", "December "/)
[23] if ( x%month < 1 .or. x%month > 12 ) print *, "Invalid month"
[24] if ( x%day < 1 .or. x%day > 31 ) print *, "Invalid day "
[25] print *, trim(month Name(x%month)), , x%day, ", ", x%year;
[26] end subroutine print Date
[27]
[28] subroutine read Date (x) ! read month, day, and year
[29] type (Date), intent(out) :: x ! into intrinsic constructor
[30] read *, x ; end subroutine read Date
[31]
[32] function set Date (m, d, y) result (x) ! manual constructor
[33] integer, optional, intent(in) :: m, d, y ! month, day, year
8/7/2019 tgsn 2 oop
16/64
[34] type (Date) :: x
[35] x = Date (1,1,1997) ! default, (or use current date)
[36] if ( present(m) ) x%month = m ; if ( present(d) ) x%day = d
[37] if ( present(y) ) x%year = y ; end function set Date
[38]
[39] end module class Date
Figure 3.6: Dening a Date Class
[ 1] include class Date.f90 ! see previous figure
[ 2] program main
[ 3] use class Date
[ 4] implicit none
[ 5] type (Date) :: today, peace
[ 6]
[ 7] ! peace = Date (11,11,1918) ! NOT allowed for private components
[ 8] peace = Date (11,11,1918) ! public constructor
[ 9] print *, "World War I ended on " ; call print Date (peace)
[10] peace = set Date (8, 14, 1945) ! optional constructor
[11] print *, "World War II ended on " ; call print Date (peace)
[12] print *, "Enter today as integer month, day, and year: "
[13] call read Date(today) ! create todays date
[14]
[15] print *, "The date is "; call print Date (today)
[16] end program main ! Running produces:
[17] ! World War I ended on November 11, 1918
[18] ! World War II ended on August 14, 1945
[19] ! Enter today as integer month, day, and year: 7 10 1997
[20] ! The date is July 10, 1997
8/7/2019 tgsn 2 oop
17/64
Figure 3.7: Testing a Date Class
3.3 Object Oriented Numerical Calculations
OOP is often used for numerical computation, especially when the standard storage mode for arrays
is
not practical or efcient. Often one will nd specialized storage modes like linked lists, or tree
structures
used for dynamic data structures. Here we should note that many matrix operators are intrinsic to
F90,
so one is more likely to dene a class sparse matrix than a class matrix. However, either
class would allow us to encapsulate several matrix functions and subroutines into a module that
could be
reused easily in other software. Here, we will illustrate OOP applied to rational numbers andintroduce
c 2001 J.E. Akin 38Person Class
name
Person_
character
character nationality
Person
Person
Person
make_Person
Person
integer sex
Person print_DOB
Person print_DOD
Date Date_Of_Birth
Date Date_Of_Death
Person print_Name
Person print_Nationality
8/7/2019 tgsn 2 oop
18/64
Person print_Sex
Person set_DOB
Person set_DOD
Figure 3.8: Graphical Representation of a Person Class
the important topic of operator overloading. Additional numerical applications of OOP will be
illustrated
in later chapters.
3.3.1 A Rational Number Class and Operator Overloading
To illustrate an OOP approach to simple numerical operations we will introduce a fairly complete
rational
number class, called class Rational which is represented graphically in Fig. 3.14. The dening F90
module is given in Fig. 3.15. The type components have been made private (line 5), but not the type
itself, so we can illustrate the intrinsic constructor (lines 38 and 102), but extra functionality has
been
provided to allow users to get either of the two components (lines 52 and 57). The provided routines
shown in that gure are:
add Rational convert copy Rational delete Rational
equal integer gcd get Denominator get Numerator
invert is equal to list make Rational
mult Rational Rational reduce
Procedures with only one return argument are usually implemented as functions instead of
subroutines.
Note that we would form a new rational number,z , as the product of two other rational numbers,x
andy , by invoking the mult Rational function (line 90),
z = mult Rational (x, y)
which returnsz as its result. A natural tendency at this point would be to simply write this asz =
x y . However, before we could do that we would have to have to
tell the operator, *, how to act
when provided with this new data type. This is known as overloading an intrinsic operator. We had
the
8/7/2019 tgsn 2 oop
19/64
foresight to do this when we set up the module by declaring which of the module procedures were
equivalent to this operator symbol. Thus, from the interface operator (*) statement block (line 14)
the system now knows that the left and right operands of the * symbol correspond to the rst and
second arguments in the function mult Rational. Here it is not necessary to overload the assignment
operator, =, when both of its operands are of the same intrinsic or dened type. However, to
convert
c 2001 J.E. Akin 39[ 1] module class Person ! filename: class Person.f90
[ 2] use class Date
[ 3] implicit none
[ 4] public :: Person
[ 5] type Person
[ 6] private
[ 7] character (len=20) :: name
[ 8] character (len=20) :: nationality
[ 9] integer :: sex
[10] type (Date) :: dob, dod ! birth, death
[11] end type Person
[12] contains
[13] function make Person (nam, nation, s, b, d) result (who)
[14] ! Optional Constructor for a Person type
[15] character (len=*), optional, intent(in) :: nam, nation
[16] integer, optional, intent(in) :: s ! sex
[17] type (Date), optional, intent(in) :: b, d ! birth, death
[18] type (Person) :: who
[19] who = Person (" ","USA",1,Date (1,1,0),Date (1,1,0)) ! defaults
[20] if ( present(nam) ) who % name = nam
[21] if ( present(nation) ) who % nationality = nation
[22] if ( present(s) ) who % sex = s
8/7/2019 tgsn 2 oop
20/64
[23] if ( present(b) ) who % dob = b
[24] if ( present(d) ) who % dod = d ; end function
[25]
[26] function Person (nam, nation, s, b, d) result (who)
[27] ! Public Constructor for a Person type
[28] character (len=*), intent(in) :: nam, nation
[29] integer, intent(in) :: s ! sex
[30] type (Date), intent(in) :: b, d ! birth, death
[31] type (Person) :: who
[32] who = Person (nam, nation, s, b, d) ; end function Person
[33]
[34] subroutine print DOB (who)
[35] type (Person), intent(in) :: who
[36] call print Date (who % dob) ; end subroutine print DOB
[37]
[38] subroutine print DOD (who)
[39] type (Person), intent(in) :: who
[40] call print Date (who % dod) ; end subroutine print DOD
[41]
[42] subroutine print Name (who)
[43] type (Person), intent(in) :: who
[44] print *, who % name ; end subroutine print Name
[45]
[46] subroutine print Nationality (who)
[47] type (Person), intent(in) :: who
[48] print *, who % nationality ; end subroutine print Nationality
[49]
8/7/2019 tgsn 2 oop
21/64
[50] subroutine print Sex (who)
[51] type (Person), intent(in) :: who
[52] if ( who % sex == 1 ) then ; print *, "male"
[53] else ; print *, "female" ; end if ; end subroutine print Sex
[54]
[55] subroutine set DOB (who, m, d, y)
[56] type (Person), intent(inout) :: who
[57] integer, intent(in) :: m, d, y ! month, day, year
[58] who % dob = Date (m, d, y) ; end subroutine set DOB
[59]
[60] subroutine set DOD(who, m, d, y)
[61] type (Person), intent(inout) :: who
[62] integer, intent(in) :: m, d, y ! month, day, year
[63] who % dod = Date (m, d, y) ; end subroutine set DOD
[64] end module class Person
Figure 3.9: Denition of a Typical Person Class
an integer to a rational we could, and have, dened an overloaded assignment operator procedure(line
10). Here we have provided the procedure, equal Integer, which is automatically invoked when
we write : type(Rational)y; y = 4. That would be simpler than invoking the constructor called
make rational. Before moving on note that the system does not yet know how to multiply an integer
times a rational number, or visa versa. To do that one would have to add more functionality, such as
a
function, say int mult rn, and add it to the module procedure list associated with the * operator.
A typical main program which exercises most of the rational number functionality is given in Fig.
3.16,
along with typical numerical output. It tests the constructors Rational (line 8), make Rational
c 2001 J.E. Akin 40[ 1] include class Date.f90
[ 2] include class Person.f90 ! see previous figure
8/7/2019 tgsn 2 oop
22/64
[ 3] program main
[ 4] use class Date ; use class Person ! inherit class members
[ 5] implicit none
[ 6] type (Person) :: author, creator
[ 7] type (Date) :: b, d ! birth, death
[ 8] b = Date (4,13,1743) ; d = Date (7, 4,1826) ! OPTIONAL
[ 9] ! Method 1
[10] ! author = Person ("Thomas Jefferson", "USA", 1, b, d) ! NOT if private
[11] author = Person ("Thomas Jefferson", "USA", 1, b, d) ! constructor
[12] print *, "The author of the Declaration of Independence was ";
[13] call print Name (author);
[14] print *, ". He was born on "; call print DOB (author);
[15] print *, " and died on "; call print DOD (author); print *, ".";
[16] ! Method 2
[17] author = make Person ("Thomas Jefferson", "USA") ! alternate
[18] call set DOB (author, 4, 13, 1743) ! add DOB
[19] call set DOD (author, 7, 4, 1826) ! add DOD
[20] print *, "The author of the Declaration of Independence was ";
[21] call print Name (author)
[22] print *, ". He was born on "; call print DOB (author);
[23] print *, " and died on "; call print DOD (author); print *, ".";
[24] ! Another Person
[25] creator = make Person ("John Backus", "USA") ! alternate
[26] print *, "The creator of Fortran was "; call print Name (creator);
[27] print *, " who was born in "; call print Nationality (creator);
[28] print *, ".";
[29] end program main ! Running gives:
8/7/2019 tgsn 2 oop
23/64
[30] ! The author of the Declaration of Independence was Thomas Jefferson.
[31] ! He was born on April 13, 1743 and died on July 4, 1826.
[32] ! The author of the Declaration of Independence was Thomas Jefferson.
[33] ! He was born on April 13, 1743 and died on July 4, 1826.
[34] ! The creator of Fortran was John Backus who was born in the USA.
Figure 3.10: Testing the Date and Person Classes
Student Class
who
Student_
Person
character id [SSN]
Student
Student
Student
make_Student
Student
Date matriculation
Student get_Person
Student print_DOM
integer credits
real gpa
Student print_GPA
Student set_DOM
Figure 3.11: Graphical Representation of a Student Class
(lines 14, 18, 25), and a simple destructor delete Rational (line 38). The intrinsic constructor (line
6) could have been used only if all the attributes were public, and that is considered an undesirable
practice in OOP. The simple destructor actually just sets the deleted number to have a set of
default
8/7/2019 tgsn 2 oop
24/64
components. Later we will see that constructors and destructors often must dynamically allocate
and
deallocate, respectively, memory associated with a specic instance of some object.
c 2001 J.E. Akin 41[ 1] module class Student ! filename class Student.f90
[ 2] use class Person ! inherits class Date
[ 3] implicit none
[ 4] public :: Student, set DOM, print DOM
[ 5] type Student
[ 6] private
[ 7] type (Person) :: who ! name and sex
[ 8] character (len=9) :: id ! ssn digits
[ 9] type (Date) :: dom ! matriculation
[10] integer :: credits
[11] real :: gpa ! grade point average
[12] end type Student
[13] contains ! coupled functionality
[14]
[15] function get person (s) result (p)
[16] type (Student), intent(in) :: s
[17] type (Person) :: p ! name and sex
[18] p = s % who ; end function get person
[19]
[20] function make Student (w, n, d, c, g) result (x) ! constructor
[21] ! Optional Constructor for a Student type
[22] type (Person), intent(in) :: w ! who
[23] character (len=*), optional, intent(in) :: n ! ssn
[24] type (Date), optional, intent(in) :: d ! matriculation
[25] integer, optional, intent(in) :: c ! credits
8/7/2019 tgsn 2 oop
25/64
[26] real, optional, intent(in) :: g ! grade point ave
[27] type (Student) :: x ! new student
[28] x = Student (w, " ", Date (1,1,1), 0, 0.) ! defaults
[29] if ( present(n) ) x % id = n ! optional values
[30] if ( present(d) ) x % dom = d
[31] if ( present(c) ) x % credits = c
[32] if ( present(g) ) x % gpa = g ; end function make Student
[33]
[34] subroutine print DOM (who)
[35] type (Student), intent(in) :: who
[36] call print Date(who%dom) ; end subroutine print DOM
[37]
[38] subroutine print GPA (x)
[39] type (Student), intent(in) :: x
[40] print *, "My name is "; call print Name (x % who)
[41] print *, ", and my G.P.A. is ", x % gpa, "." ; end subroutine
[42]
[43] subroutine set DOM (who, m, d, y)
[44] type (Student), intent(inout) :: who
[45] integer, intent(in) :: m, d, y
[46] who % dom = Date ( m, d, y) ; end subroutine set DOM
[47]
[48] function Student (w, n, d, c, g) result (x)
[49] ! Public Constructor for a Student type
[50] type (Person), intent(in) :: w ! who
[51] character (len=*), intent(in) :: n ! ssn
[52] type (Date), intent(in) :: d ! matriculation
8/7/2019 tgsn 2 oop
26/64
[53] integer, intent(in) :: c ! credits
[54] real, intent(in) :: g ! grade point ave
[55] type (Student) :: x ! new student
[56] x = Student (w, n, d, c, g) ; end function Student
[57] end module class Student
Figure 3.12: Dening a Typical Student Class
When considering which operators to overload for a newly dened object one should consider those
that are used in sorting operations, such as the greater-than,>, and less-than,
8/7/2019 tgsn 2 oop
27/64
[ 8] ! Method 1
[ 9] p = make Person ("Ann Jones","",0) ! optional person constructor
[10] call set DOB (p, 5, 13, 1977) ! add birth to person data
[11] x = Student (p, "219360061", Date (8,29,1955), 9, 3.1) ! public
[12] call print Name (p) ! list name
[13] print *, "Born :"; call print DOB (p) ! list dob
[14] print *, "Sex :"; call print Sex (p) ! list sex
[15] print *, "Matriculated:"; call print DOM (x) ! list dom
[16] call print GPA (x) ! list gpa
[17] ! Method 2
[18] x = make Student (p, "219360061") ! optional student constructor
[19] call set DOM (x, 8, 29, 1995) ! correct matriculation
[20] call print Name (p) ! list name
[21] print *, "was born on :"; call print DOB (p) ! list dob
[22] print *, "Matriculated:"; call print DOM (x) ! list dom
[23] ! Method 3
[24] x = make Student (make Person("Ann Jones"), "219360061") ! optional
[25] p = get Person (x) ! get defaulted person data
[26] call set DOM (x, 8, 29, 1995) ! add matriculation
[27] call set DOB (p, 5, 13, 1977) ! add birth
[28] call print Name (p) ! list name
[29] print *, "Matriculated:"; call print DOM (x) ! list dom
[30] print *, "was born on :"; call print DOB (p) ! list dob
[31] end program main ! Running gives:
[32] ! Ann Jones
[33] ! Born : May 13, 1977
[34] ! Sex : female
8/7/2019 tgsn 2 oop
28/64
[35] ! Matriculated: August 29, 1955
[36] ! My name is Ann Jones, and my G.P.A. is 3.0999999.
[37] ! Ann Jones was born on: May 13, 1977 , Matriculated: August 29, 1995
[38] ! Ann Jones Matriculated: August 29, 1995 , was born on: May 13, 1977
Figure 3.13: Testing the Student, Person, and Date Classes
machines. It includes most of the High Performance Fortran features that are in wide use. Thus,
efcient
use of OOP on parallel machines is available through F90 and F95.
None of the OOP languages have all the features one might desire. For example, the useful concept
of a template which is standard in C++ is not in the F90 standard. Yet the author has found that a
few dozen lines of F90 code will dene a preprocessor that allows templates to be dened in F90and
expanded in line at compile time. The real challenge in OOP is the actual OOA and OOD that must be
completed before programming can begin, regardless of the language employed. For example,
several
authors have described widely different approaches for dening classes to be used in constructing
OO
nite element systems. Additional example applications of OOP in F90 will be given in the following
chapters.
c 2001 J.E. Akin 43Rational Class
numerator
Rational_
integer
Rational
Rational
Rational
make_Rational
reduce
Rational add_Rational
8/7/2019 tgsn 2 oop
29/64
Rational convert
integer denominator
Rational copy_Rational
Rational is_equal_to
integer gcd
Rational Rational
Rational list
Rational mult_Rational
Rational invert
Rational delete_Rational
Rational equal_Rational
Rational get_Denominator
Rational get_Numerator
Figure 3.14: Representation of a Rational Number Class
c 2001 J.E. Akin 44[ 1] module class Rational ! filename: class Rational.f90
[ 2] implicit none
[ 3] ! public, everything but following private routines
[ 4] private :: gcd, reduce
[ 5] type Rational
[ 6] private ! numerator and denominator
[ 7] integer :: num, den ; end type Rational
[ 8]
[ 9] ! overloaded operators interfaces
[ 10] interface assignment (=)
[ 11] module procedure equal Integer ; end interface
[ 12] interface operator (+) ! add unary versions & (-) later
[ 13] module procedure add Rational ; end interface
[ 14] interface operator (*) ! add integer mult Rational, etc
8/7/2019 tgsn 2 oop
30/64
[ 15] module procedure mult Rational ; end interface
[ 16] interface operator (==)
[ 17] module procedure is equal to ; end interface
[ 18] contains ! inherited operational functionality
[ 19] function add Rational (a, b) result (c) ! to overload +
[ 20] type (Rational), intent(in) :: a, b ! left + right
[ 21] type (Rational) :: c
[ 22] c % num = a % num*b % den + a % den*b % num
[ 23] c % den = a % den*b % den
[ 24] call reduce (c) ; end function add Rational
[ 25]
[ 26] function convert (name) result (value) ! rational to real
[ 27] type (Rational), intent(in) :: name
[ 28] real :: value ! decimal form
[ 29] value = float(name % num)/name % den ; end function convert
[ 30]
[ 31] function copy Rational (name) result (new)
[ 32] type (Rational), intent(in) :: name
[ 33] type (Rational) :: new
[ 34] new % num = name % num
[ 35] new % den = name % den ; end function copy Rational
[ 36]
[ 37] subroutine delete Rational (name) ! deallocate allocated items
[ 38] type (Rational), intent(inout) :: name ! simply zero it here
[ 39] name = Rational (0, 1) ; end subroutine delete Rational
[ 40]
[ 41] subroutine equal Integer (new, I) ! overload =, with integer
8/7/2019 tgsn 2 oop
31/64
[ 42] type (Rational), intent(out) :: new ! left side of operator
[ 43] integer, intent(in) :: I ! right side of operator
[ 44] new % num = I ; new % den = 1 ; end subroutine equal Integer
[ 45]
[ 46] recursive function gcd (j, k) result (g) ! Greatest Common Divisor
[ 47] integer, intent(in) :: j, k ! numerator, denominator
[ 48] integer :: g
[ 49] if ( k == 0 ) then ; g = j
[ 50] else ; g = gcd ( k, modulo(j,k) ) ! recursive call
[ 51] end if ; end function gcd
[ 52]
[ 53] function get Denominator (name) result (n) ! an access function
[ 54] type (Rational), intent(in) :: name
[ 55] integer :: n ! denominator
[ 56] n = name % den ; end function get Denominator
(Fig. 3.15, A Fairly Complete Rational Number Class (continued))
c 2001 J.E. Akin 45[ 57] function get Numerator (name) result (n) ! an access function
[ 58] type (Rational), intent(in) :: name
[ 59] integer :: n ! numerator
[ 60] n = name % num ; end function get Numerator
[ 61]
[ 62] subroutine invert (name) ! rational to rational inversion
[ 63] type (Rational), intent(inout) :: name
[ 64] integer :: temp
[ 65] temp = name % num
[ 66] name % num = name % den
[ 67] name % den = temp ; end subroutine invert
[ 68]
8/7/2019 tgsn 2 oop
32/64
[ 69] function is equal to (a given, b given) result (t f)
[ 70] type (Rational), intent(in) :: a given, b given ! left == right
[ 71] type (Rational) :: a, b ! reduced copies
[ 72] logical :: t f
[ 73] a = copy Rational (a given) ; b = copy Rational (b given)
[ 74] call reduce(a) ; call reduce(b) ! reduced to lowest terms
[ 75] t f = (a%num == b%num) .and. (a%den == b%den) ; end function
[ 76]
[ 77] subroutine list(name) ! as a pretty print fraction
[ 78] type (Rational), intent(in) :: name
[ 79] print *, name % num, "/", name % den ; end subroutine list
[ 80]
[ 81] function make Rational (numerator, denominator) result (name)
[ 82] ! Optional Constructor for a rational type
[ 83] integer, optional, intent(in) :: numerator, denominator
[ 84] type (Rational) :: name
[ 85] name = Rational(0, 1) ! set defaults
[ 86] if ( present(numerator) ) name % num = numerator
[ 87] if ( present(denominator)) name % den = denominator
[ 88] if ( name % den == 0 ) name % den = 1 ! now simplify
[ 89] call reduce (name) ; end function make Rational
[ 90]
[ 91] function mult Rational (a, b) result (c) ! to overload *
[ 92] type (Rational), intent(in) :: a, b
[ 93] type (Rational) :: c
[ 94] c % num = a % num * b % num
[ 95] c % den = a % den * b % den
8/7/2019 tgsn 2 oop
33/64
[ 96] call reduce (c) ; end function mult Rational
[ 97]
[ 98] function Rational (numerator, denominator) result (name)
[ 99] ! Public Constructor for a rational type
[100] integer, optional, intent(in) :: numerator, denominator
[101] type (Rational) :: name
[102] if ( denominator == 0 ) then ; name = Rational (numerator, 1)
[103] else ; name = Rational (numerator, denominator) ; end if
[104] end function Rational
[105]
[106] subroutine reduce (name) ! to simplest rational form
[107] type (Rational), intent(inout) :: name
[108] integer :: g ! greatest common divisor
[109] g = gcd (name % num, name % den)
[110] name % num = name % num/g
[111] name % den = name % den/g ; end subroutine reduce
[112] end module class Rational
Figure 3.15: A Fairly Complete Rational Number Class
c 2001 J.E. Akin 46[ 1] include class Rational.f90
[ 2] program main
[ 3] use class Rational
[ 4] implicit none
[ 5] type (Rational) :: x, y, z
[ 6] ! ------- only if Rational is NOT private ----------
[ 7] ! x = Rational(22,7) ! intrinsic constructor if public components
[ 8]
[ 9] x = Rational (22,7) ! public constructor if private components
[10] write (*,("public x = "),advance=no); call list(x)
8/7/2019 tgsn 2 oop
34/64
[11] write (*,("converted x = ", g9.4)) convert(x)
[12] call invert(x)
[13] write (*,("inverted 1/x = "),advance=no); call list(x)
[14]
[15] x = make Rational () ! default constructor
[16] write (*,("made null x = "),advance=no); call list(x)
[17] y = 4 ! rational = integer overload
[18] write (*,("integer y = "),advance=no); call list(y)
[19] z = make Rational (22,7) ! manual constructor
[20] write (*,("made full z = "),advance=no); call list(z)
[21] ! Test Accessors
[22] write (*,("top of z = ", g4.0)) get numerator(z)
[23] write (*,("bottom of z = ", g4.0)) get denominator(z)
[24] ! Misc. Function Tests
[25] write (*,("making x = 100/360, "),advance=no)
[26] x = make Rational (100,360)
[27] write (*,("reduced x = "),advance=no); call list(x)
[28] write (*,("copying x to y gives "),advance=no)
[29] y = copy Rational (x)
[30] write (*,("a new y = "),advance=no); call list(y)
[31] ! Test Overloaded Operators
[32] write (*,("z * x gives "),advance=no); call list(z*x) ! times
[33] write (*,("z + x gives "),advance=no); call list(z+x) ! add
[34] y = z ! overloaded assignment
[35] write (*,("y = z gives y as "),advance=no); call list(y)
[36] write (*,("logic y == x gives "),advance=no); print *, y==x
[37] write (*,("logic y == z gives "),advance=no); print *, y==z
8/7/2019 tgsn 2 oop
35/64
[38] ! Destruct
[39] call delete Rational (y) ! actually only null it here
[40] write (*,("deleting y gives y = "),advance=no); call list(y)
[41] end program main ! Running gives:
[42] ! public x = 22 / 7 ! converted x = 3.143
[43] ! inverted 1/x = 7 / 22 ! made null x = 0 / 1
[44] ! integer y = 4 / 1 ! made full z = 22 / 7
[45] ! top of z = 22 ! bottom of z = 7
[46] ! making x = 100/360, reduced x = 5 / 18
[47] ! copying x to y gives a new y = 5 / 18
[48] ! z * x gives 55 / 63 ! z + x gives 431 / 126
[49] ! y = z gives y as 22 / 7 ! logic y == x gives F
[50] ! logic y == z gives T ! deleting y gives y = 0 / 1
Figure 3.16: Testing the Rational Number Class
c 2001 J.E. Akin 473.5 Exercises
1. Use the class Circle to create a class Sphere that computes the volume of a sphere. Have
a method that accepts an argument of a Circle. Use the radius of the Circle via a new member
get Circle radius to be added to the class Circle.
2. Use the class Circle and class Rectangle to create a class Cylinder that computes
the volume of a right circular cylinder. Have a method that accepts arguments of a Circle and a
height,
and a second method that accepts arguments of a Rectangle and a radius. In the latter member
use the height of the Rectangle via a new member get Rectangle height to be added to the
class Rectangle.
3. Create a vector class to treat vectors with an arbitrary number of real coefcients. Assume that
the
class Vector is dened as follows:
Vector Class
size
8/7/2019 tgsn 2 oop
36/64
assign
integer
Vector
Vector make_Vector
Vector add_Real_to_Vector
Vector add_Vector
real, pointer data (:)
Vector copy_Vector
logical is_equal_to
Vector Vector
real values
Vector normalize_Vector
Vector list
Vector delete_Vector
real dot_Vector
Vector equal_Real
real length
integer size_Vector
Vector subtract_Real
Vector real_mult_Vector
Vector read_Vector
Vector subtract_Vector
Vector Vector_mult_real
Vector Vector_
real Vector_max_value
real Vector_min_value
Overload the common operators of (+) with add Vector and add Real to Vector, () with
8/7/2019 tgsn 2 oop
37/64
subtract Vector and subtract Real, (*) with dot Vector, real mult Vector and Vector mult real, (=) with
equal Real to set all coefcients to a single real number, and (==) with
routine is equal to.
Include two constructors assign and make Vector. Let assign convert a real array into an instance
of a Vector. Provide a destructor, means to read and write a Vector, normalize a Vector, and
determine its
extreme values.
c 2001 J.E. Akin 484. Modify the above Vector class to extend it to a Sparse Vector Class where the
vast majority
of the coefcients are zero. Store and operate only on the non-zero entries.
Sparse_Vector Class
integer non_zeros
Sparse_Vector make_Sparse_Vector
Sparse_Vector add_Real_to_Sparse_Vector
Sparse_Vector add_Sparse_Vector
real, pointer values (:)
el_by_el_Mult
Sparse_Vector
logical is_equal_to
Sparse_Vector Sparse_Vector
Sparse_Vector show_r_v
normalize_Vector
integer largest_index
Sparse_Vector
delete_Sparse_Vector
real dot_Vector
Sparse_Vector equal_Vector
real length
integer rows_of
8/7/2019 tgsn 2 oop
38/64
Sparse_Vector set_element
Sparse_Vector real_mult_Sparse
read_Vector
Sparse_Vector
Sparse_Vector show
Sparse_Vector Sparse_mult_real
integer size_of
Sparse_vector sub_Sparse_Vector
Sparse_Vector sum_Sparse_Vector
integer, pointer rows (:)
real get_element
real norm
normalize_Vector
Sparse_Vector pretty
Sparse_Vector
real Vector_max_value
real Vector_min_value
Sparse_Vector Vector_to_Sparse
8/7/2019 tgsn 2 oop
39/64
What is Object Oriented Programming?
Now we move onto what might have been termed an advanced topic up untilabout 10 years ago. Nowadays 'Object Oriented Programminghas become the
norm. Languages like Java and Python embody the concept so much that you
can do very little without coming across objects somewhere. So what's it allabout?
The best introductions are, in my opinion:
y Object Oriented Analysis by Peter Coad & Ed Yourdon.
y Object Oriented Analysis and Design with Applications by Grady Booch (the
1st edition if you can find it)
y Object Oriented Software Construction by Bertrand Meyer (definitely the
2nd edition of this one)
These increase in depth, size and academic exactitude as you go down the list.For most non professional programmers' purposes the first is adequate. For amore programming focused intro try Object Oriented ProgrammingbyTimothy Budd(2nd edition). This uses several languages to illustrate object
oriented programming techniques. It is much more strongly oriented towards
writing programs than any of the other books which cover the whole gamut oftheory and principle behind object orientation, at the design level as well as at
the code level. Finally for a whole heap of info on all topics OO try the Web
link site at: http://www.cetus-links.org
Assuming you don't have the time nor inclination to researc h all these booksand links right now, I'll give you a brief overview of the concept. ( Note:Some
people find OO hard to grasp others 'get it' right away. Don't worry if you
come under the former category, you can still use objects even without really
'seeing the light'.)
One final point: it is possible to implement an Object Oriented design in a non
OO language through coding conventions, but it's usually an option of last
resort rather than a recommended strategy. If your problem fits well with OO
techniques then it's best to use an OO language. Most modern languages,
including Python, VBScript and JavaScript support OOP quite well. That
having been said I will be using Python throughout all the examples and onlyshowing the basic concepts in VBScript and JavaScript with little additional
explanation.
Data and Function - together
Objects are collections of data and functions that operate on that data. Theseare bound together so that you can pass an object from one part of your
8/7/2019 tgsn 2 oop
40/64
program and they automatically get access to not only the dataattributes but
the operations that are available too. This combining of data and function isthe very essence of Object Oriented Programming and is known
as encapsulation. (Some programming languages make the data invisible to
users of the object and thus require that the data be accessed via the object's
methods. This technique is properly known as data hiding, however in sometexts data hiding and encapsulation are used interchangeably.)
As an example of encapsulation, a string object would store the character
string but also provide methods to operate on that string - search, change case,
calculate length etc.
Objects use a message passingmetaphor whereby one object passes a message
to another object and the receiving object responds by executing one of its
operations, a method. So a method is invokedon receipt of the corresponding
message by the owning object. There are various notations used to represent
this but the most common mimics the access to items in modules - a dot. Thus,for a fictitious widget class:
w = Widget() # create new instance, w, of widget w.paint() # send the message 'paint' to it
This would cause the paint method of the widget object to be invoked.
Defining Classes
Just as data has various types so objects can have different types. These
collections of objects with identical characteristics are collectively known asa class. We can define classes and create instances of them, which are theactual objects. We can store references to these objects in variables in our
programs.
Let's look at a concrete example to see if we can explain it better. We will
create a message class that contains a string - the message text - and a methodto print the message.
class Message:def __init__(self, aString):
self.text = aString
def printIt(self):print self.text
Note 1:One of the methods of this class is called __init__ and it is a special
method called a constructor. The reason for the name is that it is called when anew object instance is created or constructed. Any variables assigned (andhence created in Python) inside this method will be unique to the newinstance. There are a number of special methods like this in Python, nearly all
8/7/2019 tgsn 2 oop
41/64
distinguished by the __xxx__ naming format. The exact timing of when a
constructor is called varies between languages, in Python init gets called afterthe instance has actually been created in memory, in other languages the
constructor actually returms the instance itself. The difference is sufficiently
subtle that you don't usually need to worry about it.
Note 2:Both the methods defined have a first parameter self. The name is aconvention but it indicates the object instance. As we will soon see this
parameter is filled in by the interpreter at run -time, not by the programmer.
Thus printIt is called, on an instance of the class (see below), with noarguments: m.printIt().
Note 3:We called the class Message with a capital 'M'. This is purelyconvention, but it is fairly widely used, not just in Python but i n other OO
languages too. A related convention says that method names should begin with
a lowercase letter and subsequent words in the name begin with uppercase
letters. Thus a method called "calculate current balance" would bewritten: calculateCurrentBal ance.
You may want to briefly revisit the 'Raw Materials' section and look again at
'user defined types'. The Python address example should be a little clearernow. Essentially the only kind of user-defined type in Python is a class. A
class with attributes but no methods (except __init__ ) is effectively
equivalent to a construct called a record orstruct in some programming
languages..
U
sing ClassesHaving defined a class we can now create instances of our Message class and
manipulate them:
m1 = Message("Hello world")m2 = Message("So long, it was short but sweet")
note = [m1, m2] # put the objects in a list for msg in note:
msg.printIt() # print each message in turn
So in essence you just treat the class as if it was a standard Python data type,
which was after all the purpose of the excercise!
What is "self"?
No, it's not a philosophical debate, it's one of the questions most often askedby new Python OOP programmers. Every method definition in a class in
Python starts with a parameter called self. The actual name selfis just aconvention, but like many programming conventions consistency is good so
8/7/2019 tgsn 2 oop
42/64
let's stick with it! (As you'll see later JavaScript has a similar concept but uses
the name this instead.)
So what is self all about? Why do we need it?
Basically self is just a reference to the current instance. When you create aninstance of the class the instance contains its own data (as created by theconstructor) but not of the methods. Thus when we send a message to aninstance and it calls the corresponding method, it does so via an internalreference to the class. It passes a reference to itself (self!) to the method so that
the class code knows which instance to use.
Let's look at a relatively familiar example. Consider a GUI application which
has lots of Button objects. When a user presses a button the method associatedwith a button press is activated - but how does the Button method know which
of the buttons has been pressed? The answer is by referring to the self value
which will be a reference to the actual button instance that was pressed. We'llsee this in practice when we get to the GUI topic a little later.
So what happens when a message is sent to an object? It works like this:
y the client code calls the instance (sending the message in OOP speak).
y The instance calls the class method, passing a reference to itself (self).
y The class method then uses the passed reference to pick up the instance
data for the receiving object.
You can see this in action in this code sequence, notice that we can explicitly
call the class method, as we do in the last line:
>>> class C:... def __init__(self, val): self.val = val... def f(self): print "hello, my value is:", self.val...>>># create two instances >>> a = C(27)>>> b = C(42)>>># first try sending messages to the instances >>> a.f()hello, my value is 27>>> b.f()
hello, my value is 42>>># now call the method explicitly via the class >>> C.f(a)hello, my value is 27
So you see we can call the methods via the instance, in which case Python fillsin the self parameter for us, or explicitly via the class, in which case we need
to pass the self value explicitly.
8/7/2019 tgsn 2 oop
43/64
Now you might be wondering why, if Python can provide the invisible
reference between the instance and its class can't Python also magically fill inthe self by itself? The answer is that Guido van Rossum designed it this way!
Many OOP languages do indeed hide the self parameter, but one of
the guiding principles of Python is that "explicit is better than implicit". You
soon get used to it and after a while not doing it seems strange.
Same thing, Different thing
What we have so far is the ability to define our own types (classes) and createinstances of these and assign them to variables. We can then pass messages tothese objects which trigger the methods we have defined. But there's one last
element to this OO stuff, and in many ways it's the most important aspect of
all.
If we have two objects of different classes but which support the same set of
messages but with their own corresponding methods then we can collect theseobjects together and treat them identically in our program but the objects will
behave differently. This ability to behave differently to the same input
messages is known aspolymorphism.
Typically this could be used to get a number of different graphics objects todraw themselves on receipt of a 'paint' message. A circle draws a very
different shape from a triangle but provided they both have a paint method we,
as programmers, can ignore the difference and just think of them as 'shapes'.
Let's look at an example, where instead of drawing shapes we calculate their
areas:
First we create Square and Circle classes:
class Square:def __init__(self, side):
self.side = sidedef calculateArea(self):
return self.side**2
class Circle:def __init__(self, radius):
self.radius = radius
def calculateArea(self):import mathreturn math.pi*(self.radius**2)
Now we can create a list of shapes (either circles or squares) and then print out
their areas:
list = [Circle(5),Circle(7),Square(9),Circle(3),Square(12)]
8/7/2019 tgsn 2 oop
44/64
for shape in list:print "The area is: ", shape.calculateArea()
Now if we combine these ideas with modules we get a very powerfulmechanism for reusing code. Put the class definitions in a module - say
'shapes.py' and then simply import that module when we want to manipulate
shapes. This is exactly what has been done with many of the standard Pythonmodules, which is why accessing methods of an object looks a lot like using
functions in a module.
Inheritance
Inheritance is often used as a mechanism to implement polymorphism. Indeedin many OO languages it is the only way to implement polymorphism. It
works as follows:
A class can inheritboth attributes and operations from a parentorsuperclass.This means that a new class which is identical to another class in most respects
does not need to re-implement all the methods of the existing class, rather itcan inherit those capabilities and then override those that it wants to do
differently (like the calculateArea method in the case above)
Again an example might illustrate this best. We will use a class hierarchy ofbank accounts where we can deposit cash, obtain the balance and make a
withdrawal. Some of the accounts provide interest (which, for our purposes,we'll assume is calculated on every deposit - an interesting innovation to the
banking world!) and others charge fees for withdrawals.
The BankAccount class
Let's see how that might look. First let's consider the attributes and operationsof a bank account at the most general (orabstract) level.
It's usually best to consider the operations first then provide attributes as
needed to support these operations. So for a bank account we can:
y Depositcash,
y Withdrawcash,
y Check current balance and
y Transferfunds to another account.
To support these operations we will need a bank account ID (for the transfer
operation) and the current balance.
8/7/2019 tgsn 2 oop
45/64
We can create a class to support that:
class BalanceError(Exception):value = "Sorry you only have $%6.2f in your account"
class BankAccount:def __init__(self, i nitialAmount):
self.balance = initialAmountprint "Account created with balance %5.2f" % self.balance
def deposit(self, amount):self.balance = self.balance + amount
def withdraw(self, amount):if self.balance > = amount:
self.balance = self.balance - amountelse:
raise BalanceError, BalanceError.value % self.balance
def checkBalance(self):return self.balance
def transfer(self, amount, account):try:
self.withdraw(amount)account.deposit(amount)
except BalanceError:print BalanceError.value
Note 1: We check the balance before withdrawing and also use an exception
to handle errors. Of course there is no Python error type BalanceError so weneeded to create one of our own - it's simply an subclass of the
standard Exception class with a string value. The string value is defined as an
attribute of the exception class purely as a convenience, it ensures that we cangenerate standard error messages every time we raise an error. When we raise
BalanceError we pass the internal format string value filled in with the current
value of the object's balance. Notice that we didn't use self when defining the
value inBalanceError, that's because value is a shared attribute across allinstances, it is defined at the class level and known as a class variable. We
access it by using the class name followed by a dot: BalanceError.value asseen above. Now, when the error generates its traceback it concludes by
printing out the formatted error string showing the current balance.
Note 2: The transfer method uses
the BankAccount's withdraw/depositmember functions or methods to do thetransfer. This is very common in OO and is known as self messaging. It means
that derived classes can implement their own versions of deposit/withdraw but
the transfer method can remain the same for all account types.
8/7/2019 tgsn 2 oop
46/64
The InterestAccount class
Now we use inheritance to provide an account that adds interest (we'll assume3%) on every deposit. It will be identical to the standard BankAccount class
except for the deposit method. So we simply override that:
class InterestAccount(BankAccount):def deposit(self, amount):
BankAccount.deposit(self,amount)self.balance = self.balance * 1.03
And that's it. We begin to see the power of OOP, all the other methods have
been inherited from BankAccount (by putting BankAccount inside theparentheses after the new class name). Notice also that deposit calledthesuperclass's deposit method rather than copying the code. Now if wemodify the BankAccount deposit to include some kind of error checking
thesub-class will gain those changes automatically.
The ChargingAccount class
This account is again identical to a standard BankAccount class except thatthis time it charges $3 for every withdrawal. As for the InterestAccount we
can create a class inheriting from BankAccount and modifying the withdraw
method.
class ChargingAccount(BankAccount):def __init__(self, initialAmount):
BankAccount.__init__(self, initialAmount)
self.fee = 3
def withdraw(self, amount):BankAccount.withdraw(self, amount+self.f ee)
Note 1: We store the fee as an instance variable so that we can change it later
if necessary. Notice that we can call the inherited __init__ just like any other
method.
Note 2: We simply add the fee to the requested withdrawal and call the
BankAccount withdraw method to do the real work.
Note 3: We introduce a side effect here in that a charge is automatically leviedon transfers too, but that's probably what we want, so is OK.
Testing our system
To check that it all works try executing the following pi ece of code (either at
the Python prompt or by creating a separate test file).
8/7/2019 tgsn 2 oop
47/64
from bankaccount import *
# First a standard BankAccount a = BankAccount(500)b = BankAccount(200)a.withdraw(100)# a.withdraw(1000)
a.transfer(100,b)print "A = ", a.checkBalance()print "B = ", b.checkBalance()
# Now an InterestAccount c = InterestAccount(1000)c.deposit(100)print "C = ", c.checkBalance()
# Then a ChargingAccount d = ChargingAccount(300)d.deposit(200)print "D = ", d.checkBalance()
d.withdraw(50)print "D = ", d.checkBalance()d.transfer(100,a)print "A = ", a.checkBalance()print "D = ", d.checkBalance()
# Finally transfer from charging account to the interest one# The charging one should charge and the interest one add# interestprint "C = ", c.checkBalance()print "D = ", d.checkBalance()d.transfer(20,c)print "C = ", c.checkBalance()
print "D = ", d.checkBalance()
Now uncomment the line a.withdraw(1000) to see the exception at work.
That's it. A reasonably straightforward example but it shows how inheritance
can be used to quickly extend a basic framework with powerful new features.
We've seen how we can build up the example in stages and how we can puttogether a test program to check it works. Our tests were not complete in that
we didn't cover every case and there are more checks we could have included -
like what to do if an account is created with a negative amount...
Collections of Objects
One problem that might have occurred to you is how we deal with lots ofobjects. Or how to manage objects which we create at runtime. It's all very
well creating Bank Accounts statically as we did above:
acc1 = BankAccount(...)acc2 = BankAccount(...)
8/7/2019 tgsn 2 oop
48/64
acc3 = BankAccount(...)etc...
But in the real world we don't know in advance how many accounts we need
to create. How do we deal with this? Lets consider the problem in more detail:
We need some kind of 'database' that allows us to find a given bank accountby its owners name (or more likely their bank account number - since one
person can have many accounts and several persons can have the same
name...)
Finding something in a collection given a unique key....hmmm, sounds like a
dictionary! Lets see how we'd use a Python dictionary to hold dynamically
created objects:
from bankaccount import *import time
# Create new function to generate unique id numbers def getNextID():
ok = raw_input("Create account[y/n]? ")if ok[0] in 'yY': # check valid input
id = time.time() # use current time as basis of ID id = int(id) % 10000 # convert to int and shorten to 4 digits
else: id = -1 # which will stop the loop return id
# Let's create some accounts and store them in a dictionary accountData = {} # new dictionary while 1: # loop forever
id = getNextID()if id == -1:
break # break forces an exit from the while loop bal = float(raw_input("Opening Balance? ")) # convert string to
floataccountData[id] = BankAccount(bal) # use id to create new dictionary
entryprint "New account crea ted, Number: %04d, Balance %0.2f" % (id,bal)
# Now let's access the accounts for id in accountData.keys():
print "%04d\t%0.2f" % (id,accountData[id].checkBalance())
# and find a particular one # Enter non-number to force exception and end program while 1:
id = int(raw_input("Which account number? "))if id in accountData.keys():
print "Balance = %0.2d" % accountData[id].checkBalance()else: print "Invalid ID"
Of course the key you use for the dictionary can be anything that uniquely
identifies the object, it could be one of its attributes, like balance say (exceptthat balance would not be a very good unique key!). Anything at all that is
8/7/2019 tgsn 2 oop
49/64
unique. You might find it worthwhile going back to the raw materials chapter
and reading the dictionary section again, they really are very useful containers.
Saving Your Objects
One snag with all of this is that you lose your data when the program ends.You need some way of saving objects too. As you get more advanced you will
learn how to use databases to do that but we will look at using a simple textfile to save and retrieve objects. (If you are using Python there are a couple of
modules called Pickle and Shelve) that do this much more effectively but as
usual I'll try to show you the generic way to do it that will work in anylanguage. Incidentally the technical term for the ability to save and restore
objects isPersistence.
The generic way to do this is to create save and restore methods at the highestlevel object and override in each class, such that they call the inherited version
and then add their locally defined attributes:
class A:def __init__(self,x,y):self.x = xself.y = y
def save(self,fn):f = open(fn,"w")f.write(str(self.x)+ ' \n') # convert to a string and add newline f.write(str(self.y)+' \n')return f # for child objects to use
def restore(self, fn):
f = open(fn)self.x = int(f.readline()) # convert back to original type self.y = int(f.readline())return f
class B(A):def __init__(self,x,y,z):A.__init__(self,x,y)self.z = z
def save(self,fn):f = A.save(self,fn) # call parent save f.write(str(self.z)+'\n')return f # in case further children exist
def restore(self, fn):f = A.restore(self,fn)self.z = int(f.readline())return f
# create instances a = A(1,2)b = B(3,4,5)
# save the instances
8/7/2019 tgsn 2 oop
50/64
a.save('a.txt').close() # remember to close the file b.save('b.txt').close()
# retrieve instances newA = A(5,6)newA.restore('a.txt').close() # remember to close the file newB = B(7,8,9)
newB.restore('b.txt').close()print "A: ",newA.x,newA.yprint "B: ",newB.x,newB.y,newB.z
Note: The values printed out are the restored values not the ones we used to
create the instances.
The key thing is to override the save/restore methods in each class and to call
the parent method as the first step. Then in the child class only deal with childclass attributes. Obviously how you turn an attribute into a string and save it is
up to you the programmer but it must be output on a single line. When
restoring you simply reverse the storing process.
Mixing Classes and Modules
Modules and classes both provide mechanisms for controlling the complexityof a program. It seems reasonable that as programs get bigger we would wantto combine these features by putting classes into modules. Some authorities
recommend putting each class into a separate module but I find this simplycreates an explosion of modules and increases rather than decreases
complexity. Instead I group classes together and put the group into a module.
Thus in our example above I might put all the bank account class definitions in
one module, bankaccount, say, and then create a separate module for theapplication code that uses the module. A simplified representation of that
would be:
# File: bankaccount.py## Implements a set of bank account classes###################
class BankAccount: ....
class InterestAccount: ...
class ChargingAccount: ...
And then to use it:
import bankaccount
newAccount = bankaccount.BankAccount(50)newChrgAcct = bankaccount.ChargingAccount(200)
8/7/2019 tgsn 2 oop
51/64
# now do stuff
But what happens when we have two classes in different modules that need to
access each others details? The simplest way is to import both modules, create
local instances of the classes we need and pass the instances of one class to theother instances methods. Passing whole objects around is what m akes itobject orientedprogramming. You don't need to extract the attributes out ofone object and pass them into another, just pass the entire object. Now if thereceiving object uses a polymorphic message to get at the information it needs
then the method will work with any kind of object that supports the message.
Let's make that more concrete by looking at an example. Let's create a shortmodule called logger that contains two classes. The first logs activity in a file.
This logger will have a single method log() which has a "loggable object" as a
parameter. The other class in our module is a Loggable class that can be
inherited by other classes to work with the logger. It looks like this:
# File: logger.py## Create Loggable and Logger classes for logging activities# of objects############
class Loggable:def activity(self):
return "This needs to be overridden locally"
class Logger:def __init__(self, logfilename = "logger.dat"):
self._log = open(logf ilename,"a")
def log(self, loggedObj):self._log.write(loggedObj.activity() + ' \n')
def __del__(self):self._log.close()
Note that we have provided a destructormethod (__del__) to close the filewhen the logger object is deleted or garbage collected. This is another "magic
method" in Python (as shown by the double '_' characters) similar in many
ways to __init__()
Also notice that we've called the log attribute _log with a '_' character in frontof the name. This is another common naming convention in Python, like usingcapitalized words for class names. A single underscore indicates that the
attribute is not intended to be accessed directly, but only via the methods of
the class.
8/7/2019 tgsn 2 oop
52/64
Now before we can use our module we will create a new module which
defines loggable versions of our bank account classes:
# File: loggablebankaccount.py#
# Extend Bank account classes to work with logger module.###############################
import bankaccount, logger
class LoggableBankAccount( bankaccount.BankAccount, logger.Loggable):def activity(self):
return "Account balance = %d" % self.checkBalance()
class LoggableInterestAccount(bankaccount.InterestAccount,logger.Loggable):
def activity(self):return "Account balance = %d" % self.checkBalance()
class LoggableChargingAccount(bankaccount.ChargingAccount,
logger.Loggable):def activity(self):
return "Account balance = %d" % self.checkBalance()
Notice we are using a feature called multiple inheritance, where we inherit notone but two parent classes. This isn't strictly needed in Python since we could
just have added an activity() method to our original classes and achieved the
same effect but in statically typed OOP languages such as Java or C++ thistechnique would be necessary so I will show you the technique here for future
reference.
The sharp eyed amongst you may have noticed that the activity() method inall three classes is identical. That means we could save ourselves some typing
by creating an intermediate type of loggable account class that inheritsLoggable and only has an activity method. We can then create our three
different loggable account types by inheriting from that new class as wel l as
from the vanilla Loggable class. Like this:
class LoggableAccount(logger.Loggable):def activity(self):
return "Account balance = %d" % self.checkBalance()
class LoggableBankAccount(bankaccount.BankAccount, LoggableAccount):
pass
class LoggableInterestAccount(bankaccount.InterestAccount,LoggableAccount):
pass
class LoggableChargingAccount(bankaccount.ChargingAccount,LoggableAccount):
pass
8/7/2019 tgsn 2 oop
53/64
It doesn't save a lot of code but it does mean we only have one method
definition to test and maintain instead of three identical methods. This type ofprogramming where we introduce a superclass with shared functionality is
sometimes called mixin programmingand the minimal class is called a mixin
class. It is a common outcome of this style that the final class definitions have
little or no body but a long list of inherited classes, just as we see here. It's alsoquite common that mixin classes do not themselves inherit from anything,
although in this case we did. In essence it's just a way of adding a commonmethod (or set of methods) to a class or set of classes via the power of
inheritance. (The term mixin originates in the world of ice cream parlours
where different flavours of ice cream are added (or mixed i