Caveat: The author wrote this set of notes for a course that used Java before generics were added to the language. It has not been fully modified for either Java generics or the Scala concepts and notations that the author currently uses.
For the purposes of this discussion, assume that software development proceeds through the four lifecycle phases shown in the following diagram.
.__________________. | | | Analysis |<--: |__________________| | | | | ^ .________V_________. | | |-->: | Design | | |__________________|<--. | | | ^ .________V_________. | | |-->: | Implementation | | |__________________|<--: | | | | .________V_________. ^ | | | | Maintenance |-->: |__________________|
In the analysis phase we move from a vague description of the problem to be solved to a precise and unambiguous requirements specification.
The requirements specification might be a precise, but informal description written in careful natural language text and diagrams. Alternatively, the specification might be a formal description written in a mathematically precise language. Or the requirements specification might be something in between these.
The requirements specification should be:
In the design phase, we move from a requirements specification to a design specification. The design specification gives the system structure. The design tasks are to:
In the implementation phase, we move from a design specification to a tested executable system. The implementation tasks are to:
In the maintenance phase, we move from a complete "working" system to a modified system. The maintenance tasks are to:
Observations:
Conclusions:
The type of software development projects familiar to most students can be described as programming in the small. Such projects have the following attributes:
Programming in the large characterizes projects with the following attributes:
The techniques of object-oriented design and programming are useful in both programming in the small and programming in the large situations. However, some of the techniques are best appreciated when the difficulties of programming in the large are understood.
In contemporary practice, most software engineers approach the design of programs from an object-oriented perspective.
Assumptions:
Assumption 1 simplifies analysis, design, and implementation--makes them more reliable.
Assumptions 2 and 3 support reuse of code, prototyping, and incremental development.
Assumption 4 supports design for change.
The object-oriented approach:
Our object model includes four basic components:
Objects are characterized by:
An object is a separately identifiable entity that has a set
of operations and a state that records the effects of the
operations. That is, an object is essentially the same as an abstract
data structure as discussed in this author's notes on Data
Abstraction.
The various components of the state are sometimes called the
attributes of the object.
As an example, consider an object for a student desk in a
simulation of a classroom. The relevant state might be
attributes like location, orientation, person using, items in basket,
items on top, etc. The relevant operations might be
state-changing operations (mutators) such as "move the desk", "seat
student", or "remove from basket" or might be state-observing
operations (accessors) such as "is occupied" or "report items on
desktop".
Note: This section describes objects that are mutable, that
is, whose states may change. Objects may be immutable, in
which case the state cannot change after the objects are initialized.
However, some operations may "change" the state by creating a copy of
the object whose state is modified in some way.
A language is object-based if it supports objects as a
language feature.
Object-based languages include Ada, Modula, Clu, C++, Java, Scala, C#,
and Smalltalk. Pascal (without module extensions), Algol,
Fortran. and C are not inherently object-based.
A class is a template for creating objects.
A class description includes definitions of
As an example, again consider a simulation of a classroom. There
might be a class of
An object-based language is class-based if the concept of
class occurs as a language feature and every object has a class.
Class-based languages include Clu, C++, Java, Scala, C#, Smalltalk,
and Ada 95. Ada 83 and Modula are not class-based.
A class C inherits from class P if C's objects form a subset
of P's objects.
The importance of inheritance is that it encourages sharing and reuse
of both design information and program code. The shared state and
operations can be described and implemented in base classes and shared
among the subclasses.
As an example, again consider the student desks in a simulation of
a classroom. The
The simulation might also include a
In Java and Scala, we can express the above inheritance relationships
using the
Both
The
Some languages support multiple inheritance as shown above for
Because multiple inheritance is both difficult to use correctly and to
implement in a compiler, the designers of Java and Scala did not
include multiple inheritance of classes as features. Java has a
single inheritance hierarchy with a top-level class named
To see some of the problems in implementing multiple inheritance,
consider the above example. Class
The difficulties with multiple inheritance are greatly decreased if we
restrict ourselves to inheritance of class interfaces (i.e.,
the signatures of a set of operations) rather than a supporting the
inheritance of the class implementations (i.e., the instance
data fields and operation implementations). Since interface
inheritance can be very useful in design and programming, the Java
designers introduced a separate mechanism for that type of
inheritance.
The Java
A Java
This definition requires the
The Scala
The concept of polymorphism (literally "many forms") means
the ability to hide different implementations behind a common
interface.
Polymorphism appears in several forms.
Consider the following Java definitions in the same class:
The choice among the alternatives can be done statically at the time
the program is compiled.
Java supports overloading of method calls, but does not support
user-defined overloading of operator symbols. (C++ supports both.)
In Scala, the usual operator symbols are just identifiers that denote
methods defined on the various classes. Hence, they can be
overloaded and overridden in a manner similar to other method names.
In the context of an object-oriented language, this kind of
polymorphishm enables the definition of generic classes, that
is, classes in which methods and attributes can be defined in terms of
type parameters of the class. When a class is instantiated into an
object, the programmer must supply specific types for these parameters.
The original versions of Java did not support parameterized class
definitions. However, Java 1.5 added a generic class mechanims.
Scala enhanced this concept further. The
This form of polymorphism is carried out at run time. Given an object
(i.e., class instance) to which an operation is applied, the system
will first search for an implementation of the operation associated
with the object's class. If no implementation is found in that class,
the system will check the superclass, and so forth up the hierarchy
until an appropriate implementation is found. Implementations of the
operation may appear at several levels of the hierarchy.
The combination of dynamic binding with a well-chosen inheritance
hierarchy allows the possibility of an instance of one subclass being
substituted for an instance of a different subclass during execution.
Of course, this can only be done when none of the extended operations
of the subclass are being used.
As an example, again consider the simulation of a classroom. As in
our discussion of inheritance, suppose that the
As we noted before, invocation of operations to
Extending the example, suppose that we need a special version of the
To do this, we can define this special version of the
The definition of
In Java, this can be expressed as follows:
A class-based language is object-oriented if class
hierarchies can be incrementally defined by an inheritance mechanism
and the language supports polymorphism by inheritance along these
class hierarchies.
Object-oriented languages include C++, Java, Scala, C#, Smalltalk, and
Ada 95. The language Clu is class-based, but it does not include an
inheritance facility.
Other object-oriented languages include Objective C, Object Pascal,
Eiffel, and Oberon 2.
The task of the analysis phase is to define the problem and write a
clear, consistent, complete, and precise description of the system to
be constructed. The analysts must elicit the requirements for the
system from the clients and communicate the requirements to the system
designers, who have the task of designing the new system.
The analysts must determine the scope of the system: What must the
system accomplish? And, perhaps just as importantly, what behaviors
are clearly outside the system in its environment?
In approaching the requirements analysis for a new system, the
analysts should:
This includes all documents that directly address the requirements as
well as documents that may indirectly address them. The indirect
sources include such items as memos, meeting minutes, problem reports
from the (manual or automated) system being replaced, and employee,
supplier, or customer complaints and suggestions. The indirect
sources might also include information about how recent or expected
future changes in the business, regulatory, social, or technological
environment may impact the requirements for the system.
This includes the outputs (e.g., reports) generated in the current
system and the descriptions of outputs desired from the new system.
The analysts should talk to a wide range of experienced users of the
current system and its outputs and take careful notes. The
experienced users are the ones who know how the system is "really"
being used in practice -- what its strengths and weaknesses are, what
works well and what must be worked around, who uses what aspect of the
system or its outputs, etc.
The users of the system may have many different relationships to the
system and, hence, will likely have different perspectives on how it
works. The users might include a wide range of employees of the
organization (clerical personnel, computer operators, managers,
technical professionals, factory workers, salespersons, etc.) and
might also include customers or suppliers to the client organization.
The analysts should review the official documentation on the current
system (whether automated or manual) as well as any unofficial or
personal notes users or maintainers of the system have.
Good analysts are good detectives! Among the mass of detail, the
analysts must find the clues that allow them to solve the mystery of
what system the client needs. It is important that analysts keep a
complete record of the information they have gathered and their
reasoning on any "conclusions" they reach about that information.
The result of the analysts' work is a document called the
requirements specification. Typically this will be a natural
language (e.g., English) document. The writers of this document
should use great care, using the language in a clear, consistent, and
precise manner. The writers are establishing the vocabulary for
communication with the clients and among the designers, implementers,
and testers of the system.
The goals of the design phase are to:
What kinds of objects do we have?
What does each kind of object do? What information does it hold?
Does one kind of object use another in some way? Is it a
special case of another kind?
The actual design process is iterative. Elaboration of a class may
lead to identification of additional classes or changes to those
already identified.
Classes should be crisply defined. It should be as easy as
possible to determine what class an object belongs in. Coming up with
a good categorization is often difficult in complex situations.
The responsibilities of a class are of two types:
Responsibilities should be defined precisely. Ambiguity and
imprecision will likely cause problems later in the design and
implementation.
The collaboration relationships among classes should not be
excessively complex. A collaboration between two classes means that
one class depends upon the other in some way. A change in one class
may necessitate a change in the other.
The information gathered in the design phase is the basis for the
implementation. A good design makes the implementation easier and
faster and the resulting product more reliable.
The basic methods for identifying classes and responsibilities are as
follows.
To develop an object-oriented design model for this application, as
designers we can carry out the following steps:
Begin by listing the nouns, noun phrases, and pronoun antecedents from
the requirements specification, changing all the plurals to singular.
Other classes may be be implicit in the specification or may emerge as
the design of individual classes proceed and the designer's knowledge
of the application domain increases. For example, the design may need
classes corresponding to:
Several, but not necessarily all, of these will become classes in our
design.
Begin by listing the verbs from the specification. Listing the
objects of transitive verbs may also be helpful.
Transitive verbs become operations that respond to
inputs to the object. All of the above verbs except "to be arranged"
are transitive and, hence, will likely give rise to operations.
Intransitive verbs typically specify attributes of
classes. For example, "to be arranged in alphabetical order" in the
above example denotes an ordering property of the entries in a
listing. It is not an operation. Of course, in some cases, this kind of
attribute might require a "sort" operation.
At this point, we may want to narrow the list of candidate classes
by quickly dividing them into three categories based on their
relevance to the system scope:
In the telephone book example, these might include telephone book,
entry, student, faculty, staff member, and so forth.
In the telephone book example, these might include
university which we do not need to explicitly model in this
application) and printer (which is handled by other
software/hardware outside of this application).
In the telephone book example, these might include the
computerized telephone book system, an item that definitely
will be built (it is the whole system) but for which we may not need
an explicit class.
For example, computerized telephone book, telephone
book, and directory can be combined into a single
Similarly, entry and person can be combined into a
single
In addition, all the
Some candidate classes may turn out to represent information held by
other classes instead of being classes themselves.
A candidate class may be an attribute (i.e., a responsibility) of
another class rather than itself a class if:
For example, we might choose to make the entity name an
immutable string and make it an attribute of a class
Adjectives modify nouns. In our technique, nouns give rise to classes
or objects.
Prepositional phrases may modify nouns in the requirements
specification. Such phrases may lead to subclasses, objects, or
instances as described above for adjectives.
Adverbs may modify adjectives in the requirements
specification. They provide other information about the adjective
that should be considered in the analysis. For example, a reference
to a "brilliantly red truck" might signal the need for another
attribute, subclass, or instance that is different from a plain "red
truck" or from a "dull red truck".
A hot spot is a portion of the system that is likely to change from
one system variant to another.
To readily support change, encapsulate the variable aspects within
components and design the interfaces of and the relationships among
system components so that change to the architecture is seldom
necessary.
This technique enables convenient reuse of the relatively static,
overall system architecture and common code. It makes change easier to
implement at hot spots.
In the telephone book example, we might consider aspects of the
application that likely will need to change over time to meet the
needs of the identified client. The hot spots identified might be how
the telephone book information is stored, how the printed listings are
formatted, what the user interface looks like, what exact information
is stored in entries, what kinds of computing platforms the
application executes on, what types of printers are used, etc. The
detailed design decisions relative to these issues should be
encapsulated within single components and documented well.
Sometimes we need to approach the design of an application as the
design of a whole product line rather than the design of a
single product.
For example, we might consider what might change if we needed to
modify the application to handle the needs of other organizations. In
the telephone book example, what would need to change if we wanted to
make it useful to other universities? to government agencies? to
private nonprofit service agencies? to businesses? to institutions in
other countries (i.e., internationalization)? Clearly, the
categorization of student, staff, and professor would need to be
flexible. The handling of the name and address data and of other
aspects of the
Similarly, we can consider what might need to change if we wanted to
expand the application from just maintaining telephone book
information to other types of membership applications. Can we
separate the application-specific behaviors from more general
behaviors? Can we make it so that it is easy to design new specific
behaviors and plug them in to the overall application structure?
A design pattern is a
design structure that has been successfully used in a similar
context--i.e., a reusable design.
Design patterns may be distillations of the development organization's
experience -- or may be well-known general patterns selected from a
book such as Design Patterns: Elements of Reusable
Object-Oriented Software by the Gang of Four (Gamma, Helm,
Johnson, and Vlissides) (Addison-Wesley, 1995).
The use of the design pattern may require the addition of new classes
to the design or the modification of core classes.
An example of a high-level pattern is the Pipes and Filters pattern.
In Unix for instance, a filter is a program that reads a stream of
bytes from its standard input and writes a transformed stream to its
standard output. These programs can be chained together with the
output of one filter becoming the input of the next filter in the
sequence via the pipe mechanism. Larger systems can thus be
constructed from simple components that otherwise operate
independently of one another.
An example of a lower level pattern is the Iterator. This pattern
defines general mechanisms for stepping through container data
structures element by element. For instance, a Java object that
implements the
A framework is a collection of classes--some abstract, some
concrete--that captures the architecture and basic operation of an
application system. Systems are created by extending the given
classes to add the specialized behaviors.
Frameworks are "upside down libraries" -- system control resides in
framework code that calls "down" to user-supplied code.
Examples of software frameworks include graphical user interface
toolkits like the Java AWT and some discrete event simulation
packages.
To fit an application into a framework may require addition or
modification of core classes.
For example, in the telephone book example associate
Sometimes there might be a choice on where to associate an operation.
For example, the "insert person into telephone book" operation could
be a operation of
Which is better? Associating the operation with
During design we should not be concerned with the minute details of
the implementation. However, it is appropriate to consider whether
there is a "reasonable" implementation. In fact, it is better to make
sure there are two different possible implementations to ensure
flexibility and adaptability.
It is good to have more than one person involved in a design. A
second designer can review the first's work more objectively and ask
difficult questions -- and vice versa.
The selection of names for classes and operations is an important
task. Give it sufficient time and thought.
Two classes in a design may be related in one of several ways. The
three relationships that are common are:
Class A uses B when:
If objects of A can carry out all operations without any awareness of
B, then A does not use B.
From the telephone book example,
If class A uses class B, then a change to class B (particularly to its
public interface) may necessitate changes to class A.
The following principle will make modification of a design and
implementation easier.
Class A uses class B for aggregation if objects of A contain
objects of B.
Note: Object A contains (has an) object B if A has
an instance variable that somehow designates object B--a Java
reference to B, the index of B, the key of B, etc.
Aggregation is a one-to-N relationship; one object might contain
several others.
Note: Aggregation is a special case of the use relationship. If asked
to identify the aggregation and use relationships, identify an
aggregation relationship as such rather than as a use relationship.
For example, objects of the
The Pascal
Class D inherits from class B if all objects of D are also
objects of B.
As noted previously,
In the telephone book example, we design
Inheritance can lead to powerful and extensible designs.
However, use and aggregation are more common than inheritance.
The outputs of the object-oriented design phase include:
The purpose of the implementation phase is to code, test, and
integrate the classes into the desired application system.
Object-oriented development involves an integration of testing with
implementation:
Note: If we seek to evolve a prototype into a full program, we should
never hesitate to reopen the analysis or design if new insight is
discovered.
Some of the material here is based on the presentation in the
following books:
The first version of these lecture notes were written for use in
the CSci 490 section on object-oriented design and programming using
C++ in the 1996 spring semester. The notes were subsequently expanded
and put on the Web for the first Java-based version of CSci 211 (File
Systems) during the 1996 fall semester. They were further modified
for use in the CSci 581 (Object-Oriented Design and Programming)
classes in the 1997 fall and 1999 spring semesters, in the CSci 405
(Computer Simulation) class in the 2000 spring semester, and the Engr
691 (Software Architecture) class in the 2000 fall and 2002 spring
semesters. The notes were updated for use in the Multiparadigm
Programming in Scala class in Fall 2008 and Software Families class in
Fall 2011.
UP to CSci 691-6 Lecture Notes root document?
The state is encapsulated within the object--is not directly visible.
Classes
StudentDesk
objects from which
specific instances can be created as needed.
Inheritance
StudentDesk
class might be derived
(i.e., inherit) from a class Desk
, which in turn might be
derived from a class Furniture
. In diagrams, it is the
convention to draw arrows from the subclass to the superclass.
Furniture <-- Desk <-- StudentDesk
ComputerDesk
class
that also derives from Desk
.
Furniture <-- Desk <-- ComputerDesk
extends
keyword as follows.
class Furniture // extends cosmic root class for references by default
{ ... // (i.e., java.lang.Object or scala.AnyRef)
}
class Desk extends Furniture
{ ...
}
class StudentDesk extends Desk
{ ...
}
class ComputerDesk extends Desk
{ ...
}
StudentDesk
and ComputerDesk
objects will need operations to simulate a move
of the
entity in physical space. The move
operation can thus be
implemented in the Desk
class and shared by objects of
both classes. Invocation of operations to move
either a
StudentDesk
or a ComputerDesk
will be bound
to the general move
in the Desk
class.
StudentDesk
class might inherit from a
Chair
class as well as the Desk
class.
Furniture <-- Chair <-- StudentDesk
StudentDesk
(e.g., C++, Eiffel). Other languages only support a
single inheritance hierarchy.
Object
from which all other classes derive (directly or
indirectly). Scala is similar, with the corresponding top-level class
named AnyRef
.
class StudentDesk extends Desk, Chair // NOT VALID in Java or Scala
{ ...
}
StudentDesk
inherits
from class Furniture
through two different paths. Do the
data fields of the class Furniture
occur once or twice?
What happens if the intermediate classes Desk
and
Chair
have conflicting definitions for a data field or
operation with the same name?
interface
construct can be used to define an
interface for classes separately from the classes themselves. A Java
interface
may inherit from (i.e., extend
)
zero or more other interface
definitions.
interface Location3D
{ ...
}
interface HumanHolder
{ ...
}
interface Seat extends Location3D, HumanHolder
{ ...
}
class
may inherit from (i.e.,
implement
) zero or more interfaces as well as inherit
from (i.e., extend
) exactly one other class
.
interface BookHolder
{ ...
}
interface BookBasket extends Location3D, BookHolder
{ ...
}
class StudentDesk extends Desk implements Seat, BookBasket
{ ...
}
StudentDesk
class to provide
actual implementations for all the operations from the
Location3D
, HumanHolder
,
BookHolder
, Seat
, and
BookBasket
interfaces. The Location3D
operations will, of course, need to be implemented in such a way
that they make sense as part of both the HumanHolder
and
BookHolder
abstractions.
trait
provides a more powerful, and more
complex, mechanism than Java's interface
. In addition to
signatures, a trait
can define method implementations and
data fields. These traits can be added to a class in a controlled,
linearized manner to avoid the semantic and implementation problems
associated with multiple inheritance of classes. This is called
mixin inheritance.
Polymorphism
void move () { ... }
void move (Location l) { ... }
void move (Desk d) { ... }
template
facility in C++ and the generic
facility in Ada are other
examples of parametric polymorphism.
StudentDesk
and ComputerDesk
classes are
derived from the Desk
class and that a general
move
operation is implemented as a part of the
Desk
class. This could be expressed in Java as follows:
class Desk extends Furniture
{ ...
public void move(...)
...
}
class StudentDesk extends Desk
{ ...
// no move(...) operation here
...
}
class ComputerDesk extends Desk
{ ...
// no move(...) operation here
...
}
move
either a StudentDesk
or a ComputerDesk
instance will be bound to the general move
in the
Desk
class.
move
operation for ComputerDesk
objects.
For instance, we need to make sure that the computer is shut down and
the power is disconnected before the entity is moved.
move
operation and associate it with the
ComputerDesk
class. Now a call to move
a
ComputerDesk
object will be bound to the special
move
operation, but a call to move
a
StudentDesk
object will still be bound to the general
move
operation in the Desk
class.
move
in the ComputerDesk
class is said to override the definition in the
Desk
class.
class Desk extends Furniture
{ ...
public void move(...)
...
}
class StudentDesk extends Desk
{ ...
// no move(...) operation here
...
}
class ComputerDesk extends Desk
{ ...
public void move(...)
...
}
Requirements Analysis
Object-Oriented Design
Object-Oriented Design: Finding Classes and Responsibilities
PhoneBook
class.
Person
class.
print
operations probably could be
combined into a single print
operation with the differing
functionalities specified by the parameter.
Person
rather than have it a separate class.
Truck
class--unless, of
course, only one color of truck is needed in the application.
FireTruck
of class Truck
if other kinds of
trucks are needed in the application. A "fire truck" has several
behaviors and attributes that differ from the generic concept of a
"truck".
Truck
.
Person
entries would need to be flexible.
Enumeration
interface is returned by the
elements()
of a Vector
object; successive
calls of the nextElement()
method of the
Enumeration
object returns successive elements of the
Vector
object.
lookup
, insert
, delete
, and
modify
entry operations with the PhoneBook
class, associate compare
, setPhoneNumber
and
getPhoneNumber
with the Person
class, etc.
Person
(that is, insert this
person into the argument telephone book) or a operation of
PhoneBook
(that is, insert the argument person in
this telephone book).
Person
would require the Person
class to have access to the
internal representation details of the PhoneBook
.
However, associating the operation with PhoneBook
would
not require the PhoneBook
to know about the internal
details of the Person
class--except to be able to
compare
two entries. Thus making insert
an
operation of PhoneBook
and compare
an
operation of Person
would best maintain the encapsulation
of data.
Coming Up with Names
Object-Oriented Design: Finding Relationships Among Classes
Use Relationship
PhoneBook
uses
Person
objects (that is, it inserts, deletes, modifies,
etc., the entries). Person
objects do not use
PhoneBook
objects.
Aggregation Relationship
Person
class in the telephone
book example contain (has) name
,
address
, and phoneNumber
objects.
record
and C structure
are
aggregations; they contain other data fields.
They are not, however, objects.
Inheritance Relationship
Professor
to
inherit from Employee
; similarly, we design
Staff
to inherit from Employee
.
We make Employee
itself inherit from Person
;
similarly, we design Student
to inherit from
Person
.
Object-Oriented Implementation
Acknowledgments
Copyright © 2011, H. Conrad Cunningham
Last modified: Thu 8 Sep 2011