.__________________. | | | 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.
We will 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 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 we have discussed previously.
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".
A language is object-based if it supports objects as a language feature.
Object-based languages include Ada, Modula, Clu, C++, Java, 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 "student desks" from which specific instances (objects) can be created as needed.
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, Smalltalk, and Ada 95. Ada 83 and Modula are not class-based.
A class D inherits from class B if D's objects form a subset of B'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 "student desk" class might be derived (i.e., inherit) from a class "desk", which in term might be derived from a class "furniture".
furniture <-- desk <-- student_desk
The simulation might also include a "computer desk" class that also derives from "desk".
furniture <-- desk <-- computer_desk
In Java, we can express the above inheritance relationships using the
extends
keyword as follows:
class Furniture // extends Object by default { ... } class Desk extends Furniture { ... } class StudentDesk extends Desk { ... } class ComputerDesk extends Desk { ... }
Both "student desks" and "computer desks" 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 "student desk" or a "computer desk" will be bound to the general "move" in the "desk" class.
The "student desk" class might inherit from a "chair" class as well as the "desk" class.
furniture <-- chair <-- student_desk
Some languages support multiple inheritance as shown above for "student desk" (e.g., C++, Eiffel). Other languages only support a single inheritance hierarchy.
Because multiple inheritance is both difficult to use correctly and to
implement in a compiler, the designers of Java did not include a
multiple inheritance of classes as feature. Java has a single
inheritance hierarchy with a top-level class named Object
from which all other classes derive (directly or indirectly).
class StudentDesk extends Desk, Chair // NOT VALID in Java { ... }
To see some of the problems in implementing multiple inheritance,
consider the above example. Class 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?
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 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
s.
interface Location3D { ... } interface HumanHolder { ... } interface Seat extends Location3D, HumanHolder { ... }
A Java 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 { ... }
This definition requires the 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.
The concept of polymorphism (literally "many forms") means the ability to hide different implementations behind a common interface.
Polymorphism appears in several forms.
void move () { ... } void move (Location l) { ... } void move (Desk d) { ... }
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.
Java does not currently support parameterized class definitions. The
template
facility in C++ and the generic
facility in Ada are examples of parametric polymorphism. There are
experimental extensions to Java that do support parametric
polymorphism.
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.
As an example, again consider the simulation of a classroom. As in our discussion of inheritance, suppose that the "student desk" and "computer desk" 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 ... }
As we noted before, invocation of operations to "move" either a "student desk" or a "computer desk" will be bound to the general "move" in the "desk" class.
Extending the example, suppose that we need a special version of the
"move" operation for "computer desks". For instance, we need to make
sure that the computer is shut down and the power is disconnected
before the entity is moved. We can define this special version of the
"move" operation and associate it with the "computer desk" class. Now
a call to "move" a "computer desk" will be bound to the special "move"
operation, but a call to "move" a "student desk" will still be bound
to the general "move" operation in the "desk" class. The definition
of move
in ComputerDesk
overrides
the definition in Desk
.
In Java, this can be expressed as follows:
class Desk extends Furniture { ... public void move(...) ... } class StudentDesk extends Desk { ... // no move(...) operation here ... } class ComputerDesk extends Desk { ... public void move(...) ... }
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, Smalltalk, and Ada 95. The language Clu is class-based, but does not include an inheritance facility.
Other object-oriented languages include Objective C, Object Pascal, Eiffel, and Oberon 2.
The goals of the design phase are to:
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.
Operations should be defined precisely. Ambiguity and imprecision will likely cause problems later in the design and implementation.
Relationships among classes should not be excessively complex.
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.
As an example, consider a computerized telephone book for a university. The telephone book should contain entries for each person in the university community--student, professor, and staff member. Users of the directory can look up entries. In addition, the administrator of the telephone book can, after supplying a password, insert new entries, delete existing entries, modify existing entries, print the telephone book, and print a listing of all students or of all faculty.
computerized telephone book, university, telephone book, entry, person, university community, student, professor, staff member, employee, user, administrator, password
Several, but not necessarily all, of these will become classes in our design.
Other classes may be be implicit in the specification or may emerge as the design of individual classes proceed. For example, the design will likely need name, address, and telephone number fields of the entries for each person, need components of the user interface (such as a menu), and need information structure for storing the collection of personal entries that make up the telephone book.
lookup entry, supply password, insert new entry, delete existing entry, modify existing entry, print telephone book, print all students, print all employees, set telephone number field, get telephone number field, compare entries.
For example, computerized telephone book, telephone
book, and directory can be combined into a single
PhoneBook
class. Similarly, entry and
person can be combined into a single Person
class. In addition, all the print operations probably
probably be combined into a single print
operation with
the differing functionalities specified by parameters.
For example, associate lookup
, insert
,
delete
, and modify
entry operations with the
PhoneBook
class, associate compare
,
setPhoneNumber
and getPhoneNumber
with the
Person
class, etc.
Sometimes there might be a choice on where to associate a operation.
For example, the "insert person into telephone book" operation could
be a operation of 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).
Which is better? Associating the operation with 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.
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.
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, PhoneBook
uses
Person
objects (that is, it inserts, deletes, modifies,
etc., the entries). Person
objects do not use
PhoneBook
objects.
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 Person
class in the telephone
book example contain (has) name
,
address
, and phoneNumber
objects.
The Pascal record
and C structure
are
aggregations; they contain other data fields.
They are not, however, objects.
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 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
.
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:
The purpose of the implementation phase is to code, test, and integrate the classes into the desired application system.
Object-oriented development:
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.
UP to CSCI 581 Lecture Notes root document?