Functional versus object-oriented development
The functional approach is a relatively well-established
method of software development.
In essence, a functional approach amounts
to decomposing a task into the steps that must
be taken to complete it.
For instance, when describing a compiler in a functional
manner, a first decomposition can be made as follows:
lexical scan parser code generation
Each of these steps can be regarded as transforming
its input to a representation suitable for
the next step, finally resulting in an executable
program.
See slide [3-functional].
Obviously, the method of functional decomposition
lends itself to a structured approach,
since each step (in the example above) may be decomposed
into a number of smaller steps.
Graphically, a functional decomposition
may be pictured as a tree with the elementary
steps as its leaves.
A functional decomposition may be augmented
by a data flow diagram that depicts the nature
of the data transformations effected by each step.
Basically, a data flow diagram is a graph with nodes
labeled by the (intermediate) data representations and edges
that are labeled by the functional components
identified in the function decomposition tree.
Functional development methods
- each module represents a major step in the process
Disadvantages
- no data abstraction / information hiding
- not responsive to changes
- inadequate for natural concurrency
slide: Functional development methods
In contrast, the components that result from an object-oriented approach
do not represent actual steps
toward a solution, but rather may be seen as
(abstract) entities that contribute to the solution
on the basis of their assigned responsibilities.
Correspondingly, the flow of control is far less
regular than in a functional decomposition.
The equivalent of the combined data flow and functional
decomposition diagram for an object-oriented system
is a message-passing diagram depicting the actual
interaction between objects.
This diagram is a graph with objects as nodes,
and edges labeled by requests to execute a method.
In general, this graph will be too complex to
be represented pictorially.
It is possible, however, to divide up an object
interaction graph into meaningful related pieces,
which is the approach taken for Fusion, for example.
See section [Fusion].
Moreover, the complexity of object-interaction
is compensated for by the opportunities for a more
tight definition of the semantics, that is the
responsibilities and obligations, of each (object)
component participating in the computation.
The major drawback of a functional approach,
as observed in [Booch86], is the absence of data abstraction
and information hiding.
Typically, the functional approach is not concerned
with issues of data representation,
although it does allow additional procedural abstractions
to access and modify the data.
However, the functional approach does not in itself
provide any support for a tight coupling between
the functional components and data representations
or the procedural abstractions that are used for the
purpose of information hiding.
Hence, changes in the data representation or
the structure of the algorithm may ripple
across the system in an unforeseen manner.
Object-oriented development -- design for change
Advantages
- improved maintainability
- improved understandability
Support for concurrency {\em -- active objects}
- no modifications needed for concurrency
slide: Support for change
In contrast, object-oriented development is neither
data nor procedure oriented,
but strives to encapsulate both data and procedures
acting on these data in objects.
Instead of a detailed description of the steps
that need to be taken to solve a problem or complete a task,
the required functionality is distributed among the objects
that may later be assembled (in a rather straightforward way)
to actually perform the task.
Perhaps the most important advantage of
encapsulating data and procedures, from the perspective
of design, is that (in many cases)
changes may be kept strictly local
to the classes defining the relevant objects.
See slide [3-OO].
Ideally, object-oriented design is design for change,
in other words the development of an architecture
that is adaptable to changing requirements or changes
in the underlying software and hardware support.
However, to achieve this adaptability generally
requires a non-trivial effort during design,
in order to find stable abstractions
with a well-defined semantics that determines their
role in the system.
Obvious potential spin-offs from the effort to design for change
are improved maintainability
of the system and a better understanding of
its architectural structure.
Another benefit of an object-oriented approach,
mentioned in [Booch86],
is the possibility of introducing concurrency
in a later stage.
Starting from a functional decomposition,
the introduction of concurrency would generally incur a total
restructuring of the algorithm.
With an object-oriented approach, however,
concurrency may be introduced by employing
active objects.
In principle, clients of an object need not be aware
of whether the object is passive, that is merely
responding to messages, or active, which
means that the object in addition to answering messages
has (autonomous) behavior of its own.
However, employing active objects imposes a number of
additional constraints, on an implementation
level but also on a design level.
We will explore these issues in chapter \ref{Distribution and concurrency}.