Java Programming, Lecture Notes # 4, Revised 07/25/01.
Preface
Introduction
An Introductory Description of OOP
Encapsulation
Inheritance
Polymorphism
Exception Handling
Intro to Object-Oriented Design
OOD/OOP Versus Traditional Techniques
Minimum Knowledge Requirements for OOD/OOP
Program Specification
Conclusions Regarding Minimal Knowledge
Analysis and Design Results, Summary
Discussion
Interesting Code Fragments for Thermostat Program
Program Listing for Thermostat Program
Noun/Verb Analysis and Design
Review
By the end of the course, students in Prof. Baldwin's Introductory Java Programming classes at ACC are responsible for knowing and understanding all of the material in this lesson (except that they are not responsible for detailed information that is specific to C++).
The detailed material on C++ is provided as supplementary material for the benefit of persons already familiar with C++ who are making the transition into Java.
This lesson is designed primarily as a reading assignment. It is not likely that Prof. Baldwin will spend any class time discussing the material in this lesson. However, he will respond to specific questions from students regarding the material in this lesson.
This lesson is intended to be general in nature. Therefore, no effort has been expended to keep it in compliance with any particular version of the Java JDK.
This lesson is a compilation of ideas and examples (a few of which are my own) from a variety of authors. Let me apologize in advance to those authors whose ideas I may have included without giving specific credit. If you, as an author, see one of your original ideas here without proper credit being given, please notify me by E-mail and I will be pleased to footnote the document to give you credit for that specific idea (or remove your idea from the document if that is your preference).
This lesson concentrates on Object-Oriented Programming (OOP). The lesson is intended to be independent of any specific language. As a practical matter, it is necessary to use some language for illustration purposes, and rather than to conjure up some artificial language, examples in this lesson are provided using either the Java or the C++ programming language.
Some languages such as C do not readily support OOP. Other languages such as C++ support OOP, but don't require you to use the object-oriented features of the language.
Still other languages such as Java require you to program using OOP techniques. In particular, it is not possible to write a program in Java without taking an object-oriented approach. You may do a good job, or you may do a poor job, but you must program using objects in Java.
Since C++ does not enforce a requirement to write object-oriented programs, it is possible to learn to use major aspects of the C++ language without ever learning to use the object-oriented features. In that case, the major challenge is simply learning to use the rudiments of the language.
On the other hand, the real challenge in becoming a Java programmer is not simply to learn the language. Even though the language contains some very sophisticated features such as multi-threading, it is not a complex language and is not particularly difficult to learn. Persons capable of programming in Pascal or C should be able to master the core aspects of the language with a modest effort.
The real challenge in becoming a Java programmer lies in two areas:
The first of these challenges can be met on a gradual basis. In other words, it is not necessary to know the entire class library to produce useful Java programs.
The second challenge cannot be met on a gradual basis. It is not possible to create even the simplest Java program without programming in the object-oriented paradigm.
The technical prerequisite for this lesson is the ability to successfully write programs in Pascal or some other suitable modern structured programming or scripting language. It is assumed that students in this lesson are not familiar with OOP.
This lesson provides a discussion of OOP from both a theoretical and a practical viewpoint. In other words,
This lesson also provides a very brief introduction to object-oriented design (OOD) and introduces a methodology for performing the first pass of an object-oriented design. However, this lesson will not endeavor to teach OOD to any significant depth. OOD is a very complex topic that is reserved for other lessons.
Finally, this lesson attempts to determine the minimum skill set that would be required of a beginning programmer before that programmer would have the ability to design and implement a program using object-oriented techniques.
An introductory description of OOP can be based on the following guideline:
The solution to the problem should resemble the problem, and observers of the solution should be able to recognize the problem without necessarily knowing about it in advance. |
A good example of this guideline from the computing world is the use of OOP to develop a stack class from which stack objects can be instantiated. If a stack is implemented as a class, instantiated as an object, and documented appropriately, programmers familiar with stacks, queues, and other similar data structures will recognize it as a stack without other prior knowledge.
Many "application frameworks" are written according to the OOP paradigm. Important among these is the Borland ObjectWindows Library (OWL) which can be used to simplify the development of the Graphical User Interface (GUI) portions of Windows programs. This package and other similar application frameworks use C++ class libraries to encapsulate the interface and implementation of the operating platform's applications programing interface (API). While the use of the OWL does not make it easy to develop GUI programs, the use of the OWL does greatly reduce the complexity of such development.
We are object-oriented creatures living in an object-oriented world. We tend to think in object-oriented ways.
For example, when planning a motor trip, we usually think first about the best way to get from point A to point B without being too concerned about how to get through the traffic in each of the cities along the way.
Once we are satisfied that we have the overall route mapped out appropriately, we may go back and begin thinking about the details, such as how to avoid five-oclock traffic in a particular city, where is the best place to stop for the night, is there some particular restaurant that we want to visit, and if so, how can we arrange the timing so as to arrive there at dinner time, etc. This is object-oriented thinking.
Previous approaches to programming (pre-OOP) tend to separate the data from the methods used to manipulate that data, or at least don't strongly encourage them to be considered in concert.
The world and its applications are not organized into values and procedures separate from one another. People who solve problems in other crafts do not perceive the world that way. They deal with their problem domains by concentrating on the objects and letting the characteristics of those objects determine the procedures to apply to them.
To build a house, fix a flat tire, or repair a carburetor, you first think about the object and its purpose and behavior. Then you select your tools and procedures. The solution fits the problem.
Any object-oriented language must support three very important concepts:
We use these three concepts extensively as we attempt to model the real-world problems that we are trying to solve with our object-oriented programs.
Consider the steering mechanism of a car as real-world an example of encapsulation. During the past eighty years or so, the steering mechanism has evolved into an object in the OOP sense. In particular, most of us know how to use the steering mechanism of an automobile without having any idea whatsoever how it is implemented. All most of us care about is the interface which we refer to as a steering wheel. We know that if we turn the steering wheel clockwise, the automobile will turn to the right, and if we turn it counterclockwise, the car will turn to the left.
Most of us don't know, and don't really care, how the steering mechanism is actually implemented "under the hood." In fact, there are probably a number of different implementations for various brands and models of automobiles. Regardless of the brand and model, however, the human interface is pretty much the same. Clockwise turns to the right, counterclockwise turns to the left.
To appreciate the importance of this standard interface, attach a short rental trailer to your car and try backing it into your driveway. Turning the steering wheel counterclockwise causes the trailer to turn to the right and clockwise causes the trailer to turn to the left; just the opposite from the above. Most of us aren't accustomed to this interface and have some difficulty using it, at least initially. It is probably safe to suggest that the human factors aspect of the interface to the steering mechanism in your car wasn't designed for backing up with a trailer attached. (The trick to adapting the interface is to put you hand on the bottom of the steering wheel instead of the top.)
In any event, as in the steering mechanism for a car, a common approach in OOP is to "hide the implementation" and "expose the interface" through encapsulation.
Another important aspect of OOP is inheritance. Let's form an analogy with the teenager who is building a hotrod. That teenager doesn't normally start with a large chunk of steel and carve an engine out of it. Rather, the teenager will usually start with an existing engine and make improvements on it. In OOP lingo, that teenager extends the existing engine, derives from the existing engine, inherits from the existing engine, or subclasses the existing engine.
Just like in "souping up" an engine for a hotrod, a very common practice in OOP is to create new improved objects using new definitions that extend existing definitions. In fact, one of the major arguments in favor of OOP is that it provides a formal mechanism which encourages the reuse of existing programming elements. One of the mottos of OOP is: reuse, don't reinvent.
A third important aspect of OOP is polymorphism. This is a greek word meaning something like one name, many forms. This is a little more difficult to explain in non-programming terminology. However, we will stretch our imagination a little and say that polymorphism is somewhat akin to the automatic transmission in your car. In my Honda, for example, the automatic transmission has four different methods or functions known collectively as Drive (in addition to the functions of Reverse, Park, and Neutral).
As an operator of the automobile, I simply select Drive (meaning go forward). Depending on various conditions at runtime, the automatic transmission system decides which version of the Drive function to use in every specific situation. The specific version of the function that is used is based on the current conditions. This is somewhat analogous to what we will later refer to as runtime polymorphism.
I also believe it is true that my Honda has only one method which we refer to as Reverse. Once I select Reverse, that one method gets used. There is no automatic selection among multiple reverse methods. Therefore, my Honda exhibits polymorphic behavior when going in the forward direction, but exhibits non-polymorphic behavior when going backwards.
The world is object-oriented, and the object-oriented programming paradigm attempts to express computer programs in ways that model how people perceive the world.
OOP involves a whole new vocabulary (or jargon) which is different from or supplemental to the vocabulary of procedural programming.
For example the object-oriented programmer defines an abstract data type by encapsulating its implementation and its interface into a class.
One or more instances of the class can then be instantiated.
An instance of a class is known as an object.
Every object has state and behavior where the state is determined by the current values stored in the instance variables and the behavior is determined by the instance methods of the class from which the object was instantiated.
Inherited abstract data types are derived classes or subclasses of base classes or superclasses. We extend superclasses to create subclasses.
Within the program the programmer instantiates objects (creates instances of classes) and sends messages to the objects by invoking the class's methods (or member functions).
If a program is "object oriented", it uses encapsulation, inheritance, and polymorphism. It defines abstract data types, encapsulates those abstract data types into classes, instantiates objects, and sends messages to the objects.
To make things even more confusing, almost every item or action used in the OOP jargon has evolved to be described by several different terms. For example, we can cause an object to change its state by sending it a message, invoking its methods, or calling its member functions. The term being used often depends on the author who wrote the specific book that you happen to be reading at the time.
Hopefully most of this terminology will become clear as we pursue this lesson.
An object-oriented program has three explicit characteristics and one implicit characteristic. The three explicit characteristics are encapsulation, inheritance, and polymorphism. The implicit characteristic is abstraction.
The implicit characteristic of abstraction is used to specify new abstract data types (ADT).
Encapsulation is used to gather an ADT's data representation and its behavior into one encapsulated entity (to convert from the abstract to the concrete). You might think of this as being similar to converting an idea for an invention into a set of blueprints from which it can be built, or converting a set of written specifications for a widget to a set of drawings that can be used by the machine shop to build the widget.
Automotive engineers encapsulated the specifications for the steering mechanism of my car into an object where they exposed the interface (steering wheel) and hid the implementation (levers, bolts, etc.). In all likelihood, the steering mechanism object contains a number of other more-specialized embedded objects, each of which has state and behavior and has interface and implementation. The interfaces to those embedded objects aren't exposed to me, but they are exposed to the other parts of the steering mechanism that use them.
Inheritance is used to derive a new data type from an existing one. (reuse, don't reinvent.)
The teenager building the hotrod doesn't reinvent the engine, rather he extended an existing engine to produce a new type of engine that will run faster.
Polymorphism is used to customize the behavior of an instance of a type (an object) based on existing conditions.
The automatic transmission in my car modifies its behavior at runtime on the basis of existing conditions.
Before getting into a detailed discussion of encapsulation, inheritance, and polymorphism, let's consider some basic questions.
This lesson will concentrate primarily on the first and third questions, and will provide a very brief introduction to object-oriented design near the end.
Simply stated, and taking a very liberal view:
An object is an instance of a data type. |
The following C++ code fragment declares two objects. The first object is an instance of a simple integer variable named ndays. (Some authors would not agree that an instance of an integer is actually an object, but this author is taking a very liberal view at this point for purposes of illustration.)
The second object is an instance of an abstract data type where the object is named cdt and the abstract data type is named Date.
void f() { ... int ndays; //an instance of an int //an instance of abstract data type identified as Date Date cdt; ... } |
You probably already know, or can surmise, where the integer data type comes from. It is a type which, along with float, long, double, etc., is intrinsic to C, C++, and Java. In other words, it is one of the primitive types of the language.
It is assumed that you don't know about the abstract data type named Date. During this lesson, you will learn, in concept at least, how to define new data types and how to create instances of them (objects).
So, what are the objects? Again, taking a very liberal view, objects are the variables that you declare using either data types which are intrinsic to the programming language, or new data types that you invent.
Many authors would not use the term object for variables of the primitive or intrinsic types, but would reserve the term object for variables of newly-defined types which are not intrinsic to the language. This author generally agrees with that philosophy. Usually in this lesson, when we use the word object, we will be referring to a variable of a type which is not intrinsic to the language. The intrinsic types have been included up to this point simply for illustration, in an attempt to bridge the gap and remove some of the mystery from the subject.
The implicit characteristic of an object-oriented program is Abstraction. Abstraction is the specification of an abstract data type, which includes a specification of the type's data representation and behavior. In particular,
For our purposes, an abstract data type is a new type (not intrinsic to the language). It is not one of the primitive data types that are built into the programming language (such as int, long, float, etc.).
The distinction in the previous paragraph is very important. The data representation and behavior of the intrinsic or primitive types is already known to the compiler and cannot be modified by the programmer.
The representation and behavior of an abstract type is not known to the compiler until it is specified by the programmer and presented to the compiler in an appropriate manner.
How do we present the specification of an abstract type to the compiler? Java and C++ programmers define the data representation and the behavior of a new type (present the specification to the compiler) using the keyword class (C++ programmers can also use the keywords struct and union).
In other words, in Java and C++, the keyword class is used to convert the specification of a new type into something that the compiler can work with; a set of plans as it were.
Other languages may use different mechanisms to present the specification of the abstract type to the compiler.
Once the new type is defined, one or more objects of that type can be brought into being (instantiated, caused to occupy memory).
Once instantiated, the object is said to have state and behavior. The state of an object is determined by the current values of its data (instance variables) and the behavior of an object is determined by its methods (member functions or instance methods).
For example, again taking some liberties, if we view a GUI button as an object, it is fairly easy to visualize state and behavior.
A GUI button can usually manifest any of a number of different states: size, position, depressed image, not depressed image, caption, etc. Each of these states is determined by data stored in the instance variables of the button object at any given point in time. (The combination of one or more instance variables that determine a particular state are often referred to as a property of the object.)
Similarly, it is not too difficult to visualize the behavior of a GUI button. When you click it with the mouse, that usually causes some specific action to take place.
In fact, if you dig deeply enough into GUI programming tools, you will probably find that there is a class of button of which each individual button is an instance (object).
Each individual button object has instance variables, the values of which define the state of the button at any given time.
Every instance of the button class has certain fundamental behavior (respond to a click event, respond to a mouse dragover, etc.) which can be used to trigger some higher level action.
We have probably reached the point in this discussion where we should provide a real example so that you can cast some of these concepts into the real world.
The boldface portion of the following Java program creates an abstract data type by expressing its data representation and behavior using the class keyword. The compiler knows nothing about this new data type (at least not until it is defined by the programmer). In case you are interested, this Java program displays the date 4/8/37 as a string on the console screen.
import java.util.*; //define a new type using keyword class class MyDateClass { int month, day, year; // instance variables of the class //instance method to store data void setDate(int mo, int da, int yr) { month = mo; day = da; year = yr; }//end method setDate() String getDate()//instance method to get data { return "" + month + "/" + day + "/" + year; }// end method getDate() }//end class MyDateClass definition //Driver program follows class java1 { //define the controlling class public static void main(String[] args){ //define main MyDateClass obj = new MyDateClass(); //instantiate obj obj.setDate(4,8,37); //store data in instance variables //get and display instance variables System.out.println( obj.getDate() ); }//end main }//end java1 class |
This abstract data type named MyDateClass has month, day, and year data members (instance variables).
The behavior of the new type is defined by two instance methods. One can be used to store a date in an object of the new type. The other can be used to retrieve a stored date from the object.
The new type could be expanded to incorporate other behaviors by providing additional methods.
Having defined the new type, we can (and do) create instances of the type (objects) and deal with those objects much as we would deal with any other variables created from the primitive data types.
The first of the three major characteristics of an object-oriented program is encapsulation. If abstraction is the design or specification of a new type, then encapsulation is its definition and implementation.
A programmer encapsulates the data representation and behavior of an abstract data type into a class, thereby defining its implementation and interface.
According to good object-oriented programming practice, an encapsulated design usually hides its implementation from the class user and reveals only its interface. This is accomplished in different ways with different languages as described below.
Just as most of us don't usually need to care about how the steering mechanism of a car is implemented, a user of a class should not need to care about the details of implementation -- only that it works as advertised. Of course this assumes that the user of the class has access to good documentation describing the class.
For a properly designed class, the class designer should be able to change the implementation -- perhaps changing the date representation to a long integer count of days since January 1, 1970 in the above program -- and the using programs should not be affected by the change.
Various object-oriented programming languages provide the ability to control access to the members of a class. For example, both C++ and Java use the keywords public, private, and protected to control access to the individual members of a class. To a first approximation, at least, you can probably guess what public and private mean. The protected keyword is used to provide inherited classes with special access privileges to the members of their base classes. (Java has some subtle complexities, related to access within a package which won't be discussed here.)
In general, the interface of a class which is visible to the user of the class consists of the public methods. The class user stores, reads, and modifies values in the data representation by invoking those methods with respect to a specific instance (object) of the class (sometimes referred to as sending a message to the object asking it to change its state).
Normally, if the class is properly designed, (the implementation is hid) the user cannot modify the values contained in the instance variables of the object without going through the prescribed interface (normally you cannot cause your car to change direction without turning the steering wheel).
The class interface in the above example program consists of a set method and a get method. The set method is used to store new data into the instance variables of an object of the class. The get method is used to retrieve or fetch the values stored in the instance variables of an object of the class.
Note however that because of the complexity introduced by the package concept in Java, and because of the desire to keep this program as simple as possible, no attempt was made to hide the implementation. The primary purpose of this program was to provide a first look at classes and it is not a well-designed program from an object-oriented programming viewpoint.
As an aside, methods whose names begin with set and get have a special significance in Java. In particular, the introspection capability of the Java Beans API considers these names to represent design patterns for manipulating the properties of an object. |
An object-oriented design is not a good design by default. In an attempt to produce good designs, experienced object-oriented programmers generally agree on certain design standards for classes. For example, the data members (instance variables) are usually private. The interface usually consists only of methods and includes few if any data members.
One exception to this general rule is that often data members which are intended to be used as symbolic constants are made public and defined in such a way that their values cannot be modified.
The methods in the interface should control access to, or provide a pathway to, the private instance variables. The interface should be generic in that it is not bound to any particular implementation. Hence, the class author should be able to change the implementation without affecting the using programs so long as the interface doesn't change. In practice, this means that the signatures of the interface methods should not change, and that the arguments should continue to have the same meaning.
So far, we have dealt only with instance variables and instance methods. We also need to understand the concept of class variables and class methods which will be discussed in the next section.
Instance variables are those data members of a class that are defined such that every individual instance of the class (object) has its own set of variables, totally independent of the variables associated with all other objects of the same or different classes.
In other words, each object has its own memory space where its own set of variables is stored. Because instance variables are bound to an object, they can be accessed only by way of the object to which they are bound.
Assuming that access to an instance variable is not prohibited by access control mechanisms (such as the use of the keyword private), it is usually accessed by joining the name of the object (or the name of a pointer to the object) to the name of the instance variable using the appropriate joining operator. The joining operator for Java is a simple period (sometimes called the dot operator).
The joining operator for C++ depends on whether the instance variable is being accessed using the name of the object or the name of a pointer to the object. When the name of the object is used, the joining operator is a simple period. When the name of a pointer to the object is used, the pointer operator (->) is used.
Other languages may use different methods for accessing the instance variables of an object.
Class variables are those data members of a class that are shared among all objects of the class. It's not too far-fetched to think of them as quasi-global variables whose accessibility is restricted only to objects of a particular class, or to the class itself. In other words, only one copy of a class variable exists in memory and all objects of the class can access that copy.
An important characteristic of class variables is that they can also be accessed without the requirement to instantiate an object of the type. In this case, they are usually accessed using the name of the class joined to the name of the variable using the appropriate joining operator. The joining operator for Java is a simple period. The joining operator for C++ is the scope resolution operator which is two colons with no space in between (::).
In Java and C++, class variables are designated as such using the static keyword. (Designation of a class variable in C++ also involves a requirement to re-declare the variable outside the class definition.)
Also in Java and C++, member variables declared without the use of the static keyword are instance variables. In other words, by default, all member variables of a class are instance variables unless you designate them as class variables using the static keyword.
Note the following very important statements:
The methods of a class have direct access to the member variables of the same class regardless of their access control designation such as public, private, or protected (except that class methods can usually access only class variables). There is no requirement for the code in a method of the class to use either the name of an object or the name of the class to access the member variables of the same class. |
The methods of a class come in two varieties: instance methods and class methods. As with instance and class variables, those methods designated static are class methods in Java and C++. Methods not designated static are instance methods.
An instance method can only be invoked by way of an object of the class. In that sense, an instance method is bound to an object. However, unlike an instance variable, an object normally doesn't have its own separate copy of an instance method. It would be wasteful of memory resources to provide a separate copy of every instance method for every object of a class.
It is very important to understand that when an instance method is invoked on a particular object, and when the code in that method needs to access an instance variable of the class, it will access the particular copy of the instance variable belonging to the object on which it was invoked. It is this ability to temporarily bind the method to the instance variables of the object on which the method is invoked that causes it to behave as an instance method.
Instance methods in Java and C++ (and perhaps other object-oriented languages as well) exhibit another interesting characteristic. In particular, whenever an instance method is invoked, a pointer variable or reference variable named this comes into existence automatically. This reference variable refers to the object on which the method was invoked, and can be used by the code in the method for any purpose that a reference to that object might be needed.
Class methods can only access other class members (class variables or other class methods). They cannot access instance variables or instance methods.
The most important thing about class methods is that they can be accessed using the name of the class without a requirement to instantiate an object of the class. As with class variables, class methods can be accessed by joining the name of the class to the name of the method using the appropriate joining operator.
Much of what we have been discussing can probably be better understood when seen in the context of an actual program.
The following sample program illustrates the use of class and instance variables along with class and instance methods in a Java program. This is not an attempt to teach the Java programming language at this point. Rather, it is simply an attempt to illustrate OOP concepts using an actual Java program as the vehicle. As before, because of the access control complexity caused by the package concept in Java, and the desire to keep this sample program as simple as possible, no attempt was made to hide the implementation.
When compiled and executed, this program will produce the following output on the standard output device. You might want to remember this place so that you can refer to it as you examine the program.
A - Instance variable contains 5 B - Class variable contains 10 C - Class variable contains 10 |
Before we take a look at the complete program, lets examine some of the interesting code fragments that make up the program.
The first interesting code fragment shows the declaration of two member variables of the class. One is a class variable named classVariable and the other is an instance variable named instanceVariable.
int instanceVariable; //declare an instance variable static int classVariable; //declare a class variable |
These are typical variable-declaration statements in Java and C++ consisting of the name of the type followed by the name of the variable. The important thing to note in the context of this discussion is the use of the static keyword in the declaration of the class variable.
The next code fragment shows the definitions of two methods (with the bodies of the methods deleted for brevity). One of these methods is a class method named classMethod and the other is an instance method named instanceMethod.
void instanceMethod(){//define an instance method //body of method deleted for brevity }//end instanceMethod() static void classMethod(){//define a class method //body of method deleted for brevity }//end classMethod() |
Again, these are typical method or member function definitions for Java and C++ consisting of the name of the return type (where void means that nothing is returned) followed by the name of the method, followed by the formal argument list (which happens to be empty in this case). The body of the method is then enclosed within a matching pair of curly braces { }.
Again, the important thing to note in the context of this discussion is the use of the static keyword in the definition of the class method.
The next code fragment is a single statement taken from the body of one of the methods. This statement causes output to be displayed on the standard output device.
This single statement incorporates classes, class variables, instance methods, and overloaded operators, and illustrates some of the syntactical complexity that can be encountered in object-oriented programming.
System.out.println( "A - Instance variable contains " + instanceVariable); |
This is a Java statement. A completely different syntax would be required to achieve the same result in C++.
Note first that this statement has three elements joined with periods. The first element is the word System which is the name of one of the classes in the standard Java class library. As background information, the System class is automatically loaded whenever a Java application is started.
The name of the System class is joined to the word out using a period. The word out is the name of a member variable of the System class.
The member variable named out is a public class variable. This makes it possible to access the variable using the name of the class and the name of the variable joined by the period.
Note that the class variable named out is also joined to the word println using the period as the joining operator. The variable out is not only a class variable, it is also a reference variable (as opposed to a primitive variable) and it contains a reference to an object of the PrintStream class.
The PrintStream class has an instance method named println(). In fact, there are ten overloaded versions of the println() method in the PrintStream class. The behavior of the version used here is to cause its string argument to be displayed on the standard output device.
Now consider the string argument to the println method as shown below:
("A - Instance variable contains " + instanceVariable) |
In Java (and C++ as well), literal strings are enclosed in quotation marks. You will note that not everything in this argument is enclosed in quotation marks. Note in particular the plus sign near the middle of the argument.
In Java, the plus sign is overloaded so that in addition to being used as an arithmetic addition operator, it is also used to concatenate strings.
An overloaded operator exhibits different behavior depending of the types of its operands. |
Furthermore, the behavior of the overloaded plus operator also includes the ability to coerce its right operand into a string representation if it isn't already a string. In this case, the right operand is not a string, but rather is the instance variable named instanceVariable. Thus the behavior of the overloaded plus operator is to first convert the value of instanceVariable to a string representation and than to concatenate it to the left operand.
Some object-oriented languages such as C++ allow the programmer to overload almost all of the operators so as to define the behavior of those operators when used with operands of new abstract data types. However, Java does not provide that capability. In this case, the plus operator is intrinsically overloaded by the system.
Now let's take another look at the same two methods as before, this time preserving the bodies of the methods for further examination.
void instanceMethod(){//define an instance method System.out.println( "A - Instance variable contains " + instanceVariable); System.out.println( "B - Class variable contains " + classVariable); }//end instanceMethod() static void classMethod(){//define a class method System.out.println( "C - Class variable contains " + classVariable); }//end classMethod() |
Here we see the code in the body of the methods accessing the member variables of the class. Recall that one of the member variables is an instance variable named instanceVariable and the other member variable is a class variable named classVariable.
The instance method named instanceMethod is able to access and display both the instance variable and the class variable while the class method named classMethod is only allowed to access and display the class variable. Class methods cannot access instance variables.
Now consider the contents of the main method as shown below. Both Java and C++ applications (not applets) require a main method or function as the controlling method of the application. In our simple application, we will use code in the main method to instantiate an object and to access both the instance method and the class method.
Recall that in order to access an instance method, it is necessary to access it via an object of the class. The next code fragment is the code in the main method that instantiates an object named obj of the class named Oop01.
//instantiate an object of the class Oop01 Oop01 obj = new Oop01(); |
This is a typical Java statement for instantiating an object and is similar to one form of statement that can be used to instantiate an object in C++. (C++ provides other forms as well.)
This statement uses the new operator to request that the operating system provide memory "from the heap" to store one copy of an object of type Oop01.
If the required memory is successfully allocated, the address of that block of memory will be assigned to the reference variable named obj. If unsuccessful, the Java runtime system will throw an exception. This is a type of exception which can either be ignored, or can be caught and processed by the program. If ignored, it will cause the runtime system to shut down the program. (Exception handling is discussed later in this lesson.)
Both Java and C++ support exception handling, but do so in slightly different ways.
Once we have access to an object of the class (or more correctly access to a reference variable which refers to an object of the class), we can use that reference variable to access the public member variables and to invoke the public methods of the class. This is illustrated in the following code fragment.
The two statements in the following code fragment use the reference variable named obj along with the period to access the instance variable and the instance method of the object. Recall that the instance variables and the instance methods can be accessed only via an object of the class.
//access instance variable via the object obj.instanceVariable = 5; //access instance method via the object obj.instanceMethod(); |
Equally important is the fact that the class variable and the class method can be accessed without the requirement to use an object of the class. The two statements in the following code fragment simply use the name of the class to access the class variable and the class method of the class.
//access class variable via the class Oop01.classVariable = 10; //access class method via the class Oop01.classMethod(); |
Class variables and class methods can be accessed either via an object of the class, or via the name of the class alone. However, this sample program does not illustrate accessing class variables and methods using an object of the class.
Finally, we put it all together in the Java application shown below.
/*File Oop01.java Copyright 1997, R.G.Baldwin Illustrates instance and class variables along with instance and class methods. The output from this program is: A - Instance variable contains 5 B - Class variable contains 10 C - Class variable contains 10 **********************************************************/ class Oop01{ //define controlling class int instanceVariable; //declare an instance variable static int classVariable; //declare a class variable void instanceMethod(){//define an instance method System.out.println( "A - Instance variable contains " + instanceVariable); System.out.println( "B - Class variable contains " + classVariable); }//end instanceMethod() static void classMethod(){//define a class method System.out.println( "C - Class variable contains " + classVariable); }//end classMethod() public static void main(String[] args){ //instantiate an object of the class Oop01 Oop01 obj = new Oop01(); //access instance variable via the object obj.instanceVariable = 5; //access class variable via the class Oop01.classVariable = 10; //access instance method via the object obj.instanceMethod(); //access class method via the class Oop01.classMethod(); }//end main() }//end class Oop01 |
Methods are sometimes called member functions (particularly in books on C++).
A message is simply the invocation of a method or member function. |
The program "sends a message" to an object telling it to invoke the method and sometimes provides parameters for the method to use.
Someone recently wrote that an object-oriented program consists simply of a bunch of objects laying around sending messages to one another. This might be a slight exaggeration, but is not too far from the truth.
The allocation, reclamation, and reuse of dynamic memory from the heap is an important aspect of most object-oriented programs, and some non-object-oriented programs as well. The next few sections discuss how these requirements are met with respect to objects in Java and C++. Other object-oriented language use other techniques to accomplish the same objectives:
Failure to deal with this important issue results in a condition often referred to as "memory leakage."
Both Java and C++ and possibly other object-oriented languages as well, support the notion of a constructor. The following Java code fragment shows a constructor at work.
//instantiate an object of the class Oop01 in Java Oop01 obj = new Oop01(); |
A constructor is a special method of a class that is used to instantiate (and optionally initialize) a new object of the class type. In the above statement, the invocation of the constructor method is highlighted in boldface. Constructors can be overloaded just like other methods in Java and C++.
Method overloading will be discussed in more detail in a later section on polymorphism. Briefly, method overloading means that two or more methods can share the same name provided that they have different argument lists. When the program is compiled, the compiler determines which version of the method to use in that instance on the basis of the actual parameters being passed to the method. |
In this particular statement, the new operator is used to allocate dynamic memory from the heap, and the constructor is used to construct the object in that memory space. The address of the memory containing the object is returned and assigned to the reference variable named obj. If the memory cannot be allocated, an exception will be thrown.
In Java and C++, if you do not define a constructor when you define a new class, a default constructor that takes no parameters is defined on your behalf. This is often referred to as the default constructor or the noarg constructor.
It is also possible for you to define a constructor for your class which takes no arguments and which performs some special action when it is invoked. Defining a constructor is only slightly different from defining any other method in Java or C++ (it must have the same name as the class, does not have a return type, and must not have a return statement).
Although you are free to cause your constructor to perform just about any action that you want, the intended purpose of a constructor is to perform some form of initialization (open a file, initialize instance variable values, etc.) If you provide such a noarg constructor, it will be invoked in place of the default constructor when you invoke the constructor as shown in the above code fragment with no arguments. The constructor shown above is a Java constructor. The invocation of a constructor in C++ is even simpler. An example is shown below:
Oop01 obj; //instantiate object of class Oop01 in C++ |
C++ provides other formats for invoking the constructor in addition to that shown above.
It is also possible for you to define one or more overloaded versions of the constructor that do take parameters. These are commonly called parameterized constructors. Typically you will include code in the body of the constructor to use the arguments passed as parameters for some sort of initialization, but again, you can write the code to perform just about any action that you want.
The following code fragment shows the important parts of a Java program, similar to the previous one which has been modified to use a parameterized constructor.
Oop02(int iV, int cV){ //parameterized constructor instanceVariable = iV; //initialize the instance variable classVariable = cV; //initialize the class variable }//end parameterized constructor ... public static void main(String[] args){ //instantiate an object of the class Oop02 Oop02 obj = new Oop02(2,3); |
This code fragment shows the parameterized constructor method named Oop02 and then shows the statement used in the main method to instantiate and initialize an object of the class Oop02 named obj. As you can see, the parameterized constructor in Java looks just like a method except that it has no return type or return statement.
A destructor is a special method typically used to perform cleanup after an object is no longer needed by the program. C++ supports destructors, but Java does not support destructors. Other object-oriented languages may or may not support destructors.
In C++, you can optionally define a destructor for a class. It looks much like any other method except that it cannot take parameters, does not have a return type, must not have a return statement, cannot be overloaded, and has the same name as the class except that it has a tilde (~) as a prefix to the name.
In C++, if you define a destructor for a class, it will be automatically invoked whenever an object of that class goes out of scope. Typically destructors are used to perform cleanup of some sort, and are often used to return dynamic memory to the operating system when the object goes out of scope and that memory is no longer needed by the program. The code in the destructor can perform other actions as well, such as closing files, etc., or can do just about anything that you want it to do.
Java supports another mechanism for returning memory to the operating system when it is no longer needed by an object: garbage collection. The garbage collector is a part of the runtime system that runs in a low-priority thread reclaiming memory that is no longer needed by objects used by the program. An object becomes eligible for garbage collection in Java when there are no longer any reference variables that reference the object.
In Java, you can have one or more reference variables which reference the same object. That object will not become eligible for garbage collection until all of those reference variables go out of scope, are assigned to a different object, or are assigned a value of null.
The first major characteristic of an object-oriented program is encapsulation which was discussed above. The second of the three major characteristics is inheritance, with polymorphism occupying the third slot. Now let's take a look at inheritance.
A class can normally inherit the attributes and characteristics of another class (although inheritance can be blocked using different methods in different languages).
The original class is often called the base class or the superclass, and the new class is often called the derived class or the subclass. Inheritance is often referred to as extending the base class or superclass.
Inheritance is hierarchical. In other words, a class may be the subclass of one class and the superclass of another class.
The derived class inherits the data representation and behavior of the base class except where the derived class modifies the behavior by overriding methods. The derived class can also add new data representation and behavior that is unique to its own purpose.
(The teenager building a hotrod may override the old two-barrel carburetor by replacing it with a new four-barrel model, and may also add a blower and other speed-enhancing widgets that were not a part of the original engine design.)
A program can usually instantiate objects of a base class as well as of a class which is derived from the base class. (It is also possible to block instantiation of the base class in some cases by defining it as an abstract base class.) If the base class is an abstract base class -- one that exists only to be derived from -- the program may not instantiate objects of the base class but can instantiate objects of classes derived from the base class.
The Java and C++ inheritance mechanisms allow you build an orderly hierarchy of classes. (In C++, because of multiple inheritance, it is also possible to build hierarchies that are not so orderly.) Java does not support multiple inheritance.
When several of your abstract data types have characteristics in common, you can design their commonalities into a single base class and separate their unique characteristics into unique derived classes. This is one of the purposes of inheritance.
For example, suppose you are building a program dealing with airships. All airships have altitude and range parameters in common. Therefore, you could build a base Airship class containing data and methods having to do with range and altitude.
From this base class, you might derive a Balloon class and an Airplane class.
The Balloon class might add variables and methods dealing with passenger capacity and what makes it go up (helium, hydrogen, or hot air). Objects of the Balloon class would then be able to deal with altitude, range, passenger capacity, and what makes it go up.
The Airplane class might add variables and methods dealing with engine type (jet or propeller) and cargo capacity. Objects of the Airplane class could then deal with altitude, range, engine type, and cargo capacity.
Having created this hierarchy of classes, you could instantiate objects of type Airship, Balloon, or Airplane with the objects of each type having variables and methods to deal with those special characteristics of the flying machine indicated by the name of the class. (Although there isn't any requirement to do so, hopefully your will use descriptive names for your classes.)
You may have noticed that in this hierarchical class structure, inheritance causes the structure to grow in a direction from most general to less general. This is typical.
C++, and perhaps other object-oriented languages as well, allow for multiple inheritance. This means that a new class can be derived from more than one base class. This has advantages in some cases, but can lead to difficulties in other cases.
The designers of the Java language chose not to support multiple inheritance. Instead they provided a different mechanism called an interface which can often be used to achieve the same end result as multiple inheritance with fewer potential problems. This topic is much too specific to discuss in detail here. It is mentioned simply to alert you to the fact that an object-oriented language that you choose may, or may not support multiple inheritance, and if not, may provide some alternative mechanism to achieve the same end result.
You will sometimes hear people speak of the ISA relationship when discussing OOP. The source of this terminology is more fundamental than you may at first suspect.
Object-oriented designers often strive to use inheritance to model relationships where a derived class "is a kind of" the base class. For example, a car "is a kind of" vehicle. A programmer "is a kind of" employee which in turn "is a kind of" person.
This relationship is called the ISA relationship. It's that simple.
The three required characteristics of an object-oriented language are encapsulation, inheritance, and polymorphism.
Polymorphism (from the Greek, meaning "many forms", or something similar) is the quality that allows one name to be used for more than one (hopefully related) but technically different purpose.
The purpose of polymorphism as it applies to OOP is to allow one name to be used to specify a general class of actions. Within a general class of actions, the specific action to apply in any particular instance is determined by the type of data involved.
More generally, the concept of polymorphism is the idea of "one interface, multiple methods". Polymorphism exists when functions or operators are overloaded or overridden to cause them to perform operations not implicitly recognized by the compiler. (Java does not support operator overloading but it does support the overloading and overriding of methods.)
Perhaps the best-known simple example of polymorphism is the ability to replace the three C functions,
by a single C++ or Java function called ABS()(or whatever name you choose to call it).
C requires three separate functions to return the absolute value of each of the three data types: int, long, and float. With C++ (and with Java), you can overload functions so that the absolute value of any of the three data types can be obtained by calling a function having the same name for every data type. This is accomplished through function or method overloading. Function overloading provides a type of polymorphism often referred to as compile-time polymorphism. (Note that some authors do not consider function overloading to be a true or pure form of polymorphism.)
Polymorphism is not necessarily new to Java, C++ and other modern languages. For example some programming languages that have been around for quite a while interpret the plus operator to be an arithmetic operator when applied to numeric data and to be a concatenation operator when applied to string data. (Actually that is the one form of operator overloading that is also supported by Java.) This is clearly an example of polymorphic behavior, and has been around since the early days of programming. However, that form of polymorphic behavior has been intrinsic to the language and could not be modified or controlled by the programmer.
C++ provides the opportunity, and (with one or two exceptions) the responsibility for the programmer to define the behavior of (almost) any operator that may be applied to an object of a new class. We refer to this as operator overloading. One operator (the assignment operator and possibly a few others) has a default behavior when applied to objects of a new class. The default behavior of the assignment operator is to make an identical bitwise copy of the object on the right and store it in the object on the left. Even this default behavior can be, and often is, overloaded to provide different behavior for the assignment operator relative to objects of a new class.
There are about four or five operators that cannot be overloaded in C++. Beyond these, the C++ programmer can define the behavior of any operator when the operator is applied to an object of a new class. (Note however, that the programmer cannot modify the behavior of operators as applied to variables of the intrinsic or primitive types.)
This is an extremely valuable capability when used wisely. For example, in a program that deals extensively with vectors in 3D space, the programmer might choose to overload a subset of the arithmetic operators with respect to objects of a new vector class so as to provide a complete vector algebra for objects of that class.
Each object of the class could represent a vector in 3D space, and the plus sign, for example, could be applied to two such objects to produce a third object which would represent the sum of the two vectors represented by the operands.
Unfortunately, Java does not support operator overloading. There is probably little or nothing that can be accomplished with overloaded operators that cannot also be accomplished with overloaded methods, but in many cases, overloaded operators will provide a much cleaner and more intuitive syntax.
Note that this section discusses the word override as opposed to the word overload. Overriding a method is an entirely different thing from overloading a method.
Previous sections have discussed overloading methods and overloading operators. Polymorphism also exists when a derived class customizes the behavior of methods defined in the base class to meet the special requirements of the derived class. This is known as overriding methods and leads to runtime polymorphism.
Both Java and C++ support the notion of overriding a method in a base class to cause it to behave differently relative to objects of the derived class. In other words, a method named joe that is defined in the base class and is overridden in the derived class would behave differently depending on whether it is invoked on an object of the base class or on an object of the derived class.
Many people find runtime polymorphism to be one of the most difficult concepts of OOP to understand. Hopefully after this explanation, you won't fall in that category.
In both Java and C++, a pointer or reference variable of a base-class type can be used to point to or reference an object of any class derived from that base class.
If an overridden method is invoked using that base-class pointer or reference variable, the system will be able to determine, at runtime, which version of the method to use based on the true type of the object, and not on the type of pointer or reference variable used to invoke the method.
Therefore, it is possible to make selection decisions at runtime among a family of overridden methods as to which method to invoke based on which type of derived object is being pointed to by the base-class pointer when the overridden method is invoked on the base-class pointer. (Note that function overriding in C++ is somewhat more complex than in Java.)
This is illustrated in the following small, but non-trivial Java application. As a baseline, the application first invokes an overridden method named joe() an a base-class reference to a base-class object and then invokes the overridden method named joe() on a derived-class reference to a derived-class object.
In the first case, the base-class version of joe() is actually invoked. In the second case, the derived-class version of joe() is invoked. No surprises here.
Then the application invokes the overridden method named joe() on a base-class reference which actually refers to a derived-class object. When this is done, it is the version of the method defined in the derived class and not the version defined in the base class that is actually invoked. This is the essence of runtime polymorphism.
/*File Polymorph01.java Illustrates runtime polymorphism with overridden methods Assigns a derived class object to a base class reference and then invokes an overridden method on the base class reference. As expected, the overridden method in the derived class is actually executed. Output from the program is: Executing base class version of method joe 1 Executing derived class version of method joe 2 Executing derived class version of method joe 3 **********************************************************/ class Polymorph01{ static public void main(String[] args){ //base class ref to base class object Base baseRef = new Base(); baseRef.joe(1); //invoke method named joe() //derived class ref to derived class object Derived derRef = new Derived(); derRef.joe(2); //invoke method named joe() //base class ref to derived class object baseRef = derRef; //invoke method named joe() on base ref to // derived object baseRef.joe(3); }//end main }//end class Polymorph01 class Base{ void joe(int dataIn){ System.out.println( "Executing base class version of method joe " + dataIn); }//end joe() }//end class Base class Derived extends Base{ void joe(int dataIn){ System.out.println( "Executing derived class version of method joe " + dataIn); }//end joe() }//end class Derived |
Inheritance and method overriding are used extensively in almost all Java programming. Even the simplest "Hello World" Java applet requires that the Applet class be extended and the paint() method be overridden.
Although it is possible to write some significant C++ programs without becoming involved in any form of OOP, those C++ programs that make use of commercial class libraries for such tasks as creation of data structures, building GUI interfaces, etc., also make extensive use of inheritance and method overriding.
Many authors recognize two types of polymorphism:
On the other hand, some authors only recognize run-time polymorphism as being the true and pure form of polymorphism.
In C++, the programmer must be concerned about the difference and program accordingly (only virtual functions can be overridden). Generally speaking, in Java, the programmer need not make much of a distinction between the two because all methods can be overridden unless they are declared final.
The implementation of polymorphism in both Java and C++ essentially consists of providing multiple functions or methods with the same name, and calling the correct one at the appropriate time on the basis of the type of data involved..
If the construction is such that it is clear at compile time which version of two or more functions having the same name should be called in a specific instance, this is referred to as early binding, static binding, or compile-time polymorphism.
For example, function overloading and operator overloading are forms of compile-time polymorphism because the compiler knows when the program is compiled which of several versions of the same function name should be invoked in each instance.
As shown earlier, it is also possible to override and invoke a method in such a way that it is not known at compile time which version of the function is to be called in a particular instance. That determination cannot be made until the program is actually executed. This is often referred to as late binding, dynamic binding, or run-time polymorphism.
While exception handling may not be considered by everyone as part and parcel of OOP, Java requires it, C++ supports it, and it can probably be expected that any object-oriented language that is not currently supporting it will do so in the future. Therefore, a section on exception handling is being included here to introduce you to the subject in a general sense.
Stated in simple terms, exception-handling capability makes it possible for you to monitor for exceptional conditions within your program, and to transfer control to special exception-handling code designed by you whenever an exceptional condition is detected. This is accomplished using the keywords: try, throw, throws, catch, and finally.
You try to execute the statements contained within a block surrounded by braces.
If your code (or the runtime system) detects an exceptional condition within that block, your code (or the runtime system) throws an exception object of a specific type.
Your code can then catch and process the exception object using code that you have designed, or your code can pass it up to the next level in the method-invocation hierarchy for handling there.
In Java, you can then optionally execute a block of code designated by finally which is normally used to perform some type of cleanup which is needed whether or not an exception occurs.
As indicated above, you can design your code to detect exceptional conditions and to throw an exception object. There are also situations where an exceptional condition will be detected by the runtime system which automatically throws an exception and attempts to transfer control to special exception-handling code which you write (cases where you don't provide the code to throw the exception object). In this case, you are responsible only for the code in the catch block and optionally, in Java, for the code in the finally block.
Although Java and C++ provide similar exception handling capabilities, the manner in which exception handling is implemented is different between the two languages. Therefore, the remainder of this section will concentrate on exception handling in Java, for those parts of the discussion which need to be language specific.
Our discussion will attempt to briefly touch on following topics, not necessarily in this order:
According to The Java Tutorial by Campione and Walrath:
"An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions." |
When an exceptional condition causes an exception to be thrown in Java, that exception is an object derived, either directly, or indirectly from the class Throwable. In C++, it is also possible to throw exceptions of primitive types such as int.
The runtime system and many different methods in many different classes automatically throw exceptions in Java. Very few exceptions are automatically thrown in C++. Whereas Java requires you to program using exception handling, C++ makes it optional. However, once you begin using proprietary class libraries in C++, the optional nature of exception handling disappears rather quickly.
In both languages, you can define and throw exception objects of your own design (but in Java you must inherit your exception class, either directly, or indirectly from the class Throwable).
The Throwable class in Java has two subclasses:
Paraphrasing David Flanagan and Java in a Nutshell, an Error indicates that a non-recoverable error has occurred that should not be caught. Errors usually cause the Java interpreter to display a message and exit.
Still paraphrasing Flanagan, an Exception indicates an abnormal condition that must be properly handled to prevent program termination.
Of the many possible exceptions that can be automatically thrown in Java, there is a subset for which catching and processing is optional. The compiler will allow you to simply ignore the exceptions in this subset. If one of the exceptions which you ignore actually occurs, it will be caught by the runtime system, and the program will be terminated.
The remaining exceptions that can automatically be thrown in Java must be recognized by your code in order for your program to compile.
Recognition can consist of either
Within this category, if one of the methods called by your method throws an exception, your method must either catch and process it, or pass it up to the next level in the method-invocation hierarchy.
If your method passes an exception up to the next level in the invocation hierarchy, this must be declared along with the method signature using the throws keyword.
If your code catches and processes an exception, the processing code can be as elaborate or as simple as you want to make it. In fact, simply ignoring it after you catch it will satisfy the compiler and allow your program to compile. This may, or may not be a good idea, depending on the type of the exception.
All exception objects inherit the methods of the Throwable class in Java, as well as the methods of any intermediate classes in the inheritance class hierarchy. Various methods are available to assist you in examining an exception object to extract the diagnostic information contained therein. Sometimes this information can be useful to your code in determining how to deal with the exceptional condition.
Exception handling provides the following advantages over "traditional" error management techniques:
The Java Tutorial by Campione and Walrath provides a good illustration where they show how a simple program having only about six lines of code gets "bloated" into about 29 lines of very confusing code through use of traditional error management techniques. Not only does the program suffer bloat, the logical flow of the original program gets lost in the clutter of the modified program.
The authors then show how to accomplish the same error management using exception handling. This version contains about seventeen lines of code which is considerably fewer than 29 lines of code. Furthermore, it is orderly and easy to understand. The additional lines of code do not cause the original logic of the program to get lost.
The use of exception handling does not spare you the effort of doing the work of detecting, reporting, and handling errors. What it does do is provide a way to separate the details of what to do when something out-of-the-ordinary happens from the normal logical flow of the program code.
Sometimes it is desirable to propagate exception handling up the call stack and let the corrective action be taken at a higher level.
For example, you might provide a class with methods which implements a stack data structure. One of the methods of your class might be to pop an element off the stack.
What should the methods in your stack class do if a using program attempts to pop an element off an empty stack.? That decision might best be left to the user of your stack class and you might simply propagate the notification up to the calling method and let that method take the corrective action.
The following statements describe certain aspects of exception handling:
As a result of the above, a natural hierarchy can be created which causes exceptions to be grouped in logical ways.
For example, going back to the stack example, you might create an exception class which applies to all exceptional conditions associated with an object of your stack class. From this, you might inherit other classes which pertain to specific exceptional conditions such a push exceptions, pop exceptions, initialization exceptions, etc.
If your code throws an exception object of one of the specific types, that object can be caught
This capability to handle exceptions on an organized hierarchical basis is very powerful.
Object-oriented design (OOD) is a technology in its own right, often treated as being separate and apart from OOP. In my view, OOP is the implementation of OOD.
Many books have been written describing a variety of methods for accomplishing OOD. This lesson will not attempt to teach OOD in any depth. However, it probably will be useful to explain how some designers make the first pass at identifying the classes, data members, and methods needed to model the problem as an object-oriented design. Once the first pass is made, the process then often turns into an iterative process of testing and refinement.
One common technique is to create a narrative description of the solution to the problem, and then use the nouns and the verbs to help identify the classes, data members, and methods needed in the design. This or a similar methodology is found in many books on the subject.
Consider the following narrative description of the solution to an object-oriented design problem. We will use this description to design a simple object-oriented program. The nouns will give us hints about the classes and the data members that will be needed, and the verbs will give us an idea of the methods that will be needed.
The narrative description follows with the important nouns in boldface and the important verbs in italicized boldface.
Design a digital Counter which has three bits. Provide the ability to initialize the Counter to 000. Provide the ability to increment the Counter by 1. Provide the ability to show the contents of the Counter. For each bit: Provide the ability to set the value of the bit to a 0. Provide the ability to get the value stored in the bit. Provide the ability to add a 1 to the value stored in the bit and return the carry according to the truth-table that follows: The truth-table for addition is: 0 + 0 = 0, c = 0 0 + 1 = 1, c = 0 1 + 0 = 1, c = 0 1 + 1 = 0, c = 1 Provide a test program that will instantiate a Counter object and exercise the Counter by incrementing it 8 times and displaying the result. The output from the test program should be: 000 001 010 011 100 101 110 111 000 |
The program resulting from this design effort follows:
/*File Design01.java Copyright 1997, R.G.Baldwin The purpose of this file is to illustrate the process of using a narrative description of a problem to identify classes, variables, and methods. **********************************************************/ class Bit{ int value; void set(){//method to set the value of the bit to 0 value = 0; }//end set() int get(){//method to get the value stored in the bit return value; }//end get() //method to implement binary addition int add(int inValue){ int carry = 0; if((value == 0) && (inValue == 0)){ //0+0=0,c=0 carry = 0; }else if((value == 0) && (inValue == 1)){//0+1=1,c=0 value = 1; carry = 0; }else if((value == 1) && (inValue == 0)){//1+0=1,c=0 carry = 0; }else if((value == 1) && (inValue == 1)){//1+1=0,c=1 value = 0; carry = 1; }//end if statement return carry; }//end add() }//end class Bit class Counter{ Bit msb = new Bit();//instantiate three bit objects Bit mid = new Bit(); Bit lsb = new Bit(); //method to initialize the bit objects to 0 void initialize(){ msb.set(); mid.set(); lsb.set(); }//end initialize() //method to add 1 to lsb and have it ripple up // through all three bits of the counter void increment(){ msb.add(mid.add(lsb.add(1))); }//end increment() //method to display the value of each bit in the counter void show(){ System.out.println("" + msb.get() + mid.get() + lsb.get()); }//end show() }//end class Counter //controlling class required by Java application class Design01{ public static void main(String[] args){ //instantiate a counter object Counter myCounter = new Counter(); myCounter.initialize();//initialize the counter object myCounter.show(); //display contents of counter object //increment and display counter object for(int cnt = 0; cnt < 8; cnt++){ myCounter.increment();//increment it myCounter.show();//display it }//end for loop }//end main() }//end class Design01 |
As you can see, the program resulting from the design effort included a class named Bit with a data member named value as well as methods to set the instance variable, get the instance variable, and add a 1 to the value of the instance variable returning the carry from the binary addition. You should be able to identify these items as nouns or verbs in the narrative description.
Also as you can see, the design resulted in a class named Counter which had three embedded Bit objects as instance variables or data members. This class has a method named initialize that is used to initialize the three-bit counter to 000.
This class also has a method named increment which adds one to the least-significant bit, adds the carry from the least significant bit to the middle bit, and adds the carry from the middle bit to the most-significant bit. This is the typical ripple pattern for a binary counter.
This class also has a show method that displays the values stored in each of the three Bit objects in the order from most-significant to least-significant.
You should also be able to identify nouns and verbs in the narrative description that relate to this class, its data members, and its methods.
This program also contains a class named Design01 which is not represented by a noun in the narrative description. This is because all applications in Java require a controlling class, and in this case the controlling class is named Design01.
The controlling class always contains a main method. In this case the main method instantiates a Counter object initializes it, and shows it. Then it increments the counter eight times, showing its value each time it is incremented. You should also be able to identify nouns and verbs in the narrative description which relate to the manner in which the main method exercises the Counter object.
It is not likely that you will find a perfect correspondence between nouns, verbs, classes, data members, and methods. However, the correlation should be close enough that you should be able to see how a narrative description can be used for a first-pass estimate as to what the classes, data members, and methods should be.
There seems to be fairly widespread agreement among leading authorities in computer science that OOD/OOP, when properly done, produces software systems that are much more maintainable and extensible than systems designed and programmed according to more traditional, Top-Down Design (TDD), procedural techniques.
The reason isn't too difficult to understand. Traditional design techniques result in tightly-coupled systems where a minor change in one part of the system ripples throughout the system, often requiring major changes in many other parts of the system. OOD, on the other hand, (can) produce systems where the different components of the system have minimal coupling, and the need to make a change in one part of the system will require few, if any, changes in other parts of the system.
At this point we encounter a paradox. If OOD/OOP produces better systems, why isn't it the design technique of choice in the early computer science courses at colleges and universities? Probably most colleges and universities are still teaching TDD and procedural programming in their early courses. This part of this lesson won't provide the answer to that question, but hopefully it will provide information that can be used to seek an answer.
One of the interesting discussions in this area centers around the question of just when is a new programming student able to understand and use the concepts of OOD/OOP?
Obviously if they are going to do much serious programming in Java, they need to be ready for OOP very early since Java doesn't allow the use of global variables and global functions. Further, since most of the functionality of Java resides in the large class library referred to as the API, it is impossible to do much useful programming in Java without doing it in an object-oriented way.
However, while Java does enforce the use of an object-oriented style of programming, it doesn't enforce the quality of the design. It is just as easy to produce a bad design in an object-oriented language as in a traditionally procedural language such as Pascal.
Some college professors, such as myself, advocate an approach where beginning students would be taught OOD as their first design technique, and TDD would be relegated to an appendix in the back of the book. Granted, we are in the minority. We believe that it is easier to learn to do the job "right" initially (using OOD), than to learn how to do it "wrong" initially (using TDD), and then have to break the bad habits acquired from using TDD and replace them with good habits using OOD. Bad habits are hard to break.
In any event, the purpose of the next few sections is an attempt to determine the point in a new student's programming education where the student would be capable of doing OOD/OOP on minimal programs. The approach used is to perform an object-oriented design and implementation on a minimal program, and then to tabulate the specific knowledge that the student would require in order to accomplish that design and implementation.
The purpose of the following material is to establish the minimum knowledge that must have been acquired by a beginning programming student before that student would be capable of designing and implementing a specific minimal object-oriented program in Java. (Other specific programs may require other specific knowledge and other languages may require other specific knowledge.)
A somewhat broader purpose is to provide information that can be used to determine at what point OOD/OOP could be effectively introduced into a beginning programming course, and what topics should be emphasized early in the course in order to accelerate that process.
The program uses the noun/verb methodology to convert the program specification into an object-oriented design. A complete walk-through of that process is given at the end of the lesson. Conclusions regarding the minimal knowledge required and a summary of the design results follow the program specification. The conclusions are followed by a detailed discussion of the analysis that led to the conclusions.
Write an object-oriented program that simulates a furnace, a thermostat, and a user.
First, the thermostat gets the set point from the user (the user adjusts the thermostat).
Then the thermostat gets the current temperature from the user (the user substitutes for the built-in thermometer in the thermostat).
Then the thermostat tests the current temperature against the set point. If the current temperature is above the set point, the thermostat announces that there is no need to turn the furnace on. If the current temperature is below the set point, it sends a message to the furnace asking it to turn on.
When the furnace is turned on, the furnace announces that it has been turned on (like a pilot light).
The program terminates at this point.
After reviewing the program that satisfies the previous specification, I have concluded that the student would need to have knowledge of the following topics in order to be able to design and implement the program.
Mechanics of using the Java development environment to create, compile, and run a program.
How comment indicators (// and /* */) are used in Java.
Minimal (possibly memorized) knowledge of the java.io and java.util packages.
Minimal (possibly memorized) knowledge of the use of import statements in Java.
Syntax used to display messages on the screen in Java.
How to get input data from the keyboard.
Every Java application requires a controlling class.
Controlling class requires a main() method with a fixed signature.
Skeleton (possibly memorized) for the controlling class and the skeleton for the main() method.
The main() method controls the flow of a Java application.
Use of primitive types, such as int.
Use of the assignment operator.
Fundamentals of sequence, selection, and loop preferred, but only if/else is required for this particular program.
OOD knowledge sufficient to use the noun/verb methodology to discover the objects, variables, and methods required in the design (also see Classes and Objects below).
Variables in general.
Java has two kinds of variables: primitive and reference.
Syntax required to declare a variable of either type.
Variable name is an abstraction for a group of bytes in memory that contain a value.
Value stored in the memory represented by a primitive variable can be thought of as the value of the variable.
Value stored in the memory represented by a reference variable is (or represents) the address in memory where an object (see Classes and Objects below) is stored.
Address of an object stored in a reference variable is used to gain access to the members of the object.
Objects are instances of classes.
Syntax for instantiating new objects.
Noun/verb design methodology sufficient to discover that this program has three objects: thermostat, furnace, and user.
Sufficient OOD knowledge to discover that the thermostat object uses the furnace and user objects, and how and for what purpose it uses them.
Class definition can contain member variables, member methods, or both.
Syntax required to define methods in a class definition.
Syntax required to declare instance variables in a class definition.
Instance variables can be either primitive variables or reference variables.
Instance variables of a class are directly accessible to the code in all of the methods (and constructors) that belong to the class.
Syntax of constructors and the optional ability to pass parameters to a constructor.
OOD Noun/Verb methodology sufficient to discover that this design needs the following methods:
Skeleton for a method, including the specification of the return type, or void in the method signature.
Invocation of methods with or without parameters.
What to do with incoming parameters when viewed from inside the method.
Proper use of the return statement in a method.
How to cause one method to call other methods.
Use of local variables in methods.
Incoming parameters to a method are local variables (either primitive or reference) in the method.
Mechanism and syntax for sending a message to an object in Java.
Sending a message to another object can result in the object returning an answer and knowledge of what to do with the answer.
Classes/objects established from nouns were:
Variables established from nouns were:
Methods established from verb phrases were:
Implicit actions in methods established from verb phrases were:
.
A previous section stated that the purpose of this lesson is to establish the minimum knowledge that must have been acquired by a beginning programming student before that student would be capable of designing and implementing the minimal object-oriented program specified above using the Java programming language.
This section will discuss code fragments from the program for the purpose of establishing the minimum prior knowledge required of the student to qualify the student to design and write this program.
To begin with, the student would need to know about the two different kinds of comment indicators(// and /* */) used in Java. Javadoc comments can be deferred until later.
The student would need to have minimal knowledge of the concept of objects, variables, message passing, and methods in order to make use of the noun/verb methodology used earlier to discover the objects, variables, and methods required in the design.
All four of these concepts have analogs in the physical world such as
Therefore, it shouldn't be too difficult to introduce these concepts at a very early stage in the educational process and to make the student understand how the programming constructs are often analogs of physical entities.
The required level of knowledge of these concepts will be discussed further along with the code fragments.
As in any programming activity, the student would have to know the mechanics of using the Java development environment in order to be able to enter, compile, and run a program.
The student would have to have minimal knowledge of the java.io and java.util packages in order to accomplish keyboard input.
Along this same line, the student would have to have minimal knowledge of the use of import statements in Java. At this stage in the process, this knowledge could have been simply memorized and understanding of the underlying concepts would not be required (for example, how many Pascal students really understand the underlying concepts for file I/O?).
The student would have to know how to display messages on the screen using statements such as the following:
System.out.println("Main method terminating"); |
Again, at this stage, this could simply be memorized knowledge.
The student would have to have access to, and know how to use a method capable of getting integer data input from the keyboard. Unfortunately, this is one of the most difficult concepts in Java to master, and would of necessity be memorized knowledge at this stage.
The student would need to know that every Java application requires a controlling class, and that the controlling class requires a main() method with a fixed signature. Again, the skeleton for the controlling class and the skeleton for the main() method could be memorized, but the student would need to understand the body of the main() method.
The student would need to know that the main() method controls the flow of a Java application. In particular, when the main() method starts, the program starts running, and when the main() method terminates, the program stops running.
The student would need to have minimal knowledge of the concept of variables, and would need to know that Java has two kinds of variables: primitive variables and reference variables.
The student would need to understand that a variable name is an abstraction for a group of several bytes in memory that contain a value.
The student would need to know that the memory to contain the value represented by the variable of either type can be allocated using a statement of the form:
variableType variableName; |
The student would need to know that the value stored in the memory represented by a primitive variable can be though of as the value of the variable. In other words, the name of a primitive variable evaluates to the value stored in its memory.
The student would need to know that the value stored in the memory represented by a reference variable is (or represents) the address in memory where an object is stored.
Along this line, the student would need to know that the address of an object stored in a reference variable is used to gain access to the object. While this is a rather abstract concept, the party game named scavenger hunt can be used to illustrate the concept, and most students should have no difficulty grasping the concept if it is carefully explained.
(The reason for hedging on address in the above text is that some Java virtual machines use a second level of indirection to refer to an object and the value in the reference is not necessarily the actual address of the object. However, this is all handled automatically, so the student doesn't need to be concerned about it.)
The student would need to know that objects are instances of classes, and that new objects are created (instantiated, caused to occupy memory) using statements of the form:
refVariableName = new ClassName(optional parameters); |
where ClassName is the class of which this object is an instance.
The way this particular program is written, the student would also need to know that it is allowable (but not necessary) to combine the creation of the reference variable and the instantiation of the object using a combined statement such as the following:
Thermostat thermostat = new Thermostat(furnace,user); |
where Thermostat is the name of a class and thermostat is the name of a reference variable. furnace and user are the optional parameters mentioned above. This statement will cause a new object to come into existence and occupy memory. The reference variable named thermostat will contain (or represent) the address of the new object in memory.
In order to have accomplished the object-oriented design of this program, the student would have discovered that there are three objects: thermostat, furnace, and user. The student would also have discovered that the thermostat object uses the furnace and user objects.
One object uses another object if the first object initiates communication with the other object for any purpose. In this case, the thermostat object uses the user object to get the value of the thermostat set point and the current temperature. It uses the furnace object by telling the furnace to turn itself on if the temperature is below the set point and more heat is needed.
The student would need to have knowledge of the concept of object constructors, and in particular, the ability to pass parameters to an object constructor. As a practical matter, this means that the student would need to have knowledge of the invocation of methods in general and the passing of parameters to those methods. This, in turn, means that the student would need to know what to do with incoming parameters when viewed from inside the method.
Having discovered all this, the student would need to translate that knowledge into the instantiation of the three objects shown in the following code fragment. Note that links or references to the furnace and user objects are passed as parameters to the constructor for the thermostat object to make it possible for the thermostat object to use these two objects as described above.
Furnace furnace = new Furnace(); User user = new User(); Thermostat thermostat = new Thermostat(furnace,user); |
As indicated in the following code fragment, the student would need to have minimal knowledge of message passing, and in particular would need to know that the mechanism for sending a message to an object in Java is to invoke a method on a reference to the object using the period to join the name of the reference and the name of the method. For example, the following statement sends a message to the thermostat object to start it running.
thermostat.runThermostat(); |
The following code fragment shows the concepts and statements discussed above in context. A complete program listing is provided at the end of this lesson that shows the individual code fragments, such as this one, in the context of the entire program.
class Ood05{//controlling class public static void main(String[] args){ //Instantiate the objects used in the program Furnace furnace = new Furnace(); User user = new User(); Thermostat thermostat = new Thermostat(furnace,user); //Send a msg to start the thermostat running thermostat.runThermostat(); //Return to here when runThermostat() method terminates System.out.println("Main method terminating"); }//end main()//terminate the program }//end class Ood05 |
Now, on to the next code fragment.
As mentioned earlier, the student would need to know something about classes. In particular, the student would need to know that a class definition can contain member variables, member methods, or both.
The distinction between class and instance members would not be important at this point. Also, issues regarding access control would not be important at this point. All the code could be written using package access.
The student would need to know that the skeleton for a minimal class definition is as follows:
class ClassName{//begin class definition //optional data members //optional method members }//end class definition |
The student would need to know that data members, commonly called instance variables, are declared using statements such as the following:
//Instance Variables for data int setPoint; int currentTemperature; //Instance Variables for links to other objects Furnace furnace; User user; |
This implies that the student has some knowledge of the primitive types, such as int. It also implies that the student knows that instance variables can be either primitive variables or reference variables, because the above code fragment contains two of each.
The student would need to know that the use of a parameterized constructor is optional. In this program, a parameterized constructor is required for the Thermostat class, but is not required for the other classes.
The student would need to know the syntax for a parameterized constructor as shown below:
Thermostat(Furnace theFurnace, User theUser){ //Save links to other objects passed in as parameters furnace = theFurnace; user = theUser; }//end constructor |
This constructor indicates that the student would need to understand the use of the assignment operator, and would need to understand that the instance variables of a class are directly accessible to the code in all of the methods (and constructors) that belong to the class.
The student would also need to know that incoming parameters to a method are essentially local variables (either primitive or reference) in the method and are directly accessible to all of the code in the method. This implies some knowledge of the use of local variables.
The student would need to understand message passing from both the sending and the receiving end. As mentioned earlier, sending a message to an object requires invoking a method of the object on a reference to the object.
The Thermostat class also illustrates message passing from the receiving end as well. The following method is designed to receive and process a method to cause the thermostat object to run.
void runThermostat(){ //send messages to get set point and // current temperature setPoint = user.getSetPoint(); currentTemperature = user.getCurrentTemperature(); //test currentTemperature against setPoint if(currentTemperature setPoint)//make announcement System.out.println("No need to turn furnace on"); else //send msg to furnace to turn itself on furnace.turnFurnaceOn(); //announce runThermostat terminating System.out.println( "Task complete, thermostat terminating"); }//end runThermostat() |
Initially we see that the student would need to know the skeleton for a method, including the specification of the return type, or void in the method signature.
The first two statements show that the student will need to understand that sending a message to another object can result in the object sending back an answer. The student will need to know what to do with that answer. The simplest thing is to assign it to a variable as shown above.
Following this, we see that for this program in particular, the student will need to know how to use the if/else construct. Ideally, the student would know the fundamentals of sequence, selection, and loop before attempting an object-oriented design, even if it is a simple one.
At this point, the student shouldn't have any trouble using the following statement to send a message to the furnace object to tell it to turn itself on to provide some heat:
furnace.turnFurnaceOn(); |
This method ends with the runThermostat() method displaying a termination message and returning to the method from which it was called (the method that sent the message to activate it).
The concepts and code fragments discussed above are shown in context in the following code fragment. This code fragment is shown in the context of the entire program later.
//This is the class from which the thermostat object // is instantiated. class Thermostat{ //Instance Variables for data (could use local variables) int setPoint; int currentTemperature; //Instance Variables for links to other objects Furnace furnace; User user; //-----------------------------------------------------// //Parameterized Constructor Thermostat(Furnace theFurnace, User theUser){ //Save links to other objects passed in as parameters furnace = theFurnace; user = theUser; }//end constructor //-----------------------------------------------------// //Instance Methods void runThermostat(){ //send msg to get setPoint setPoint = user.getSetPoint(); //send msg to get currentTemperature currentTemperature = user.getCurrentTemperature(); //test currentTemperature against setPoint if(currentTemperature setPoint)//make announcement System.out.println("No need to turn furnace on"); else //send msg to furnace to turn itself on furnace.turnFurnaceOn(); //announce runThermostat terminating System.out.println( "Task complete, thermostat terminating"); }//end runThermostat() }//end class Thermostat |
That brings us to the Furnace class from which the furnace object is instantiated. This is the simplest class of the three. It has no instance variables and has only one method.
The method named turnFurnaceOn() receives and processes messages to turn the furnace on to produce more heat. In this case, the only processing involved is to display a message indicating that the furnace has been turned on. There is nothing new for the student to be concerned about in this class.
//Class from which furnace object is instantiated class Furnace{ //Instance Variables - none //Instance Methods void turnFurnaceOn(){//accepts turn-on message System.out.println("The furnace is on"); }//end turnFurnaceOn() }//end class Furnace |
The next class is a class from which the user object is instantiated. The student will need to understand that the user object is an abstraction for the human user of the system.
In particular, the user object receives messages from other objects requesting information about the set point on the thermostat and the current temperature. The object then forwards those messages to the human user, and gets input from the human user by accessing the keyboard.
For example, the method shown below named getSetPoint() accepts messages from other objects requesting information on the set point. The method then
The student will need to know how to properly use the return statement in a method.
The student will also need to know how to call other methods of the same class.
int getSetPoint(){//accepts get setPoint message //Get set point data from human user System.out.println("Enter set point"); return getKeyboardInput(); }//end getSetPoint() |
The method named getCurrentTemperature() behaves essentially the same as the one discussed above, and doesn't require any new knowledge on the part of the student so I won't discuss it here.
An understanding of the method named getKeyboardInput() in the following code fragment is clearly beyond the reach of the beginning Java student. This method exposes one of the gaping holes in the Java development environment. While the input/output system of Java is extremely flexible, it is also difficult to understand and and difficult to use for simple I/O.
Many intermediate-level Java programmers won't understand how this method works without considerable study. Fortunately, this is one of those standard operations that can simply be cut and pasted into a program to get the job done.
Peter van der Linden has provided an I/O class called EasyIn in his book Just Java 1.1 and Beyond, Third Edition which provides similar code for all the basic I/O operations, and has given the rest of us permission to use that code. The method shown below is a modified version of one of the methods in his EasyIn class.
The following code fragment puts the concepts and methods discussed above in context. This code fragment is shown in the context of the entire program in the next section.
//Class from which user object is instantiated. An object // of this class forms the interface between the program // and the human user. class User{ //Instance Variables - none //Instance Methods int getSetPoint(){//accepts get setPoint message //Get set point data from human user System.out.println("Enter set point"); return getKeyboardInput(); }//end getSetPoint() //-----------------------------------------------------// int getCurrentTemperature(){//accepts get temperature msg //Get current temperature data from human user System.out.println("Enter current temperature"); return getKeyboardInput(); }//end getCurrentTemperature() //-----------------------------------------------------// //This utility method will read an int from the keyboard. // Based on Peter van der Linden's EasyIn class. See // "Just Java 1.1 and Beyond, Third Edition" int getKeyboardInput(){ try { BufferedReader br = new BufferedReader( new InputStreamReader( System.in )); StringTokenizer st = new StringTokenizer( br.readLine()); return Integer.parseInt(st.nextToken()); }catch (IOException excep) { System.err.println("IO Exception in readInt"); return 0; }//end catch }//end getKeyboardInput() }//end class User |
.
The following program listing puts the previous discussion and code fragments in the context of the entire program.
/*File Ood05.java Copyright 1997, R.G.Baldwin The purpose of this program is to establish the minimum knowledge that must have been acquired by a beginning programming student before that student would be capable of designing and writing a minimal object-oriented program. The program uses the noun/verb methodology to convert the program specification into an object-oriented design. Program Specification: Write an object-oriented program that simulates a furnace, a thermostat, and a user. First, the thermostat gets the set point from the user. Then the thermostat gets the current temperature from the user. Then the thermostat tests the current temperature against the set point. If the current temperature is above the set point, the thermostat announces that there is no need to turn the furnace on. If the current temperature is below the set point, it turns the furnace on. When the furnace is turned on, the furnace announces that it has been turned on (like a pilot light) The program terminates at this point. End program specification The bulk of the Analysis and Design of this program was explained earlier in this lesson and won't be repeated here. The result of that analysis and design effort is shown below. Establish classes/objects from nouns: Furnace/furnace Thermostat/thermostat User/user Establish variables from nouns: setPoint currentTemperature embedded links to other objects Establish methods from verb phrases: void runThermostat() in Thermostat class Thermostat(Furnace, User) constructor in Thermostat class int getSetPoint() in User class int getCurrentTemp() in User class void turnFurnaceOn() in Furnace class int getKeyboardInput() utility method in User class Establish implicit actions in methods from verb phrases: send getSetPoint msg - runThermostat() send getCurrentTemp msg - runThermostat() test currentTemperature vs setPoint - runThermostat() announce no need to... - runThermostat() announce furnace turned on - turnFurnaceOn() terminate program - runThermostat() Tested using JDK 1.1.3 under Win 95. Two sets of output from the program follow: Enter set point 75 Enter current temperature 80 No need to turn furnace on Task complete, thermostat terminating Main method terminating -------------- Enter set point 80 Enter current temperature 75 The furnace is on Task complete, thermostat terminating Main method terminating **********************************************************/ import java.io.*; import java.util.*; //=======================================================// class Ood05{ //All Java applications require a controlling class // with a main method. public static void main(String[] args){ //Instantiate the objects used in the program // Could instantiate anonymously in call to Thermostat // constructor, but would not illustrate the object // concept as clearly. Furnace furnace = new Furnace(); User user = new User(); //Make the Thermostat object aware of the Furnace // object and the User object when it is instantiated. Thermostat thermostat = new Thermostat(furnace,user); //Start the thermostat running thermostat.runThermostat(); //Return to here when runThermostat() method terminates System.out.println("Main method terminating"); }//end main() }//end class Ood05 //=======================================================// //This is the class from which the thermostat object // is instantiated. class Thermostat{ //Instance Variables for data (could use local variables) int setPoint; int currentTemperature; //Instance Variables for links to other objects Furnace furnace; User user; //-----------------------------------------------------// //Parameterized Constructor Thermostat(Furnace theFurnace, User theUser){ //Save links to other objects passed in as parameters furnace = theFurnace; user = theUser; }//end constructor //-----------------------------------------------------// //Instance Methods void runThermostat(){ //send msg to get setPoint setPoint = user.getSetPoint(); //send msg to get currentTemperature currentTemperature = user.getCurrentTemperature(); //test currentTemperature against setPoint if(currentTemperature setPoint)//make announcement System.out.println("No need to turn furnace on"); else //send msg to furnace to turn itself on furnace.turnFurnaceOn(); //announce runThermostat terminating System.out.println( "Task complete, thermostat terminating"); }//end runThermostat() }//end class Thermostat //=======================================================// //Class from which furnace object is instantiated class Furnace{ //Instance Variables - none //Instance Methods void turnFurnaceOn(){//accepts turn-on message System.out.println("The furnace is on"); }//end turnFurnaceOn() }//end class Furnace //=======================================================// //Class from which user object is instantiated. An object // of this class forms the interface between the program // and the human user. class User{ //Instance Variables - none //Instance Methods int getSetPoint(){//accepts get setPoint message //Get set point data from human user System.out.println("Enter set point"); return getKeyboardInput(); }//end getSetPoint() //-----------------------------------------------------// int getCurrentTemperature(){//accepts get temperature msg //Get current temperature data from human user System.out.println("Enter current temperature"); return getKeyboardInput(); }//end getCurrentTemperature() //-----------------------------------------------------// //This utility method will read an int from the keyboard. // Based on Peter van der Linden's EasyIn class. See // "Just Java 1.1 and Beyond, Third Edition" int getKeyboardInput(){ try { BufferedReader br = new BufferedReader( new InputStreamReader( System.in )); StringTokenizer st = new StringTokenizer( br.readLine()); return Integer.parseInt(st.nextToken()); }catch (IOException excep) { System.err.println("IO Exception in readInt"); return 0; }//end catch }//end getKeyboardInput() }//end class User //=======================================================// |
.
This section walks through the noun/verb analysis and design process for this object-oriented program. Note that very little in the way of explanation is provided, so it may make for fairly difficult reading.
The specification for the program is repeated here for your convenience.
Write an object-oriented program that simulates a furnace, a thermostat, and a user.
First, the thermostat gets the set point from the user (the user adjusts the thermostat).
Then the thermostat gets the current temperature from the user (the user substitutes for the built-in thermometer in the thermostat).
Then the thermostat tests the current temperature against the set point. If the current temperature is above the set point, the thermostat announces that there is no need to turn the furnace on. If the current temperature is below the set point, it sends a message to the furnace asking it to turn on.
When the furnace is turned on, the furnace announces that it has been turned on (like a pilot light).
The program terminates at this point.
Extract nouns as candidates for objects and instance variables:
furnace, thermostat, set point, user, current temperature
.
Extract verb phrases as candidates for methods and implicit actions within methods:
Write..program,(programmer action)
simulates..furnace..thermostat, (programmer action)
thermostat gets..set point..user (program action)
thermostat gets..current temperature..user (program action)
thermostat tests..current temperature..set point (program action)
thermostat announces..no need.. (program action)
it(thermostat) turns.. furnace on. (program action)
furnace..announces..turned on (program action)
program terminates (program action)
.
Establish classes/objects from nouns:
Furnace/furnace, Thermostat/thermostat, User/user
.
Establish variables from nouns:
setPoint, currentTemperature, embedded links to other objects
.
Establish methods from verb phrases:
void runThermostat() in Thermostat class
Thermostat(Furnace, User) constructor in Thermostat class
int getSetPoint() in User class
int getCurrentTemp() in User class
void turnFurnaceOn() in Furnace class
int getKeyboardInput() utility method in User class
.
Establish implicit actions in methods from verb phrases:
send getSetPoint msg - runThermostat()
send getCurrentTemp msg - runThermostat()
test currentTemperature against setPoint - runThermostat()
announce no need to... - runThermostat()
announce furnace turned on - turnFurnaceOn()
terminate program - runThermostat()
Q - Both C++ and Java support the older-style top-down procedural programming approach as a complete solution to a programming problem: True or False?
A - False. In Java all programs must be written in an object-oriented style.
Q - Describe how object-oriented programming differs from typical top-down procedural programming?
A - The classic approach to procedural programming often begins with development of the functions and procedures and then progresses to development of the data structures. The result is frequently a very poor model of the problem being solved. The object-oriented programming approach attempts to express computer programs in ways that model how people perceive the world.
Q - Describe the object-oriented paradigm as it relates to the solution problems faced by persons working in other crafts.
A - People who solve problems in other crafts deal with their problem domains by concentrating on the objects and letting the characteristics of those objects determine the procedures to apply to them. (The characteristic of a nail suggests that you hit it on the head with a hammer, for example.).
Q - The terminology of object-oriented programming is part of the everyday language of programmers with no training in object-oriented programming: True or False?
A - False. Object-oriented programming has developed its own jargon which may differ widely depending on which book you may be reading at the time.
Q - List the three characteristics of an object-oriented language.
A - Encapsulation, inheritance, and polymorphism.
Q - Knowledge of the characteristics of new types is built into the Java compiler: True or False?
A - False. The java compiler knows nothing about the characteristics of a new type until provided a definition of the new type through use of a class definition.
Q - What are the two general aspects of a new type that are defined when a Java programmer defines a new type?
A - Data representation and behavior.
Q - In Java, the struct keyword is used to define a new type: True or False?
A - False. While the struct keyword can be used to define a new type in C++, only the class keyword can be used to define a new type in Java.
Q - An object normally is considered to have _____ and _____.
A - State and behavior.
Q - The state of an object is defined by what?
A - The current values of its instance variables or data members.
Q - The behavior of an object is defined by what?
A - The methods of the class from which the object was instantiated.
Q - What is the source of the jargon "To instantiate an object?"
A - This jargon derives from the fact that an object is an instance of a class.
Q - An object is an _______ of a class.
A - Instance
Q - A programmer encapsulates the data representation and behavior of an abstract data type into a class, thereby defining its implementation and interface: True or False?
A - True
Q - An encapsulated design typically hides its implementation from the class user and reveals its interface: True or False?
A - True
Q - In accordance with the theory of reusable code, the programmer should always be concerned with the implementation of the data storage mechanism in a class defined by another programmer: True or False? Explain your answer.
A - False. If a class is properly defined, adequately tested, and properly documented, it should be possible for another programmer to make use of the class without concern for the actual data storage mechanism employed by the class definition.
Q - What does it mean to "send a message to an object" in Java?
A - Sending a message to an object in Java means to invoke one of its methods.
Q - The interface of a class consists of its private methods: True or False?
A - False. The private methods of a class are not accessible and therefore cannot serve as the interface.
Q - What is usually the purpose of accessor or interface methods of a class?
A - The normal purpose of accessor or interface methods is to restrict access to the instance variables and to allow them to be manipulated only in a controlled manner.
Q - Good programming practice calls for always making the data members or instance variables of a class public: True or False.
A - False. Good programming practice usually calls for making the instance variables of a class private and providing public accessor or interface methods which allow for controlled manipulation of those instance variables.
Q - The interface methods of a class should always be bound to a particular implementation of the data storage of the class: True or False?
A - False. The interface methods should not be bound to a particular implementation of the data storage mechanism. This allows for later improving upon the data storage mechanism without rendering the interface methods invalid.
Q - Describe the messaging process in an object-oriented program.
A - Someone wrote that an object-oriented program consists simply of a bunch of objects laying around sending messages to each other. This might be a slight exaggeration, but is not far from the truth.
Q - What are some of the names given to the methods or functions that are embedded inside of a class definition?
A - Instance methods and class methods.
Q - In inheritance, what are some of the common names for the class that is inherited from, and what are some of the common names of the new class?
A - Inherited from: base class and super class. New class: derived class and subclass.
Q - Can a subclass be further subclassed?
A - Normally yes although in Java it is possible to prevent further subclassing through use of the final keyword.
Q - A derived class inherits the data representation and behavior of the base class and can also add new data representation and behavior: True or False?
A - True.
Q - What do you call the process of modifying a derived class so that some portion of the behavior of its objects differs from the behavior of objects of the base class (other than simply adding new behavior)?
A - Implementing runtime polymorphism by overriding methods.
Q - You can always instantiate objects of both the base class and the derived class: True or False?
A - False. You cannot instantiate objects of the base class if it is an abstract base class.
Q - What do you call a base class that exists only to be derived from?
A - An abstract base class.
Q - Describe one or more uses for inheritance.
A - When several of your abstract data types have characteristics in common, you can design their commonalities into a single base class and separate their unique characteristics into unique derived classes.
Q - What characteristics existing among several classes might cause you to decide to combine some of those characteristics into a base class?
A - Common characteristics.
Q - The ISA relationship is more closely related to
A - Inheritance.
Q - Both Java and C++ fully support multiple inheritance: True or False?
A - False. Java does not support multiple inheritance.
Q - Polymorphism exists when functions or operators are overloaded or overridden to cause them to perform operations not inherently recognized by the compiler: True or False?
A - True
Q - Function overloading is a form of runtime polymorphism: True or False?
A - False. Function overloading is a form of compile-time polymorphism.
Q - Operator overloading is fully supported in both C++ and Java: True or False?
A - False. Unfortunately, operator overloading is not supported by Java, at least that is the case with the JDK 1.1 specification.
Q - Polymorphic behavior exists only in the most modern languages such as C++ and Java: True or False?
A - False. For example, early languages such as BASIC, interpret the plus operator to be an arithmetic operator when applied to numeric data and to be a concatenation operator when applied to string or character data.
Q - Overloading a method is the process of modifying the method to cause it to behave differently relative to objects of the base class and the derived class: True or False?
A - False. This description applies to overriding a method.
Q - Overriding a method is the process of modifying the method to cause it to behave differently relative to objects of the base class and the derived class: True or False?
A - True
Q - What are the two kinds of polymorphism recognized by many authors?
A - Runtime polymorphism and compile-time polymorphism.
Q - The implementation of polymorphism essentially consists of providing multiple functions or methods with the same name, and calling the correct one at the appropriate time, True or False.
A - True
Q - If the construction is such that it is clear at compile time which version of two or more methods having the same name should be called in a particular instance, this is referred to as ______________________ polymorphism.
A - Compile-time polymorphism
Q - Name one form of compile-time polymorphism.
A - Operator overloading
Q - In general, overriding methods results in what type of polymorphism?
A - Runtime polymorphism
Q - In general, overriding methods results in what type of binding?
A - Late binding
Q - What is the maximum number of free-standing functions that you are allowed to define in Java?
A - None. Java does not support free-standing functions.
Q - What are the two categories of methods that can exist in a Java program?
A - Class methods and instance methods
Q - How do you invoke class methods in Java?
A - Using the name of the class and the name of the method joined by a period
Q - How do you invoke instance methods in Java?
A - Using the name of an object and the name of the method joined by a period
Q - What is the maximum number of global variables and objects that you are allowed to declare in Java?
A - None. Java does not support global variables or objects.
Q - What are the three categories of variables that can exist in a Java program?
A - Class variables, instance variables, and local variables in methods.
Q - How are class variables accessed in Java?
A - Using the name of the class and the name of the variable joined by a period.
Q - How are instance variables accessed in Java?
A - Using the name of an object and the name of the variable joined by a period.
Q - What is the access scope for a local variable in a method in Java?
A - Local variables in methods can only be accessed within the scope of the method as in C, Pascal, and other languages which support local variables within sub-programs. In some cases in Java, the actual scope of a local variable may be restricted to less than the full scope of the method by declaring it within a brace-enclosed block.
Q - Java requires the use of exception handling: True or False?
A - True.
Q - Explain in general terms the use of the keywords try, throw, and catch.
A - These keywords are used to monitor for exceptional conditions within your program, and to transfer control to special exception-handling code whenever an exceptional condition is detected.
Q - In general, in both Java and C++, you must write the code which throws all exception objects: True or False?
A - False. That is pretty much true for C++. However, for Java, there are numerous situations where an exceptional condition will be detected and thrown by the runtime system which can automatically transfer control to special exception-handling code which you write.
Q - An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions: True or False?
A - True.
Q - All exceptions in Java are thrown as objects which must derive either directly or indirectly from a particular class. What is the name of that class?
A - When an exceptional condition causes an exception to be thrown in Java, that exception is an object derived, either directly, or indirectly from the class Throwable.
Q - There is a large group of exceptional conditions in Java that must be recognized in order for your program to compile. What is meant by recognized in this context?
A - Recognition can consist of either catching and processing the exception, or passing it up to the next level in the method invocation hierarchy.
Q - What is the keyword that is used to pass an exceptional condition up to the next level in the method-invocation hierarchy?
A - If your method passes an exception up to the next level in the invocation hierarchy, this must be declared along with the method signature using the throws keyword.
Q - List three advantages of using exception handling rather than "traditional" error management techniques.
A - Exception handling provides the following advantages over "traditional" error management techniques:
Q - When you use the narrative description of your problem to generate the first-cut object-oriented design, what is suggested by the nouns in your narrative, and what is suggested by the verbs?
A - The nouns suggest the possibility of classes and/or data members of the classes while the verbs suggest methods.
Q - Write a Java application that meets the following specifications:
/*SampProg01.java from lesson 4 Copyright 1997, R.G.Baldwin Without looking at the solution which follows, write an application that will execute the following main method and display a date in month/day/year syntax followed by your name. public static void main(String[] args){ //define main MyDateClass obj = new MyDateClass(); //instantiate obj obj.setDate(4,8,37); //store data in instance var //display instance variables System.out.println( obj.getDate() ); System.out.println("Dick Baldwin"); }//end main **********************************************************/ import java.util.*; class MyDateClass { //define new type using keyword class int month, day, year; // instance variables of the class final double AVOGADRO = 6.23; //instance method to store data void setDate(int mo, int da, int yr) { int[] myArray; myArray = new int[25]; month = mo; day = da; year = yr; }//end method setDate() String getDate()//instance method to get data { return "" + month + "/" + day + "/" + year; }// end method getDate() }//end class MyDateClass definition //Driver program follows class SampProg01 { //define the controlling class public static void main(String[] args){ //define main MyDateClass obj = new MyDateClass(); //instantiate obj obj.setDate(4,8,37); //store data in instance variables //display instance variables System.out.println( obj.getDate() ); System.out.println("Dick Baldwin"); }//end main }//end SampProg01 class |
-end-
Copyright 2000, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.