This material is copyrighted.
You may not copy or print any of this material
without explicit permission of the author
or the publisher.
formats
Slides are segments or slices of the text that may be displayed independently, or in sequence, with enlarged font.
Slides are available both in dynamic HTML/javascript presentation
format, as well as in a home-grown VRML format.
PDF (reading and printing)
A PDF reader must be installed on your system. Such a reader can be freely obtained at www.adobe.com.
Alternatively,
click on the sign
at the top right for a PDF version of that particular chapter or section.
online version
Click on [],
in the topleft of the navigation area, for a contents listing.
presentation
Click on the
at the index page,or any of the other pages,
for a dynamic HTML version of the slides.
See DejaVU Online (Readers Guide) for an explanation of the navigation options. The principle is simple, click on the right yellow sidebar, until the presentation is finished.
For a VRML version,
click on the sign.
These links require the blaxxun
Contact 3D plugin.
Hit the spacebar to advance in the sequence.
The backspace takes you one back.
trails
This document may contain trails.
Qualified trails are indicated with, for example, C: < % ^ >. In this case it concerns a concepts trail.
The signs indicate respectively:
In resources links to other sites
as well as online copies of external material are given.
CDROM
The CDROM contains the full online version of the course notes.
Open the file
index.html in Netscape Navigator or
Microsoft Internet Explorer, and click on readme
for an explanation or introduction multimedia
to access the material.
e-scenari(OO)
the (e)business web-ladder (KvK aug 2001)
themes
perspective
(in the end) you must write a paper
To gain an understanding of some new area, it is virtually unavoidable to be immersed in the material for a while without exactly understanding where it will lead.
Additional keywords and phrases:
object, data abstraction,
analysis, design, implementation, distribution
In design, objects are our primary unit of decomposition. In our design, objects may reflect real life entities, such as Employer and Employee, but may also represent system artefacts, such as stacks or graphics.
In actual development, that is in the implementation, objects are our unit of implementation. Each object itself may be regarded as a collection of functions. But it is the collection of functions, and the behavior that they describe, that we take as our unit; not the individual function.
A consequence of adopting an object-oriented approach is that we have to spend more time in describing and understanding the structure and behavior of the system, and to learn the formalisms and tools that enable us to do so.
To master this complexity, we need to think about how objects can be made to fit together. To benefit from an object-oriented approach, we need to design a software architecture that defines and regulates the interactions between objects.
A framework is a kind of library of reusable objects. However, in contrast with ordinary software libraries, frameworks may at times take over control. The best-known examples of frameworks are in the GUI domain; frameworks in other domains (e.g. the business process domain) are emerging.
Using a framework may simplify your life, since a framework provides generic solutions for a particular application domain. But the price you pay is twofold. You have to understand what (patterns of) solutions the framework provides, and you have to comply with the rules of the game imposed by the framework.
Objects provide the means by which to structure a system. In Smalltalk (and most other object-oriented languages) objects are considered to be grouped in classes. A class specifies the behavior of the objects that are its instances. Also, classes act as templates from which actual objects may be created. Inheritance is defined for classes only. From the perspective of design, inheritance is primarily meant to promote the reuse of specifications.
Finally, an important feature of object-oriented
languages is their support for polymorphism.
Polymorphism is often incorrectly identified with inheritance.
Polymorphism by inheritance makes it possible
to hide different implementations behind a common interface.
However, other forms of polymorphism may arise by overloading functions
and the use of generic (template) classes or functions.
See sections polymorphism and
flavors.
Features and benefits of OOP
Encapsulation promotes modularity, meaning that objects must be regarded as the building blocks of a complex system. Once a proper modularization has been achieved, the implementor of the object may postpone any final decisions concerning the implementation at will. This feature allows for quick prototyping, with the risk that the `quick and dirty' implementations will never be cleaned up. However, experience with constructing object-oriented libraries and frameworks has shown that the modularization achieved with objects may not be very stable.
Another advantage of an object oriented approach, often considered to be the main advantage, is the reuse of code. Inheritance is an invaluable mechanism in this respect, since the code that is reused seldom offers all that is needed. The inheritance mechanism enables the programmer to modify the behavior of a class of objects without requiring access to the source code.
OO = encapsulation + inheritance
benefits of OOP
Despite the fact that our basic mathematical model of a computing device (and hence our notion of computability) has not altered significantly, the development of high level programming languages has meant a drastic change in our conception of programming. Within the tradition of imperative programming, the introduction of objects, and object-oriented programming, may be thought of as the most radical change of all. Indeed, at the time of the introduction of Smalltalk, one spoke of a true revolution in the practice of programming.
the object model
message
encapsulation
protocol
In the (ordinary) sequential machine model, the result of a computation is (represented by) the state of the machine at the end of the computation. In contrast, computation in the object model is best characterized as cooperation between objects. The end result then consists, so to speak, of the collective state of the objects that participated in the computation. See slide 2-model. Operationally, an object may be regarded as an abstract machine capable of answering messages. The collection of messages that may be handled by an object is often referred to as the protocol obeyed by the object. This notion was introduced in the Smalltalk programming environment originally to provide the means to group the messages to which an object may respond. For instance, the distinction between methods for initialization and methods for modification or processing may be convenient in developing or using a program. The notion of protocol may also be given a more formal interpretation, as has been done for instance in the notion of contracts (introduced in Eiffel) stating the requirements that must be adhered to in communicating with an object.
Structurally, an object may be regarded as a collection of data and procedures. In principle, the data are invisible from the outside and may be manipulated only by invoking the right procedure. In a pure object-oriented language such as Smalltalk and Eiffel, sending a message to an object is the only way of invoking such a procedure. Combined, data-hiding and message interface abstraction will be referred to as encapsulation. Actually, object-oriented languages, while in some way supporting objects as collections of data and procedures, may differ subtly in the degree and way in which they support data-hiding and abstraction.
For now, we need not be concerned with the precise
mathematical details of our model of a computing device.
For a very much more precise and elaborate description
of the Turing machine, the interested reader is referred
to
An interesting, but perhaps somewhat distressing, feature
of the Turing machine model is that it is the strongest model
we have, which means that any other model
of computation is at best equivalent to it.
Parallel computation models in effect do extend the power
of (sequential) Turing machines, but only in a linear
relation with the number of processors.
In other words, the Turing machine defines what we may regard
as computable and establishes a measure
of the complexity of a computation, in space and time.
The awareness of the intrinsic limitations imposed
by a precise mathematical notion of computability
has, for example, led us to regarding the claims of artificial intelligence
with some caution, see
An equally important feature of the Turing machine model is that it gives us an illustration of what it means to program a computing device, that is to instruct the machine to perform actions dependent on its input and state. As an extension to the model, we can easily build a universal computing device, into which we may feed the description of some particular machine, in order to mimic the computation of that machine. Apparently, this gives us a more powerful machine. However, this has proven not to be the case. Neither does this universal device enlarge the class of computable problems, nor does it affect in any significant sense the computational complexity of what we know to be computable. See slide 2-devices.
Object-oriented programming does not enlarge the class of computable problems, nor does it reduce the computational complexity of the problems we can handle.
Our model of a computing device does quite precisely delimit the domain of computable problems, and gives us an indication of what we can expect the machine to do for us, and what not. Also, it illustrates what means we have available to program such a device, in order to let it act in the way we want. Historically, the Turing machine model may be regarded as a mathematical description of what is called the Von Neumann machine architecture, on which most of our present-day computers are based. The Von Neumann machine consists of a memory and a processor that fetches data from the memory, does some computation and stores the data back in memory. This architecture has been heavily criticized, but no other model has yet taken its place. This criticism has been motivated strongly by its influence on the practice of programming. Traditionally, programs for the Von Neumann architecture are conceived as sequences of instructions that may modify the state of the machine. In opposition to this limited, machine-oriented view of programming a number of proposals have been made that are intended to arrive at a more abstract notion of programming, where the machine is truly at the service of the programmer and not the other way around.
One of these proposals to arrive at a more abstract notion of programming is advocated as the {\em object-oriented approach}. Before studying the intrinsics of the object-oriented approach, however, it may be useful to reflect on what we may expect from it. Do we hope to be able to solve more problems, or to solve known problems better? In other words, what precisely is the contribution of an object-oriented approach?
Based on the characterization of a computing device,
some answers are quite straightforward.
We cannot expect to be able to solve more problems,
nor can we expect to reduce the computational complexity
of the problems that we can solve.
What an object-oriented approach can contribute,
however, is simply in providing better means
with which to program the machine.
Better means, to reduce the chance of (human)
errors, better means, also, to manage the complexity
of the task of programming (but not to reduce
the computational complexity of the problem itself).
In other words, by providing abstractions that
are less machine oriented and more human oriented,
we may enlarge the class of problems that we can
tackle in the reality of software engineering.
However, we simply cannot expect that an object-oriented
approach may in any sense enlarge our notion
of what is computable.
Some history
The first abstraction mechanism beyond the level of assembler language and macros is provided by procedures. Procedures play an important role in the method of stepwise refinement introduced by the school of structured programming. Stepwise refinement allows the specification of a complex algorithm gradually in more and more detail. Program verification amounts to establishing whether the implementation of an algorithm in a programming language meets its specification given in mathematical or logical terms. Associated with the school of structured programming is a method of verification based on what has become known as Hoare logic, which proceeds by introducing assertions and establishing that procedures meet particular pre- and post-conditions.
Other developments in programming language research are aimed at providing ways in which to capture the mathematical or logical meaning of a program more directly. These developments have resulted in a number of functional programming languages (e.g. ML, Miranda) and logic programming languages, of which Prolog is the best-known. The programming language Lisp may in this respect also be regarded as a functional language.
The history of object-oriented programming may be traced back to a concern for data abstraction, which was needed to deal with algorithms that involved complex data structures. The notion of objects, originally introduced in Simula (Dahl and Nygaard, 1966), has significantly influenced the design of many subsequent languages (e.g. CLU, Modula and Ada). The first well-known {\em object-oriented language} was Smalltalk, originally developed to program the Dynabook, a kind of machine that is now familiar to us as a laptop or notebook computer. In Smalltalk, the data-hiding aspect of objects has been combined with the mechanism of inheritance, allowing the reuse of code defining the behavior of objects. The primary motivation behind Smalltalk's notion of objects, as a mechanism to manage the complexity of graphic user interfaces, has now proven its worth, since it has been followed by most of the manufacturers of graphic user interfaces and window systems.
Summarizing, from a historical perspective, the introduction of
the object-oriented approach may be regarded as a natural extension to
previous developments in programming practice, motivated
by the need to cope with the complexity of new applications.
History doesn't stop here. Later developments,
represented by Eiffel, C++ (to a certain extent) and Java,
more clearly reflect the concern with abstraction and verification,
which intrinsically belongs to the notion of abstract data types
as supported by these languages.
Design by Contract
After this first glance at the terminology and mechanisms
employed in object-oriented computation,
we will look at what I consider to be the contribution of an
object-oriented approach
(and the theme of this book) in a more thematic way.
The term `contract' in the title of this section
is meant to refer to an approach to design that has become
known as design by contract, originally introduced in
Abstract data types, that is elements thereof, are generally realized by employing a hidden state. The state itself is invisible, but may be accessed and modified by means of the observations and operations specified by the type. See slide 1-ADT.
Objects may be regarded as embodying an (element of an) abstract data type. To use an object, the client only needs to know what an object does, not (generally speaking) how the behavior of the object is implemented. However, for a client to profit from the data hiding facilities offered by objects, the developer of the object must provide an interface that captures the behavior of the object in a sufficiently abstract way. The (implicit) design guideline in this respect must be to regard an object as a server that provides high level services on request and to determine what services the application requires of that particular (class of) object(s). See slide 1-respons.
object = information + responsibilities
Contracts
if B refines A then B may be used wherever A is allowed
OOP = Contracts + Refinements
Data abstraction
When programs became larger and data more complex, the design of correct algorithms
was no longer the primary concern.
Rather, it became important to provide access to data in a representation
independent manner.
One of the early proponents of data hiding was, see
An elegant solution is provided by Java which offers multiple interface inheritance, by allowing multiple interfaces to be realized by an actual class.
With an increase in the number of software products
not satisfying user needs, prototyping
has become quite popular!Software development models
The problem domain -- complex reality
Communication -- with domain experts
Continual change -- user requirements
Reuse -- of analysis results
Concluding this brief exploration of the analysis phase, I think we may safely set as the goal for every method of analysis to aim at stable abstractions, that is a conceptual model which is robust with respect to evolving user requirements. Also, we may state a preference for methods which result in models that have a close correspondence to the concepts and notions used by the experts operating in the application domain.
With respect to notation UML (the Unified Modeling Language,
see Appendix UML) is the obvious choice.
How to apply UML in the various phases of object-oriented software construction
is an altogether different matter.
Design
In an object-oriented approach,
the distinction between analysis
and design
is primarily one of emphasis;
emphasis on modeling the reality of
the problem domain versus
emphasis on providing an architectural
model of a system that lends itself
to implementation.
One of the attractive features of such
an approach is the opportunity of a seamless
transition between the respective phases
of the software product in development.
The classical waterfall model can no
longer be considered as appropriate
for such an approach.
An alternative model, the
fountain model, is proposed by
To my mind, there is yet another reason for the extra
effort involved in design.
In practice it appears to be difficult and time
consuming to arrive at the appropriate
abstract data types for a given application.
The implementation of these structures,
on the other hand, is usually straightforward.
This is another indication that the unit of
reuse should perhaps not be small pieces of code, but
rather (the design of) components that fit into
a larger framework.
From the perspective of software quality and maintenance,
these mechanisms of encapsulation
and inheritance may be characterized as powerful means
to control the complexity of the code needed to realize a system.
In Design assignments
An Interior Design Assistant (IDA)
is a tool to support an interior design architect.
When designing the interior of a house or building,
the architect proceeds from the spatial layout
and a list of furniture items.
IDA must allow for
placing furniture in a room.
It will check for constraints. For example
placing a chair upon a table will be prohibited.
For each design, IDA must be able to
give information with respect to pricing
and the time it takes to have the furniture
items delivered.
In addition to the design facilities, IDA must also
offer a showroom mode, in which
the various designs can be inspected and compared
with respect to price and delivery time.
An Agenda Support System assists the user in maintaining a record of important events, dates and appointments. It moreover offers the user various ways of inspecting his or her agenda, by giving an overview of important dates, an indication of important dates on a calendar, and (more advanced) timely notification.
A Multi-user Agenda Support System extends a simple Agenda Support System by providing facilities for scheduling a meeting, taking into account various constraints imposed by the agendas of the participants, as for example a special event for which a participant already has an entry in his or her agenda. A minimal Multi-user Agenda Support System must provide facilities for registering important dates for an arbitrary number of users. It must, moreover, be able to give an overview of important dates for any individual user, and it must be possible to schedule a meeting between an arbitrary subset of users that satisfies the time-constraints for each individual in that particular group.
This minimal specification may be extended with input facilities, gadgets for presenting overviews and the possibility of
adding additional constraints. Nevertheless, as a piece of advice, when developing a Multi-user Agenda Support System, follow
the KISS principle: Keep It Simple ...
Implementation
In principle, the phase of implementation
follows on from the design phase.
In practice, however,
the products of design may often
only be regarded as
providing a post hoc justification
of the actual system.
As noted, for instance, in
Design is of particular importance in projects
that require long-term maintenance.
Correcting errors or adapting the functionality of
the system on the basis of code alone
is not likely to succeed.
What may help, though, are tools that extract
explanatory information from the code.
Testing and maintenance
The language Eiffel, described by
The C++ language (Stroustrup, 1991) has a somewhat different history. It was originally developed as an extension of C with classes. A primary design goal of C++ has been to develop a powerful but efficient language. In contrast to Smalltalk and Eiffel, C++ is not a pure object-oriented language; it is a hybrid language in the sense that it allows us to use functions in C-style as well as object-oriented constructs involving classes and inheritance.
The newest, and perhaps most important, object-oriented language around is Java, which owes its popularity partly to its tight connection with the Internet. Java comes with a virtual machine that allows for running Java programs (applets) in a browser, in a so-called sandbox, which protects the user from possibly malicious programs.
As the final language in this brief overview, I wish to mention the distributed logic programming language DLP (see Eliëns, 1992). The DLP language combines logic programming with object-oriented features and parallelism. I mention it, partly because the development of this language was my first involvement with OOP. And further, because it demonstrates that other paradigms of programming, in particular logic programming, may be fruitfully combined with OOP. The language DLP provides a high level vehicle for modeling knowledge-based systems in an object-oriented way.
A more extensive introduction to the Smalltalk, Eiffel, C++, Java and DLP languages is given in the appendix.
Equally focused on understanding structure and behavior,
but more from a modeling perspective, is the
Unified Modeling Language (UML), which has resulted
from a common effort of
leading experts in object-oriented analysis and design,
Grady Booch, Ivar Jacobson and James Rumbaugh, also known as
`The Three Amigos'.
UML, indeed the second trend,
aims at providing the full notational repertoire needed
for modeling every conceivable structural
and behavioral aspect of software systems.
An excellent introduction to UML is given in
With respect to technology, the field is still very much in flux.
A dominant factor here is the rapid increase in Internet usage and,
more in particular, the Web.
The Web has boosted the interest of the IT business world
in the deployment of distributed object or component
technology to extend their range of business.
Nevertheless, the very existence of this infrastructure
is in itself somewhat embarrassing, in that the Web
and the technology around which it is built is not
object-oriented. Perhaps it should be, but it simply isn't.
Our embarrassment is aggravated when we observe,
following
Challenges
More concretely, a major challenge for the next decade will be to develop and deploy frameworks that operate in areas such as finance, medical care, social welfare and insurance. This is explicitly not only a technical problem, but also a problem of coming to agreement with respect to the abstractions and corresponding standards that provide the computational infrastructure for these domains. Also on my wish-list is the separation of logic and control, by which I mean the decoupling of the more or less invariant functionality as may be provided by for example business objects and business processes and the more variable logic that controls these processes. In other words, it is necessary that the business logic is made explicit and that it is factored out of the code effectuating it.
Both our hardware and software technology are improving rapidly. Yet, we are still stuck with the WIMP interfaces. In my opinion, it is time for a change. What I would like to see is an exploration of 3D user interfaces and 3D visualisations of the structure and processes underlying information-intensive applications. Although not specifically related to object-oriented software development, this is an area where object orientation can prove its worth.
When we think about real applications, for example information or business services on the Internet, they are usually the kind of applications that we may characterize as knowledge-intensive applications. In a somewhat idealistic vision, we may think of application development that consists of composing components from perhaps even a number of frameworks, so that we don't have to bother with the tiresome details of network access and GUI development. Then what remains to be done is to glue it all together, and provide the information and knowledge that enables our application to deliver its services. Partly we can rely on database technology for the storage and retrieval of information. But in addition we will need other declarative formalisms for expressing, for example, our business logic or, as another example, for expressing the synchronisation constraints of our multimedia presentation.
Considering Web applications, even as they are today, we see applications that consist of a mixture of code, tools and information. The phrase fragmented applications seems apt here. For example a store selling books on the Internet needs everything ranging from Javascript enabled webpages, to a secure CORBA-based accounting server. It is very likely that such applications will be developed partly by composing already existing components.
In his book,
Nowadays there are many books that may serve
as a starting point for reading about OO.
Dependent on your interest, you may look
at
Object orientation has brought about a radical shift in our notion of software development. The basic mechanisms of object-oriented programming, encapsulation and inheritance, have clear advantages when it comes to data-hiding and incremental development.
Additional keywords and phrases:
generic types, assertions, canonical classes,
event-driven computation
First we will look at some examples in Java,
illustrating the use of inheritance and delegation
for the realization of some simple idioms and patterns.
Then, we will briefly deal with polymorphism in C++,
including the use of assertions that may be used to enforce
contractual obligations.
After discussing some of the idioms and patterns
that have been employed in the hush framework,
we will look more closely at the catalogue of design patterns
introduced in
In this section, we will look at some simple examples in Java that illustrate how we may use the mechanisms of inheritance and (simple) delegation to define objects that have similar functionality but differ in the way that functionality is realized. These examples prepare the way for the more complex idioms and patterns presented later in this chapter.
In the rest of this section we will look briefly at
the polymorphic constructs offered by C++.
We will also study how behavioral conformance can
be enforced in C++ by including invariants and assertions.
These sections may be skipped on first reading.
Inheritance and delegation in Java
Consider the example below,
an envelope class
that offers a message method.
In this form it is nothing but a variation on the hello world
example presented in the appendix.
public envelope() { }
public void message() {
System.out.println("hello ... ");
}
};
We will proceed in three steps: (1) The envelope class will be redesigned so that it acts only as an interface to the letter implementation class. (2) Then we introduce a factory object, that is used to create envelope and letter instances. (3) Finally, we refine the letter class into a singleton class, that prevents the creation of multiple letter instances.
public letter() { }
public void message() {
System.out.println("Message in a letter");
}
};
Factory
STL is supported by C++ compilers that adhere to the C++
standard, including Microsoft Visual C++ and the Cygnus/GNU C++ compilers.
A more extensive discussion of STL is beyond the scope of this
book, but the reader is advised to consult Assertions in C++
Whatever support a language may offer,
reliable software is to a large extent the
result of a disciplined approach to
programming.
The use of assertions has long since been
recognized as a powerful way in which to check whether
the functional behavior of a program corresponds
with its intended behavior.
In effect, many programming language environments
support the use of assertions in some way.
For example, both C and C++ define a macro assert
which checks for the result of a boolean expression
and stops the execution if the expression is false.
In the example below, assertions are used to check for the satisfying of both the pre- and post-conditions of a function that computes the square root of its argument, employing a method known as Newton iteration.
require ( arg >= 0 );
double r=arg, x=1, eps=0.0001;
while( fabs(r - x) > eps ) {
r=x; x=r-((r*r-arg)/(2*r));
}
promise ( r - arg * arg <= eps );
return r;
}
Whereas Eiffel directly supports the use of assertions by allowing access to the value of an instance variable before the execution of a method through the keyword old, the C++ programmer must rely on explicit programming to be able to compare the state before an operation with the state after the operation.
Assertions may also be used to check whether the
object is correctly initialized.
The pre-condition stated in the constructor
requires that the counter must start with
a value not less than zero.
In addition, the constructor checks whether
the class invariant, stated in the (virtual)
member function invariant, is satisfied.
Similarly, after checking whether the post-condition
of the
From a formal perspective, the use of assertions may
be regarded as a way of augmenting the type system supported
by object-oriented languages.
More importantly, from a software engineering perspective,
the use of assertions is a means to enforce contractual obligations.
Canonical class idioms
The multitude of constructs available in C++ to support
object-oriented programming may lead the reader to think
that object-oriented programming is not at all meant
to reduce the complexity of programming but rather to increase it,
for the joy of programming so to speak.
This impression is partly justified, since the number and complexity
of constructs is at first sight indeed slightly bewildering.
However, it is necessary to realize that each of the constructs introduced
(classes, constructors and destructors, protection mechanisms,
type conversion, overloading, virtual functions and dynamic binding)
may in some way be essential to support object-oriented programming
in a type-safe,
and yet convenient, way.
Having studied the mechanisms, the next step is to find proper ways,
recipes as it were, to use these mechanisms.
What we need, in the terminology of
In this section, we will look at the concrete class idiom for C++, which states the ingredients that every class must have to behave as if it were a built-in type. Other idioms, in particular an improved version of the handle/body or envelope/letter idiom that may be used to separate interface from implementation, will be treated in the next section.
Abstract data types must be indistinguishable from built-in types
To verify whether a concrete data type meets the requirements imposed by the specification of the abstract data type is quite straightforward, although not always easy. However, the task of verifying whether a concrete data type is optimally implemented is rather less well defined. To arrive at an optimal implementation may involve a lot of skill and ingenuity, and in general it is hard to decide whether the right choices have been made. Establishing trade-offs and making choices, for better or worse, is a matter of experience, and crucially depends upon the skill in handling the tools and mechanisms available.
When defining concrete data types, the list of requirements defining the canonical class idiom given in slide 2-canonical may be used as a check list to determine whether all the necessary features of a class have been defined. Ultimately, the programmer should strive to realize abstract data types in such a way that their behavior is in some sense indistinguishable from the behavior of the built-in data types. Since this may involve a lot of work, this need not be a primary aim in the first stages of a software development project. But for class libraries to work properly, it is simply essential.
In this section a brief overview will be given of the basic
concepts underlying hush.
Then we will discuss the idioms used in realizing hush and
its extensions, in particular an adapted version of
the handle/body idiom originally introduced in
The hush framework is object-oriented in that it allows for a component-wise approach to developing applications. Yet, in addition to object class interfaces, it offers the opportunity to employ a script language, such as Tcl and Prolog, to develop applications and prototypes. The hush framework is a multi-paradigm framework, not only by supporting a multi-lingual approach, but also by providing support for distributed client/server solutions in a transparent (read CORBA) manner.
In this section we will look at the idioms employed for the realization of the framework. In developing hush we observed that there is a tension between defining a clean object model and providing the flexibility needed to support a multiparadigm approach. We resolved this tension by choosing to differentiate between the object model (that is class interfaces) offered to the average user of the framework and the object model offered to advanced users and system-level developers.
In this approach, idioms play a central role.
We achieved the desired flexibility by
systematically employing a limited number of basic idioms.
We succeeded in hiding these idioms from the average
user of the framework.
However, the simplicity of our original object model
is only apparent.
Advanced or system-level developers who intend to define
extensions to the framework must be well aware
of the patterns underlying the basic concepts,
that is the functionality requirements of the classes involved,
and the idioms employed in realizing these requirements.
The hush framework -- basic concepts
In developing hush, we decided from the start to support a multiparadigm approach to software development and consequently we had to define the mutual interaction between the various language paradigms, as for example the interaction between C++ and a scripting language, such as Tcl. Current scripting languages, including Python and Tcl, provide facilities for being embedded in C and C++, but extending these languages with functionality defined in C or C++ and employing the language from within C/C++ is rather cumbersome. The hush library offers a uniform interface to a number of script languages and, in addition, it offers a variety of widgets and multimedia extensions, which are accessible through any of the script interpreters as well as the C++ interface. These concepts are embodied in (pseudo) abstract classes that are realized by employing idioms extending the handle/body idiom, as explained later on.
void eval(string cmd);
string result();
void bind(string name, handler h);
};
int dispatch( event e ); to dispatch events
int operator();
};
The use of handler objects is closely connected to the paradigm of event-driven computation. An event, conceptually speaking, is an entity that is characterized by two significant moments, the moment of its creation and the moment of its activation, its occurrence. Naturally, an event may be activated multiple times and even record a history of its activation, but the basic principle underlying the use of events is that all the information that is needed is stored at creation time and, subsequently, activation may proceed blindly. See section reactor.
...
void bind( handler h );
void bind( string action, handler h );
...
};
In addition to the widget class, the hush library also provides the class item, representing graphical items. Graphical items, however, are to be placed within a canvas widget, and may be tagged to allow for the groupwise manipulation of a collection of items, as for example moving them in response to dragging the mouse pointer.
operator();
};
In order to illustrate the idiom, we use the following class as a running example:
public A() { }
public void f1() { System.out.println("A.f1"); f2(); }
public void f2() { System.out.println("A.f2"); }
};
public A() { body = new BodyOfA(this); }
protected A(int x) { }
public void f1() { body.f1(); }
public void f2() { body.f2(); }
public void f3() { System.out.println("A.f3"); }
private A body;
};
public BodyOfA() { super(911); }
public void f1() { System.out.println("A.f1"); f2(); }
public void f2() { System.out.println("A.f2"); }
};
But the major drawback of the handle/body idiom occurs when deriving a subclass of A which partially redefines A's virtual member functions. Consider this definition of a derived class C:
public void f2() { System.out.println("C.f2"); }
};
public BodyOfA(A h) { super(911); handle = h; }
public void f1() { System.out.println("A.f1"); handle.f2(); }
public void f2() { System.out.println("A.f2"); }
A handle; // reference to invocation context
};
Note that this solution does require some programming discipline: all (implicit) references to the body object should be changed into a reference to the invocation context. Fortunately, this discipline is only required in the body classes of the implementation hierarchy.
public item(String x) { _name = x; _self = null; }
String name() { return exists()?self().name():_name; }
public void redirect(item x) { _self = x; }
boolean exists() { return _self != null; }
public item self() { return exists()?_self.self():this; }
item _self;
String _name;
};
indeed, b
}
};
Those well-versed in design patterns will recognize the Decorator
patterns (as applied in the Interviews MonoGlyph class,
Taking our view of a person as an actor
as a starting point,
we need first to establish the repertoire of possible behavior.
Next, we may wish to refine the behavior of an actor
for certain roles, such as for example the student
and employer roles, which are among the many roles
a person can play.
Assuming or `becoming' a role results in creating
a role instance if none exists and setting the _role
instance variable to that particular role.
When a person's identity has been changed,
assuming a role affects the actor that
replaced the person's original identity.
(However, only a person can change roles!)
The ability to become an actor allows us to model
the various phases of a person's lifetime
by different classes, as illustrated by
the adult class.
The design of hush and its extensions can be understood
by a consideration of two basic patterns
and their associated idioms,
that is the nested-component pattern
(which allows for nesting components that have a similar interface)
and the actor pattern
(which allows for attributing different modes or roles
to objects).
The realizations of these patterns are based on idioms
that extend an improved version of the familiar
handle/body idiom.
Our improvement concerns the introduction of
an explicit invocation context which is needed to
repair the disruption of the virtual function call mechanism
caused by the delegation to `body implementation' objects.
In this section, we will first discuss the handle/body idiom
and its improvement.
Then we will discuss the two basic patterns underlying
the design of hush
and we will briefly sketch their realization by extensions of
the (improved) handle/body idiom.
The (improved version of) the idiom is frequently used
in the hush class library.
The widget library is build of a stable
interface hierarchy, offering several common GUI widgets classes like
buttons, menus and scrollbars. The widget (handle) classes are
implemented by a separate, hidden implementation hierarchy, which
allows for changing the implementation of the widget library, without the
need to recompile dependent applications. Additionally, the idiom
helps us to ensure that the various widget implementations are used in
a consistent manner.
The nested component pattern is closely
related to the Decorator pattern treated in
The nested component pattern is realized by
applying the virtual self-reference idiom.
Key to the implementation of that idiom is the virtual
self() member of a component. The self()
member returns a
reference to the object itself (e.g. this in C++) by default,
but returns the inner component if the outer object explicitly
delegated its functionality by using the redirect() method.
Note that chasing for self() is recursive,
that is (widget) components can be nested to arbitrary depth.
The self() member must be used to access the functionality
that may be realized by the inner component.
The nested component pattern is employed
in designing the hush widget hierarchy.
Every (compound) widget
can delegate part of its functionality to an inner component.
It is common practice to derive a compound widget from another
widget by using interface inheritance only, and to delegate
functionality to an inner component by explicit redirection.
Changing roles or modes can be regarded
as some kind of state transition, and
indeed the actor pattern
(and its associated dynamic role-switching idiom)
is closely related to the
State pattern treated in
The realization of the actor pattern employs the
dynamic role-switching idiom,
which is implemented by extending the handle class
with a set of several bodies instead of only one.
To enable role-switching,
some kind of indexing is needed. Usually, a dictionary
or a simple array of roles will be sufficient.
In the hush library the actor pattern is used
to give access to multiple
interpreters via the same interface class (i.e. the kit class).
The pattern is essential in supporting the multi-paradigm nature
of the DejaVU framework.
In our description of the design of the Web components
in section Web,
we will show how dynamic role-switching is employed
for using
various network protocols via the same (net)client class.
The actor pattern is also used to define a (single)
viewer class that is
capable of displaying documents of various MIME-types
(including SGML, HTML, VRML).
The pattern schema, or rather a simplified version thereof,
is depicted in slide pattern-schema.
Each pattern must have a name, which acts as a handle
in discussions about the design. Being able to speak about
specific pattern solutions, such as a factory,
greatly facilitates discussions about design.
In this section we will look at a brief overview
of the classification as originally presented in
See Pattern Lectures
If you prefer a more dynamic approach, the prototype
pattern might be better.
A prototype is an object that may be used to create
copies or clones, in a similar way as
instances are created from a class.
However, cloning is much more dynamic, the more so
if delegation is used instead of inheritance to share
resources with some ancestor class.
See section prototypes.
The advantage of using a factory, or any of the other
creational patterns, is that exchanging product families
becomes very easy.
Just look for example at the Java Swing library.
Swing is supported under Unix, Windows and MacOS.
The key to multiple platform support is here, indeed,
the use of factories to create widgets.
Factories are also essential when using CORBA,
simply because calling a constructor is of no use
for creating objects on a remote site.
Closely related to the Composite pattern is
the Flyweight pattern, which is needed when
the number of components grows very large.
In that case, the components themselves must for obvious reasons
carry as little information as possible.
Context or state information must then be passed as a parameter.
To give some more examples, suppose there exists a
nice library for formatting text and images, but unfortunately
with only a procedural interface.
Then the Adaptor pattern may be used to provide
a interface that suits you, by wrapping the original library.
The Bridge pattern is in some sense related to the
Factory. In order to work with a platform-independent
widget library, you need, as has been explained, a factory
to hide the creation of widgets, but you also need to
bridge a hierarchy of platform-dependent implementation
classes to the more abstract platform-independent widget set.
When creating widgets to display text or images it may be very
inconvenient to create a separate class, for example
when adding scrolling functionality.
The Decorator pattern
allows you to insert additional functionality without subclassing.
Now think of a networked application, for example to be able
to incorporate components that are delivered by a server.
The library may provide a number of networking classes
that deal with all possible communication protocols.
To simplify access to these classes a Facade
may be built, hiding the complexity of the original
class interfaces.
Alternatively, remote components may be available
through a proxy.
The Proxy pattern describes how access may be regulated
by an object that acts as a surrogate for the real object.
Like composites and decorators, proxies
may be used for recursive composition.
However, proxies primarily regulate access,
whereas decorators add responsibilities,
and composites represent structure.
As an example of the Template Method pattern,
think of a compiler class that offers methods for
scanning and the creation of a parse tree.
Each of these methods may be refined without affecting the
structure of the compilation itself.
An interpreter allows for evaluating
expressions, for example mathematical formula.
Expressions may be organised in a hierarchy.
new kinds of expressions can be inserted simply by filling
in details of syntax and (semantic) evaluation.
Object composition, which employs the handle/body
idiom and delegation, is employed in
the Mediator pattern,
the Chain of Responsibility pattern
and the Observer pattern.
The actual task, such as for example updating
the display of information when the actual information
has changed, is delegated to a more specialized object,
to achieve a loose coupling between components.
The difference between a mediator and
chain of responsibility is primarily the complexity
of co-ordinating the tasks.
For example, changing the format of a single image component
from one image type to another image type may be done
simply by using an image converter (mediator),
whereas
exporting the complete document to a particular format
such as HTML may involve delegating control to a
specialized converter that itself needs access to
the original components (chain of responsibility).
We will discuss the Observer pattern in more detail later.
As an example of the Command pattern,
think of how you would realize insertion
and deletion commands in an interactive editor,
with undo!
Turning these commands into an object in which the
information necessary for undoing the command can be stored,
for example having a snapshot of the state stored in
a Memento, it suffices to stack the actual
command objects. To undo a command, pop the stack and invoke
the undo method.
The Strategy pattern may be used
to hide the details of the various layout algorithms
that are available. For example, you may use a straightforward
algorithm that formats the text line by line, or you may
use the much more advanced formatting algorithm of \TeX,
which involves the minimalization of penalties.
These alternatives can be collected in a formatting strategy
hierarchy, that hides the implementation details from the client
by a common interface.
When doing the formatting, you may wish
to separate the traversal of the component tree structure
from the actual formatting operations.
This may be accomplished by employing the Visitor
pattern.
In general it is recommended to abstract
from the data structure and use a more abstract
way, such as an Iterator or Visitor
to access and traverse it.
The State pattern is similar to the dynamic role switching idiom
that has been discussed in section hush-patterns Dynamic role-switching
For many applications, static type hierarchies
do not provide the flexibility needed
to model dynamically changing roles.
For example we may wish to consider a person
as an actor capable of various roles during his lifetime,
some of which may even coexist concurrently.
The characteristic feature of the
dynamic role switching idiom underlying the
actor pattern is that it
allows us to regard a particular entity from multiple
perspectives
and to see that the behavior of that entity changes accordingly.
We will look at a possible realization of the idiom below.
public static final int Person = 0;
public static final int Student = 1;
public static final int Employer = 2;
public static final int Final = 3;
public void walk() { if (exists()) self().walk(); }
public void talk() { if (exists()) self().talk(); }
public void think() { if (exists()) self().think(); }
public void act() { if (exists()) self().act(); }
public boolean exists() { return false; }
public actor self() { return this; }
public void become(actor A) { }
public void become(int R) { }
};
public void talk() { System.out.println("OOP"); }
public void think() { System.out.println("Z"); }
};
public void talk() { System.out.println("money"); }
public void act() { System.out.println("business"); }
};
public person() {
role = new actor[ Final+1 ];
for( int i = Person; i <= Final; i++ ) role[i]=this;
become(Person);
}
public boolean exists() { return role
public void talk() { System.out.println("interesting"); }
};
public static void main(String[] args) {
person p = new person(); p.talk(); // empty
p.become(actor.Student); p.talk(); // OOP
p.become(actor.Employer); p.talk(); // money
p.become(new adult()); p.talk(); // interesting
p.become(actor.Student); p.talk(); // OOP
p.become(p); p.talk(); // old role: employer
p.become(actor.Person); p.talk(); // initial state
}
};
The art of hush programming
\label{hush-patterns}
For the average user, programming in hush
amounts (in general) to instantiating widgets and
appropriate handler classes,
or derived widget classes that define their own
handler.
However, advanced users and system-level programmers
developing extensions are required to comply
with the constraints resulting
from the patterns underlying the design of hush
and the application of their associated idioms
in the realization of the library.
Invocation context
The handle/body idiom is one of the most popular idioms.
It underlies
several other idioms and patterns
(e.g. the envelope/letter idiom, Invocation context
The nested component pattern
Nested components
The actor pattern
Actor pattern
A catalogue of design patterns
subsections:
Why patterns, you may wonder.
Why patterns and why not a method of object-oriented design
and an introduction in one or more object-oriented languages?
The answer is simple.
Patterns bookmark effective design.
They fill the gap between the almost infinite possibilities of
object-oriented programming languages and tools
and the rigor of methodical design.
As Brian Foote expressed it in A catalogue of design patterns
Name -- handle
Problem -- when to apply
Solution -- general arrangement
Consequences -- tradeoffs
Creational patterns
Design for change means to defer commitment
to particular object implementations as long as possible.
Due to inheritance, or rather subtyping,
the client, calling a particular method,
can choose the most abstract class, highest in the hierarchy.
However, when it comes to creating objects,
there seems to be no other choice than naming
the implementation class explicitly.
Wrong.
Creational patterns are meant to take care of that,
that is to hide the actual class used as far away as possible.
Creational patterns
Structural patterns
Objects rarely live in isolation.
In slide structural-patterns a selection of the
structural patterns treated in Structural patterns
Pattern Alias Remarks Composite part/whole collections of components Flyweight handle/body extrinsic state, many objects Adaptor wrapper resolve inconsistency between interfaces Bridge handle/body relate abstraction to implementation Decorator handle/body to introduce additional functionality Facade handle/body provides unified interface Proxy handle/body to defer ... remote, virtual, protection Behavioral patterns
Our final category of patterns, behavioral patterns,
concern the construction of algorithms and the assignment
of responsibilities to the objects that cooperate in achieving
some goal.
Behavioral patterns
class
object
Encapsulating behavior
one-to-many dependencies and notification
In effect, MVC or the Observer pattern can be regarded as a simple method for constraint propagation. An advantage is that unexpected updates can be easily dealt with.
In the implementation of the Observer pattern there are a number of problems and tradeoffs that must be considered. For example, do we allow one observer to be attached to more than one subject? Do we allow for alternative update semantics, for example observer-pull instead of subject-push? Do we provide facilities for specifying aspects of interest, so that updates only need to concern those aspects? And finally, how do we guarantee mutual consistency between subjects and observers when we do allow for alternative update semantics?
In this section we will look at the Reactor
pattern that explains the interaction between objects
and the environment.
We will also look at an event system, in
which the event types are defined by the application programmer.
In this application, events are used to maintain global
consistency, similar to the Observer pattern.
The Reactor pattern
The Reactor pattern has been introduced
in
See D.C. Schmidt, Using Design Patterns to Develop Reusable Object-oriented
Communication Software, CACM October '95, 38(10): 65-74
Concrete handlers, derived from an abstract handler,
must provide a method, such as
Consequences
The fact that control is handed over to
the environment has, however, also some disadvantages.
First of all, as experience with student assignments shows,
it is difficult to learn in the beginning.
But even when mastered, applications may be hard
to debug, since it is not always clear why a particular handler
was invoked, and because it may be difficult to repeat
the computation preceding the fault.
Applicability
Abstract event systems
To conclude this chapter about idioms and patterns,
we will look at a somewhat more detailed example
employing (user-defined) events to characterize
and control the interaction between the objects.
The example is taken from
For Functional behavior
protected thermometer( float v ) { temp = v; }
public void set(float v) { temp = v; }
public float get() { return temp; }
protected float temp;
};
public centigrade() { super(0); }
public void set(float v) { temp = v + 273; }
public float get() { return temp - 273; }
};
public fahrenheit() { super(0); }
public void set(float v) { temp = (v - 32) * 5/9 + 273; }
public float get() { return temp * 9/5 + 32 - 273; }
};
User interface
public displayer() { ... }
public void put(String s) { ... }
public void put(float f) { ... }
};
public prompter(String text) { ... }
public float get() { ... }
public String gets() { ... }
};
Events
pubic void dependent(event e) { ... }
pubic void process() { ... }
public void operator(); // abstract method
private event[] dep;
};
public update(thermometer th, prompter p) {
_th =th; _p = p;
}
void operator()() {
_th.set( _p.get() );
process();
}
thermometer _th;
prompter _p;
};
public show(thermometer th, displayer d) {
_th = th; _d = d;
}
public void operator() {
_d.put( _th.get() );
process();
}
thermometer _th;
displayer _d;
};
The installation
Discussion
For an introduction to Java, there is ample choice.
An excellent online tutorial can be found on
java.sun.com/docs/books/tutorial .
As textbooks on C++ I recommend
In the previous chapter we looked at idioms and patterns that resulted from object-oriented software development. In this chapter we will focus on the software engineering of object-oriented systems and issues of design in particular, including the identification of objects and the specification of contractual obligations.
Additional keywords and phrases:
requirements, analysis, implementation, design as transition,
CRC cards, responsibilities, heuristics,
contractual obligations,
validation
In this section we will look at a variety of existing methods and the tools they offer. We do not discuss the tools and diagram techniques used in any detail. However, we will discuss the Fusion method in somewhat more detail. Fusion is a strongly systematic approach to object-oriented software development that integrates various concepts and modeling techniques from the other methods, notably OMT, Booch OOD, Objectory and CRC. We will discuss the process view underlying Fusion and sketch the models it supports in relation to the other methods. For the reader this section may supply an overview and references needed for a more detailed study of a particular method or tool.
A recent development is the Unified Modeling Language (UML), which has been approved as a standard in 1998. UML brings together the models and notations featured by the various methods. Jim Rumbaugh, Grady Booch and Ivar Jacobson, all leading experts in object-oriented development, joined forces to achieve this.
The importance of such a standardization can hardly
be overemphasized.
However, it must be noted that UML does not provide
a method, in the sense of delineating the steps that
must be taken in the development of a system.
UML itself may be regarded as a toolbox, providing
notations and modeling techniques that may be deployed
when needed.
A brief overview of UML is given in
UML.
An excellent introduction to UML, including advice
how to apply it in actual projects may be found
in
In contrast to the model used in analysis,
both the design model and the implementation model are more
solution oriented than domain oriented.
The implementation model is clearly dependent on the available
language support.
Within a traditional life-cycle, the design model may be seen as
a transition from analysis to implementation.
The notion of objects may act as a unifying factor,
relating the concepts described in the analysis document
to the components around which the design model is built.
However, as we have noted, object-oriented development
does not necessarily follow the course
of a traditional software life-cycle.
Alternatively, we may characterize the function of the
design document as a justification of the choices
made in deciding on the final architecture of the system.
This remark holds insofar as an object-oriented approach
is adopted for both design and implementation.
However, see
In
An interesting architectural issue is, how may we
provide for future extensions of the system?
How easily can we reuse the design and the code for
a system supporting different kinds of accounts,
or different input or output devices?
And how can we establish that the objects, as
identified, interact as desired?
Assigning responsibilities
Design is to a large extent a matter of creative thinking.
Heuristics such as performing a linguistic scan
on the requirements document for finding objects
(nouns), methods (verbs) and attributes (adjectives)
may be helpful, but will hopelessly fail
when not applied with good taste and judgement.
Not surprisingly, one of the classical techniques
of creative writing, namely the shoe-box method,
has reappeared in the guise of an object-oriented
development method.
The shoe-box method consists of writing fragments
and ideas on note cards and storing them in a (shoe) box,
so that they may later be retrieved and manipulated
to find a suitable ordering for the
presentation of the material.
To find a proper decomposition into objects,
the method creates for each potential
(object) class a so-called CRC card,
which lists the Class name, the Responsibilities
and the possible Collaborators of the proposed class.
In a number of iterations, a collection of cards
will result that more or less reflects the
structure of the intended system.
According to the authors (see Beck and Cunningham, 1989), the method
effectively supports the early stages of design,
especially when working in small groups.
An intrinsic part of the method consists
of what the authors call dynamic simulation.
To test whether a given collection of cards
adequately characterizes the functionality of the intended
system, the cards may be used to simulate
the behavior of the system.
When working in a group, the cards may be distributed
among the members of the group, who participate
in the simulation game according to their cards.
See slide 3-crc.
analyze a little,
design a little,
implement a little,
test a little ...
(The notion of gestalt comes from perception psychology, where it means a global perceptual configuration emerging from the background.)For dynamically checking the invariance condition, a test should be executed when evaluating the constructor and before and after each method invocation. While a method is being executed, the invariant need not necessarily hold, but it is the responsibility of a method to restore the invariant when it is disrupted. In case object methods are recursively applied, the invariant must be restored when returning to the original caller.
An alternative approach to incorporating assertions
in a class description is presented in
A pre-condition limits the cases that a supplier must handle!
To establish that the contract of a derived class refines the contract of the base class it suffices to verify that the following rules are satisfied. See slide 3-inheritance.
However, before we delve into the formal foundations of object-oriented modeling, we will first look at an example of application development and explore the design space of object-oriented languages and system implementation techniques. These insights will enable us to establish to what extent we may capture a design in formal terms, and what heuristics are available to accomplish the tasks remaining in object-oriented development.
After studying general issues in the design and software engineering of object-oriented applications and frameworks, it is time to focus in somewhat more detail on actual application development.
In this chapter we will look at the drawtool application, as a representative of a broader category of interactive editing tools.
Additional keywords and phrases:
hush framework, interactive editors, law of Demeter,
formal specification in Z, abstract systems
After that we will treat some miscellaneous issues in the design of classes. This chapter will be concluded with a case study, a concise, yet detailed, example of a more formal approach to the development of an object-oriented application.
In the Software Engineering curriculum at the Vrije Universiteit, we have repeatedly used interactive editors as a medium-term assignment for CS2 students (five weeks for groups of four or five students). One example of such an assignment is the Interactive Design Assistant discussed in section IDA. Another example is the musical score editor (see appendix Projects), which has been chosen by a selected group of CS3 and CS4 students as a practical assignment for the Object-Oriented Software Development course.
In this section we will look at the drawtool application, which is a representative realization of a (rather simple) drawing editor. The implementation of drawtool presented here is realized in the Java version of the hush framework. The hush C++ framework has been used for a number of years in the Software Engineering curriculum, but has recently been replaced by Java with Swing. The drawtool application is nevertheless interesting because it acted for many years as the basic example of an interactive editor for quite a number of students.
Before studying drawtool, we will first look at
the realization of a drawing canvas in hush
A simple drawing canvas in hush
In the widget class hierarchy depicted on the right in \sliref{hush-overview}, the widget class represents an abstract widget, defining the commands that are valid for each of the descendant concrete widget classes. The widget class, however, is not an abstract class in Java or C++ terms. It may be used for creating references to widgets defined in Tcl. In contrast, employing the constructor of one of the concrete widget classes results in actually creating a widget.
As an example look at the code for the drawing canvas
widget depicted in slide drawing-canvas.
For the draw class we must distinguish between
a handler and a canvas part.
The handler part is defined by the methods
press, release and motion.
The canvas part allows for drawing figures, such as a small circle.
Widgets may respond to events.
To associate an event with an action,
an explicit binding must be specified
for that particular widget.
Some widgets provide default bindings.
These may, however, be overruled.
The function bind is used to associate
handlers with events.
The first string parameter of
bind may be used to specify
the event type.
Common event types are,
for example,
ButtonPress, ButtonRelease
and Motion, which are the
default events for canvas widgets.
Also keystrokes may be defined as events,
for example Return,
which is the default event for
the entry widget.
The function may be
used to associate a handler object
with the default bindings
for the widget.
Concrete widgets may not override the bind
function itself, but must define the protected
function install.
Typically, the install function consists
of calls to bind
for each of the event types that is
relevant to the widget.
In addition, the widget class offers two
functions that may be used
when defining compound or mega widgets.
The function must by used
to delegate the invocation
of the eval, configure and bind functions
to the widget w.
The function gives access to the widget
to which the commands are redirected.
The function path will still deliver
the path name of the outer widget.
Calling redirect when creating the compound widget
class suffices for most situations.
However, when the default events must be changed or the
declaration
of a handler must take effect for several component widgets,
the function install must be redefined
to handle the delegation explicitly.
A structural view of the drawtool application is given
in slide drawtool-structure.
Usually, the various widgets constituting the user
interface are (hierarchically) related to each other,
such as in the drawtool application which contains
a canvas to display graphic elements,
a button toolbox for selecting the graphic
items and a menubar offering various options
such as saving the drawing in a file.
Widgets in Tk are identified by a path name.
The path name of a widget reflects its possible
subordination to another widget.
See slide widget-hierarchy.
A menubar consists of menubuttons to which actual menus are attached.
Each menu consists of a number of entries,
which may possibly lead to cascaded menus.
The second button of the menubar is defined by the
EditMenu.
The EditMenu requires a toolbox and creates
a menubutton.
It configures the button and
defines a menu containing two entries,
one of which is a cascaded menu.
Both the main menu and the cascaded menu
are given the toolbox as a handler.
This makes sense only because
for our simple application
the functionality offered by
the toolbox and EditMenu coincide.
In \sliref{object-invariant}, we have defined a class employee.
The main features of an employee are the (protected)
attribute sal (storing the salary of an employee)
and the methods to access and modify the salary
attribute.
For employee objects,
the invariant (expressing that any amount k
is equal to the salary of an employee whose
salary has been set to k)
clearly holds.
Now imagine that we distinguish between ordinary employees
and managers by adding a permanent
bonus when paying the salary of a manager,
as shown in slide hidden-bonus.
The reader may judge whether this example is realistic or not.
-- A method m is a client of C if m calls a method of C
-- If m is a client of C then C is a supplier of m
-- C is an acquaintance of m if C is a supplier of m but not (the type of) an argument of m or (of) an instance variable
of the object of m
The guideline concerning the use of safe suppliers is known
as the Law of Demeter, of which the underlying
intuition is that the programmer should not be bothered
by knowledge that is not immediately apparent
from the program text (that is the class interface)
or founded in well-established conventions
(as in the case of using special global variables).
See slide 4-demeter.
Do not refer to a class C in a method m unless C is (the type of)
In addition, one must establish
the relationships between the object classes
constituting the system and
delineate the facilities the system offers to the user.
Such facilities are usually derived from
a requirements document and may be formally specified
in terms of abstract operations on the system.
In this section we will look at
the means we have available to express
the properties of our object model,
and we will study how we may
employ abstract specifications of system
operations to arrive at the integration
of user actions and the object model
underlying a system in a seamless way.
The approach sketched may be characterized
as event-centered.
In contrast, objects (in the more general sense)
usually hide object and non-object data members,
and instead provide a method interface.
Moreover, object-oriented modeling focuses on
behavioral properties, whereas semantic modeling has been
more concerned with (non-behavioral) data types
and (in the presence of inheritance) data subtypes.
Relations, as may be expressed in the entity-relationship
model, can partly be expressed directly in terms
of the mechanisms supported by object-oriented
languages.
For instance, the is-a relation corresponds
closely (although not completely) with
the inheritance relation.
See slide 3-challenges.
Both the has-a and uses relation is
usually implemented by including (a pointer to)
an object as a data member.
Another important relation is the is-like relation,
which may exist between objects that are neither related
by the inheritance relation nor by the subtype relation,
but yet have a similar interface and hence may be regarded
as being of analogous types.
The is-like relation may be enforced by parametrized
types that require the presence of particular methods,
such as a compare operator in the case of a generic
list supporting a sort method.
In the following, an outline of
the specification language Z will be given.
More importantly, the specification
of a simple library system will be discussed,
illustrating how we may specify
user actions in an abstract way.
(The use of the Z specification language
is in this respect only of subsidiary
importance.)
In the subsequent section, we will look
at the realization of the library
employing an abstract system of objects
and events corresponding to the user
actions, which reflects the characterization
given in the formal specification.
The specification language Z is based on classical
(two-valued) logic and set theory.
It has been used in a number of industrial projects,
Similarly, we may specify the operations
for the $Bounded::Counter$ by including
the corresponding operations
specified for the Counter, adding conditions
if required.
From a schema we may easily extract the
pre-conditions for an operation by removing from
the conditions the parts involving
a primed variable.
Clearly, the post-condition is then
characterized by the conditions thus eliminated.
For example, the pre-condition of the
$Counter::Incr$
operation is $v? \geq 0$, whereas
the post-condition is $n' = n + v?$
which corresponds to the implementation requirement
that the new value of the Counter
is the old value plus the value of the
argument $v?$.
In a similar way, the pre-condition
for applying the $Bounded::Incr$ operation
is $n + v? \leq Max$.
Note, however, that this pre-condition
is stronger than the original
pre-condition $v? \geq 0$, hence to conform
with the rules for refinement
we must specify what happens when
$ n + v? > Max $ as well.
This is left as an exercise for the reader.
Clearly, although Z lacks a notion
of objects or classes, it may conveniently
be employed to specify the behavior
of an object.
In
In contrast to Eiffel, which offers only
a semi-formal way in which to specify the behavior
of object classes, Z allows for
a precise formal specification of
the requirements a system must meet.
To have the specification reflect
the object structure of the system
more closely, one of the extensions
of Z mentioned above may be used.
An example of using (plain) Z
to specify the functionality
of a library system is given below.
For
Note that lending a book involves
both the invocation of $book::borrow$
and $person::allocate$.
This could easily be simplified by extending the function
$book::borrow$ and $book::_return$
with the statements $p->allocate(this)$
and $p->deallocate(this)$ respectively.
However, I would rather take the opportunity to illustrate the use
of events, providing a generic solution
to the interaction problem noted.
The Borrow event class provides a controlled
way in which to effect the borrowing of a book.
In a similar way, a Return event class may be defined.
Events are primarily used
as intermediate between the user (interface)
and the objects comprising the library system.
For the application at hand, using events
may seem to be somewhat of an overkill.
However,
events not only give a precise characterization of
the interactions involved but, equally importantly,
allow for extending the repertoire
of interactions without disrupting
the structure of the application
simply by introducing additional event types.
The original paper on hush is
boolean dragging;
public draw(String path) {
super(path);
dragging = false;
bind(this);
}
public void press(event ev) {
dragging = true;
}
public void release(event ev) {
dragging = false;
}
public void motion(event ev) {
if (dragging)
circle(ev.x(),ev.y(),2,"-fill black");
}
};
The drawtool application
The toolbox component
tablet tablet;
public toolbox(widget w, tablet t) {
super(w,"toolbox");
tablet = t;
new toolbutton(this,"draw");
new toolbutton(this,"move");
new toolbutton(this,"box");
new toolbutton(this,"circle");
new toolbutton(this,"arrow");
}
public int operator() {
tablet.mode(_event.arg(1)); // reset tablet mode
return OK;
}
};
public toolbutton(widget w, String name) {
super(w,name);
text(name);
bind(w,name);
pack("-side top -fill both -expand 1");
}
};
The menubar component
public menubar(widget w, tablet t, toolbox b) {
super(w,"bar");
configure("-relief sunken");
new FileMenu(this,t);
new EditMenu(this,b);
new HelpButton(this);
}
};
The tablet component
int _mode;
canvas canvas;
handler[] handlers;
final int DRAW = 0;
final int MOVE = 1;
final int CIRCLE = 2;
final int BOX = 3;
final int ARROW = 5;
public tablet(widget w, String name, String options) {
super(w,name,"*");
handlers = new handler[12];
init(options);
redirect(canvas); // to delegate to canvas
bind(this); // to intercept user actions
handlers[DRAW] = new DrawHandler(canvas);
handlers[MOVE] = new MoveHandler(canvas);
handlers[BOX] = new BoxHandler(canvas);
handlers[CIRCLE] = new CircleHandler(canvas);
handlers[ARROW] = new ArrowHandler(canvas);
_mode = 0; // drawmode.draw;
}
public int operator() {
handlers The drawtool class
widget root;
tablet tablet;
public drawtool() { System.out.println("meta handler created"); }
public drawtool(String p, String options) {
super(p,"*"); // create empty tablet
init(options);
}
public int operator() {
System.out.println("Calling drawtool:" + _event.args(0) );
String[] argv = _event.argv();
if ("self".equals(argv[1])) tk.result(self().path());
else if ("drawtool".equals(argv[0]))
create(argv[1],_event.args(2));
else if ("path".equals(argv[1])) tk.result(path());
else if ("pack".equals(argv[1])) pack(_event.args(2));
else self().eval( _event.args(1) ); // send through
return OK;
}
void create(String name, String options) {
drawtool m = new drawtool(name,options);
}
void init(String options) {
root = new frame(path(),"-class Meta");
frame frame = new frame(root,"frame");
tablet = new tablet(frame,"tablet",options);
toolbox toolbox = new toolbox(frame,tablet);
menubar menubar = new menubar(root,tablet,toolbox);
toolbox.pack("-side left -fill y -expand 0");
tablet.pack("-side left -fill both -expand 1");
menubar.pack();
frame.pack("-expand 1 -fill both");
redirect( tablet ); // the widget of interest
}
};
Guidelines for design
subsections:
Computing is a relatively young discipline.
Despite its short history, a number of styles
and schools promoting a particular style have emerged.
However, in contrast to other disciplines
such as the fine arts (including architecture)
and musical composition, there is no
well-established tradition of what is to be considered
as good taste with respect to software design.
There is an on-going and somewhat pointless debate as to
whether software design
must be looked at as an art or must be promoted
into a science. See, for example, Development process -- cognitive factors
Design criteria -- natural, flexible, reusable
Individual class design
A class should represent a faithful model of a single concept,
and be a reusable, plug-compatible component
that is robust, well-designed and extensible.
In slide class-design, we list a number of suggestions
put forward by Class design -- guidelines
Inheritance and invariance
When developing complex systems or class libraries,
reliability is of critical importance.
As shown in section contracts,
assertions provide a means by which to check the runtime
consistency of objects.
In particular, assertions may be used to check that
the requirements for behavioral conformance
of derived classes are met.
Invariant properties -- algebraic laws
public:
employee( int n = 0 ) : sal(n) { }
employee* salary(int n) { sal = n; return this; }
virtual long salary() { return sal; }
protected:
int sal;
};
Invariant
Problem -- hidden bonus
public:
long salary() { return sal + 1000; }
};
Invariant
Solution -- explicit bonus
public:
manager* bonus(int n) { sal += n; return this; }
};
Invariant -- restored
An objective sense of style
The guidelines presented by G
Client
Supplier
Acquaintance
Law of Demeter
Class transformations
From specification to implementation
subsections:
Designing an object-oriented system
requires the identification of
object classes and the characterization of
their responsibilities,
preferably by means of contracts.
Structural versus behavioral encapsulation
Object-oriented modeling has clearly been inspired
by or, to be more careful, shows significant
similarity to the method of semantic modeling
that has become popular for developing information systems.
In an amusing paper, Object-oriented modeling
Model-based specification
State and operations
Change and invariance
Verification
State
Operations
Counter
Bounded counter
The specification of a library
State
Operations
Abstract systems and events
User actions may require complex interactions
between the objects constituting
the object model of a system.
Such interactions are often of
an ad hoc character in the sense that they embody one
of the many possible ways in which the functionality
of objects may be used.
What we need is a methodology or paradigm that
allows us to express these interactions in a concise
yet pragmatically amenable way.
In Abstract systems -- design methodology
Events -- high level glue
Example -- the library
Abstract system -- exemplary interface
public:
person* borrower;
book() {}
void borrow( person* p ) { borrower = p; }
void _return( person* p ) { borrower = 0; }
bool inlibrary() { return !borrower; }
};
public:
person() { books = new set example
book* ChandyMisra = new book();
book* Smalltalk80 = new book();
person* Hans = new person();
person* Cees = new person();
Stroustrup->borrow(Hans);
Hans->allocate(Stroustrup);
ChandyMisra->borrow(Cees);
Cees->allocate(ChandyMisra);
Smalltalk80->borrow(Cees);
Cees->allocate(Smalltalk80);
Events
public:
virtual void operator()() = 0;
};
public:
Borrow( person* _p, book* _b ) { _b = b; _p = p; }
void operator()() {
require( _b && _p ); // _b and _p exist
_b->borrow(p);
_p->allocate(b);
}
private:
person* _p; book* _b;
};
public:
Return( person* _p, book* _b ) { _b = b; _p = p; }
void operator()() {
require( _b && _p );
_b->_return(p);
_p->deallocate(b);
}
private:
person* _p; book* _b;
};
Summary
The drawtool application
Guidelines for design
From specification to implementation
Questions
Further reading
When developing an object-oriented system, at some time a choice has to be made for an actual programming language or environment. It goes without saying that the optimal environment will be one that is in accord with the method chosen for design. Naturally, other desiderata (involving efficiency, portability or client-imposed constraints) may play an equally significant role.
Additional keywords and phrases:
programming languages, orthogonality, reliability, complexity, types, delegation, multiple paradigms, prototypes,
reflection
Whatever motivation lies behind the development of a programming
language, every language is meant to serve some purpose.
Whether implicitly or explicitly stated, a programming
language is characterized by its design goals and
the applications it is intended to support.
A fortiori this holds for object-oriented programming
languages.
The impetus to research in object-oriented programming
may be traced back to the development of Simula,
which was originally intended for discrete event simulation.
As observed in
Of the 69 (standalone) object-oriented languages surveyed,
53 were research projects and only 16 were commercial products.
Of these, 14 were extensions of either Lisp (10)
or C (4).
Among the remaining languages, quite a number were derived from
languages
such as Pascal, Ada or Prolog.
There is a great diversity between the different object-oriented languages.
However, following
POOL-T also supports the notion of active objects.
Active objects have a body which allows them to execute their
own activity in parallel with other active objects.
To answer a request to execute a method, an active object must
explicitly interrupt its activity (by means of an answer or
accept statement as in Ada).
POOL-T is interesting, primarily, because it is complemented
by extensive theoretical research into the semantical foundations
of parallel object-oriented computing.
See Alternative object models
Since the introduction of Smalltalk, the predominant
notion of objects has been based on the distinction
between classes and objects.
Classes serve to describe the functionality and behavior of objects,
while objects are instance of classes.
In other words, classes serve as templates to create objects.
Inheritance, then, may be regarded
as a means by which to share (descriptions of) object
behavior.
It is generally defined on classes in terms of a derivation mechanism,
that allows one to declare a class to be a subclass of another (super)
class.
The distinction between classes and objects leads to a number
of difficulties, both of a pragmatic and theoretical nature.
(See also sections dimensions and meta for a discussion of the theoretical
problems.)
For example, the existence of one-of-a-kind classes,
that is classes which have only one instance, is
often considered unnatural.
An example of a class-less language is the language Self.
Self has a Smalltalk-like syntax, but in contrast
to Smalltalk only supports objects (containing slots)
and messages, and hence no classes.
Slots may be designated to be parent-slots which means that
messages that cannot be handled by the object itself are delegated to the
parent object.
In contrast to inheritance, which is static since the inherited
functionality is computed at object creation time, delegation
to parent objects as in Self is dynamic, since parent slots
may be changed during the lifetime of an object.
Objects in Self may themselves be used to create other objects
(as copies of the original object) in a similar way as classes
may be used to create instances.
However, the changes made to an object are propagated when cloning
object copies.
Single objects, from which copies are taken, may in other words be regarded
as prototypes, approximating in a dynamic way the functionality of
their offspring,
whereas classes provide a more static, so to speak universal,
description of their object instances.
Self employs runtime compilation, which is claimed
to result in an efficiency comparable to C in
Alternative object models may also be encountered
in object-oriented database managements systems and in systems
embedding objects such as hypertext or hypermedia systems.
Object extensions of Lisp, C and Prolog
The notion of object is to a certain extent orthogonal
to, that is independent of, language constructs around
which programming languages may be constructed,
such as expressions, functions and procedures.
Hence, it should come as no surprise that a number of (popular)
object-oriented programming languages were originally
developed as extensions of existing languages or
language implementations.
See slide 5-extensions.
JPL
Jacl, Tcl Blend
JPython
Scripting has clear advantages for rapid prototyping. Disadvantages of scripting concern the lack of efficiency, and the absence of compile-time checks.
Script languages may be extended using C/C++,
and more recently Java.
The impact of Java becomes evident when considering
that there exists a Java implementation for almost
each scripting language, including Tcl/Tk, Perl and Python.
JPython, which is the realization of Python in Java,
even offers the possibility to integrate Python classes with Java
classes, and is announced as a candidate
scripting platform for Java in
Java has also in other respects stimulated programming language
research,
since it appears to be an ideal platform
for realising higher level programming languages.
Objects in Javascript
Javascript is a somewhat special case, since it allows for
the use of built-in objects,
in particular the objects defined by the
Document Object Model (DOM),
and its precursors.
Nevertheless, due to its dynamic nature, Javascript also allows
for creating user-defined objects,
as indicated in the example
below.
Which objects are available as built-in objects
depends on the environment in which Javascript programs
are executed.
In the example, there is an invocation of
the write method for a document object.
The document object, as well as other objects
corresponding to the browser environment and the contents of
the page loaded, are part of the Document Object Model,
which is discussed in more detail
in section DOM.
As an aside, Javascript has become surprisingly popular for writing
dynamic HTML pages, as well
as for writing server-side scripts.
It is also supported by many VRML (Virtual Reality Modeling Language)
browsers to define script nodes.
See section DIVA.
A reference implementation of Javascript is available,
for embedding Javascript in C/C++ applications.
Smalltalk (that is Smalltalk-80 of ParcPlace Systems)
comes with a large collection of general purpose and graphics
programming classes, that are identical for both MS-DOS and
Unix platforms.
Also Eiffel
comes with a standard collection of well-documented
libraries containing common data structures, container
classes and classes for graphics programming.
For both Smalltalk and Eiffel, the accompanying classes
may almost be considered to be part of the language
definition, in the sense that they provide a
standard means to solve particular problems.
In contrast, for C++ there is almost no standard
library support (except for IO stream classes).
Even worse, the various C++ compiler vendors
disagree considerably in what functionality the standard
class libraries of C++ must offer.
Fortunately, however, there is an increasingly large
number of third party libraries (commercially and non-commercially)
available.
The burden of choosing the appropriate libraries
is, however, placed on the shoulders of a user
or a company,
which has the advantage that a more optimal
solution may be obtained
than possible within the confines of standard libraries.
Java, on the other hand, offers an overwhelming amount
of APIs, including a Reflection API, for meta programming,
APIs for networking, communication, and APIs for multimedia
and 3D.
Perhaps the greatest benefit of Java is the effort put into
the standardization of these APIs.
Of the object-oriented programming languages
we consider,
Smalltalk definitely offers the most comprehensive
programming environment (including editors, browsers and
debuggers).
Eiffel comes with a number of
additional tools (such as a graphical browser, and a
program documentation tool) to support program
development (and maintenance).
In contrast, C++ usually comes with nothing at all.
However, increasingly many tools (including browsers
and debuggers) have become available.
For Java there are a number of IDEs (Integrated Development Environments)
available, most of which run only on the PC platform.
The issue of producing documentation from C++ is still open.
A number of tools exist (including a WEB-like system
for C++ and a tool to produce manual pages from C++
header files) but no standard has
yet emerged.
Moreover, some people truly dislike the terseness of C/C++.
Personally, I prefer the C/C++ syntax above the syntactical
conventions of both Eiffel and Smalltalk,
provided that it is used in a disciplined fashion.
Java programs may be documented using javadoc.
The javadoc program may be regarded as the standard
C++ has been waiting for, in vain.
An important feature of Eiffel
is that it supports assertions that may be validated
at runtime.
In combinations with exceptions, this provides
a powerful feature for the development
of reliable programs.
At the price of some additional coding (for example,
to save the current state to enable the use
of the old value), such
assertions may be expressed by using the assert macros
provided for C++.
In defense of C++,
it is important to acknowledge that C++ offers
adequate protection mechanisms to shield classes
derived by inheritance from the implementation details
of their ancestor classes.
Neither Smalltalk nor Eiffel offer such protection.
Java was introduced as a more reliable variant of C++.
Java's reliability comes partly from the shielded
environment offered by the Java virtual machine,
and partly from the absence of pointers and
the availability of built-in garbage collection.
Practical experience shows that for the average student/programmer
Java is indeed substantially less error-prone than C++.
As far as the assertion mechanism offered by Eiffel
is concerned,
The Java language offers only single (code) inheritance,
but allows for multiple interface inheritance.
The realization of (multiple) interfaces seems to be
a fairly good substitute for multiple (implementation) inheritance.
The compilation of Eiffel programs can result in programs
having adequate execution speed.
However, in Eiffel dynamic binding takes place in principle
for all methods.
Yet a clever compiler can significantly
reduce the number of indirections needed to execute a method.
In contrast to C++, in Eiffel all objects are created
on the heap.
The garbage collection needed to remove these objects
may affect the execution speed of programs.
C++ has been designed with efficiency in mind.
For instance, the availability of inline functions,
and the possibility to allocate objects on the runtime stack
(instead of on the heap),
and the possibility to declare friend functions
and classes that have direct access to the private
instance variables of a class
allow the programmer to squeeze out the last drop
of efficiency.
However, as a drawback, when higher level functionality
is needed
(as in automatic garbage collection)
it must be explicitly programmed, and a similar price
as when the functionality would have
been provided by the system has to be paid.
The only difference is that the programmer has a choice.
At the time of writing there does not exist a truly
efficient implementation of the Java language.
Significant improvements may be expected from
the JIT (Just In Time) compilers that produce native code
dynamically, employing techniques as originally developed for
the Self language, discussed in section Self.
Eiffel contains few language elements
that extend beyond object-oriented programming.
In particular, Eiffel does not allow for overloading
method names (according to signature) within a class.
This may lead to unnecessarily elaborate method names.
(The new version of Eiffel (Eiffel-3) does
allow for overloading method names.)
Without doubt, C++ is generally regarded as a highly complex
language.
In particular, the rules governing the overloading of operators
and functions are quite complicated.
The confusion even extends to the various
compiler suppliers, which is one of the
reasons why C++ is still barely portable.
Somewhat unfortunately,
the rules for overloading and type conversion
for C++ have to a large extent been determined
by the need to remain compatible with C.
Even experienced programmers need occasionally
to experiment to find out what will happen.
According to
Java is certainly less complex than C++.
For example, it offers no templates, no operator overloading
and no type coercion operators.
However, although Java is apparently easier to use, it is
far less elegant than C++ when it comes to creating user-defined types.
Class interfaces in Java are usually much more verbose
than similar interfaces in C++.
And, due to the absence of templates,
type casts are necessary in many places.
On the other hand, casts in Java are type safe.
An example of the declaration of an active object in sC++
is given in slide ex-active.
The sC++ language is implemented as an extension to the GNU C++ compiler.
The sC++ runtime environment offers the possibility
to validate a program by executing random walks,
which is a powerful way to check the various synchronization conditions.
The model of active objects supported by sC++
has also been realized as a Java library, see
As argumented in
Whenever a method is repeatedly invoked,
the address of the recipient object may be backpatched
in the caller.
In some cases, even the result may be inlined
to replace the request.
Both techniques make it appear that
message passing takes place, but at a much lower price.
More complicated techniques,
involving lazy compilation (by delaying the compilation
of infrequently visited code)
and message splitting (involving a dataflow
analysis and the reduction of redundancies)
may be applied to achieve more optimal results.
Benchmark tests have indicated a significant improvement
in execution speed (up to 60% of optimized C code)
for cases where type information could be dynamically obtained.
The reader is referred to
To be an object, a class itself must be an instance
of a class (which for convenience we will call
a metaclass).
Take, for example, the class Point.
This class must be an instance of a (meta)class
(say Class) which in its turn must be an instance of a (meta)
class (say MetaClass),
and so on.
Clearly, following the instance relation
leads to an infinite regress.
Hence, we must postulate some
system-defined MetaClass (at a certain level)
from which to instantiate the (metaclasses of)
actual classes such as Point.
A concise treatment of programming languages
is given in
function object_display(msg) { // object method
return msg + ' (' + this.variable++ + ')';
}
function object() { // object constructor
this.variable=0;
this.display = object_display;
return this;
}
var a = new object(); // create object
document.write(a.display("a message"));
document.write(a.display("another message"));
</script>
Comparing Smalltalk, Eiffel, C++ and Java
subsections:
The languages Smalltalk, Eiffel, C++ and Java
may be regarded as the four most important
(and popular) representatives of classical
object-oriented languages, classical in the sense
of being based on a class/object distinction.
Criteria for comparison
Criteria for comparison
When choosing a particular programming language
as a vehicle for program development a number
of factors play a role, among which are the availability
of a class library, the existence of a good programming
environment, and, naturally,
the characteristics of the language itself.
Class libraries
An important criterion when selecting a language
may be the availability of sufficient class library support.
A general class library, and preferably libraries
suitable for the application domain one is working in,
may drastically reduce development time.
Another important benefit of using (well-tested)
class libraries is an improvement of the reliability of the
application.
Programming environments
Another selection criterion in choosing a language is
the existence of a good programming environment.
What constitutes a good programming environment
is not as simple as it may seem,
since that depends to a large extent upon the experience
and preferences of the user.
For example, with respect to operating systems,
many novice users favor a graphical interface as
originally offered by the Apple Macintosh computers,
while experienced users often feel constrained
by the limitations imposed by such systems.
In contrast, experienced users may delight in the
terseness and flexibility of the command-based
Unix operating system, which leads
to outright bewilderment with many novice users.
Language characteristics
Despite the commonality between Smalltalk, Eiffel, C++ and Java
(which may be characterized by saying that they all support
data abstraction, inheritance, polymorphism
and dynamic binding), these languages widely
disagree on a number of other properties,
such as those listed in slide 5-characteristics.
Language characteristics
Language characteristics
Smalltalk, Eiffel, C++ and Java differ with respect to
a number of language characteristics.
An indication of the differences between these
languages is given slide 5-language-characteristics.
This characterization conforms to the one given in
Uniformity
In Smalltalk, each data type is described by a class.
This includes booleans, integers, real numbers and control
constructs.
In Eiffel there is a distinction between elementary
data types (such as boolean, integer and real)
and (user-defined) classes.
However (in the later versions of Eiffel)
the built-in elementary types behave as if declared
by pre-declared classes.
For C++, the elementary data types and simple data structures
(as may be defined in C) do not behave as objects.
To a certain extent, however,
programmers may deal with this non-uniformity by some
work-around, for example by overloading functions
and operators or by embedding built-in types in a (wrapper) class.
Java may be regarded as a simplified version of C++.
Due to its restrictions, such as the absence of
operator overloading and type casts,
the language appears to be more uniform
for the programmers.
Documentation value
Smalltalk promotes a consistent style in writing programs,
due to the assumption that everything is an object.
One of perhaps the most important features
of Eiffel is the use of special keywords for
constructs to specify the correctness of programs
and the behavioral properties that determine the external
interface of objects.
Moreover, Eiffel provides a tool to extract a description
of the interface of the method classes
(including pre- and post-conditions associated with a method)
which may be used to document (a library of) classes.
To my taste, however, the Eiffel syntax leads to
somewhat verbose programs, at least in comparison
with programs written in C++.
Reliability
Smalltalk is a dynamically typed
language.
In other words, type checking, other than detecting runtime errors,
is completely absent.
Eiffel is generally regarded as a language possessing
all characteristics needed for writing reliable programs,
such as static type checking and constructs for stating
correctness assertions (which may be checked at runtime).
Due to its heritage from C, the
language C++ is still considered by many as unreliable.
In contrast to C, however, C++ does
provide full static type checking, including the signature
of functions and external object interfaces
as arise in independent compilation of module files.
Nevertheless, C++ only weakly supports type checking
across module boundaries.
Contrary to common
belief, Eiffel's type system is demonstrably inconsistent,
due to a feature that enables a user to dynamically define the
type of a newly created object in a virtual way
(see section self-reference).
This does not necessarily lead to type-insecure
programs though, since the Eiffel compiler
employs a special algorithm to detect such cases.
The type system of C++, on the other hand, is consistent
and conforms to the notion of subtype as introduced
informally in the previous part.
Nevertheless, C++ allows
the programmer to escape the rigor of the
type system by employing casts.
Inheritance
Smalltalk offers only single inheritance.
In contrast, both Eiffel and C++ offer multiple inheritance.
For statically typed languages, compile-time
optimizations may be applied that result in only
a low overhead.
In principle, multiple inheritance allows one to model
particular aspects of the application domain in a flexible
and natural way.
Efficiency
Smalltalk, being an interpreted language, is typically
slower than conventionally compiled languages.
Nevertheless, as discussed in section
self-implementation, interpreted object-based
languages allow for significant optimizations,
for example by employing runtime compilation techniques.
Language complexity
Smalltalk may be regarded as having a low language complexity.
Control is primarily effected by message passing,
yet, many of the familiar conditional and iterative
control constructs reappear in Smalltalk programs
emulated by sending messages.
This certainly has some elegance, but does not necessarily
lead to easily comprehensible programs.
Design dimensions of object-oriented languages
subsections:
Despites the widespread adoption of object-oriented terminology in
the various areas of computer science and software
development practice, there is considerable confusion about
the precise meaning of the terms employed and the (true)
nature of object-oriented computing.
In an attempt to resolve this confusion, Object-oriented language design
data abstraction -- state accessible by operations
strong typing -- compile time checking
Object-based versus object-oriented
\label{object-based}
How would you characterize Ada83? See Classes versus types
Another confusion that frequently arises
is due to the ill-defined relationship between
the notion of a class and the notion of a type.
The notion of types is already familiar
from procedural programming languages such as
Pascal and (in an ill-famed way) from C.
The type of variables and functions may be profitably
used to check for (syntactical) errors.
Strong static type checking may prevent errors
ranging from simple typos to using undefined
(or wrongly defined) functions.
The notion of a class originally has a more
operational meaning.
Operationally, a class is a template for object creation.
In other words, a class is a description of the collection
of its instances, that is the objects that are
created using the class description as a recipe.
Related to this notion of a class, inheritance was
originally defined as a means to share (parts of)
a description.
Sharing by (inheritance) derivation is, pragmatically,
very convenient.
It provides a more controlled way of code sharing
than, for example, the use of macros and file inclusion
(as were popular in the C community).
Since Towards an orthogonal approach -- type extensions
According to Orthogonal approach
Objects
are in essence modular computing agents.
They correspond to the need for encapsulation in design,
that is the construction of modular units to
which a principle of locality applies (due to
combining data and operations).
Object-oriented languages may, however, differ in
the degree to which they support encapsulation.
For example, in a distributed environment a
high degree of encapsulation must be offered,
prohibiting attempts to alter global variables
(from within an object) or local instance
variables (from without).
Moreover, the runtime object support system must
allow for what may best be called remote method invocation.
As far as parallel activity is concerned, only a few languages provide
constructs to define concurrently active objects.
See section Active for a more detailed discussion.
Whether objects support reactiveness,
that is sufficient flexibility to respond safely
to a message, depends largely upon (program) design.
Types
may be understood as a mechanism for expression classification.
From this perspective, Smalltalk may be regarded
as having a dynamic typing system:
dynamic, in the sense that the inability to evaluate an expression
will lead to a runtime error.
The existence of types obviates the need to have classes,
since a type may be considered as a more abstract description of the
behavior of an object.
Furthermore, subclasses (as may be derived through inheritance)
are more safely defined as subtypes in a polymorphic
type system. See section flavors.
At the opposite side of the type dimension we find the statically typed languages,
which allow us to determine the type of
the designation of a variable at compile-time.
In that case, the runtime support system need not carry any type information,
except a dispatch table to locate virtual functions.
Delegation
(in its most generic sense)
is a mechanism for resource sharing.
As has been shown in Abstraction
(although to some extent related to types)
is a mechanism that may be independently applied to provide
an interface specification for an object.
For example, in the presence of active objects
(that may execute in parallel)
we may need to be able to restrict dynamically the interface of an object as specified by its type
in order to maintain the object in a consistent state.
Also for purely sequential objects we may impose a particular protocol of interaction
(as may, for example, be expressed by a contract) to be able to
guarantee correct behavior.
Another important aspect of abstraction is protection.
Object-oriented languages may provide (at least) two kinds
of protection.
First, a language may have facilities to
protect the object from illegal access by a client (from without).
This is effected by annotations such as private and protected.
And secondly, a language may have facilities to protect the object
(as it were from within) from illegal access through delegation
(that is by instances of derived object classes).
Most languages support the first kind of protection.
Only few languages, among which are C++ and Java, support the second kind too.
The independence of abstraction and typing may further
be argued by pointing out that languages
supporting strong typing need not
enforce the use of abstract data types having a well-defined
behavior.
Multi-paradigm languages -- logic
Object-oriented programming has evolved as a new and
strong paradigm of programming.
Has it?
Of the languages mentioned, only Smalltalk
has what may be considered a radically new language design
(and to some extent also the language Self, that we will
discuss in the next section).
Most of the other languages, including Eiffel, C++
(and for that matter also CLOS and Oberon),
may be considered as object-oriented extensions of already
existing languages or, to put it more broadly, language
paradigms.
Most popular are, evidently, object-oriented extensions
based on procedural language paradigms,
closely followed by the (Lisp-based) extensions of the
functional language paradigm.
Less well-known are extensions based on the logic programming
paradigm, of which DLP is my favorite example.
In Open systems
Dimensions of modularity
Object-oriented logic programming
Logic programming is often characterized as relational
programming, since it allows the exhaustive
exploration of a search space defined by logical relations
(for instance, by backtracking as in Prolog).
The advantage of logic programming, from a modeling point of view,
is that it allows us to specify in a logical manner
(that is by logical clauses) the relations between
the entities of a particular domain.
A number of efforts to combine logic programming with
object-oriented features have been undertaken,
among which is the development of the language Vulcan.
Vulcan is based on the Concurrent Prolog language
and relies on a way of implementing objects as
perpetual processes.
Without going into detail, the idea (originally proposed in
Active objects -- synchronous Java/C++
When it comes to combining objects
(the building blocks in an object-oriented approach)
with processes (the building blocks in
parallel computing),
there are three approaches conceivable.
See slide 6-o-conc.
Object-based concurrency
Processes
The first, most straightforward approach,
is to simply add processes
as a primitive data type, allowing the
creation of independent threads of processing.
An example is Distributed Smalltalk (see Bennett, 1987).
Another example is Java, which provides support for
threads, synchronized methods and statements like wait
and notify to protect re-entrant concurrent methods.
The disadvantage of this approach,
however,
is that the programmer has full responsibility
for the most difficult part of parallel programming,
namely the synchronization between processes and the
avoidance of common errors
such as simultaneously assigning a value to a shared
variable.
Despite the fact that the literature, see Active objects
A second, and in my view preferable, approach
is to introduce explicitly a notion of active objects.
Within this approach, parallelism is introduced by having
multiple, simultaneously active objects.
An example of a language supporting active objects
is POOL, described in Asynchronous communication
Deadlock may come about by synchronous (indirect)
self-invocation.
An immediate solution to this problem is provided
by languages supporting asynchronous communication,
which provide message buffers allowing the caller
to proceed without waiting for an answer.
Asynchronous message passing, however,
radically deviates from the (synchronous) message passing
supported by the traditional (passive) object
model.
This has the following consequences.
First, for the programmer, it becomes impossible
to know when a message will be dealt with
and, consequently, when to expect an answer.
Secondly, for the language implementor,
allocating resources for storing
incoming messages and deciding when to deal
with messages waiting in a message buffer
becomes a responsibility for which it is hard to
find a general, yet efficient, solution.
Active objects with asynchronous message passing
constitute the so-called actor model,
which has
influenced several
language
designs. See Synchronous C++/Java
Prototypes -- delegation versus inheritance
subsections:
The classical object model
(which is constituted by classes, objects and inheritance)
not only has its theoretical weaknesses (as outlined
in the previous section)
but has also been criticized from a more pragmatic
perspective because of its inflexibility
when it comes to developing systems.
Code sharing has been mentioned as one of
the advantages of inheritance (as it allows incremental
development).
However, alternative (read more flexible) forms of
sharing have been proposed, employing
prototypes and delegation instead of inheritance.
Alternative forms of sharing
A class provides a generic description
of one or more objects, its instances.
From a formal point of view, classes are related to types,
and hence a class may be said to correspond to
the set of instances that may be generated from it.
This viewpoint leads to some anomalies,
as in the case of abstract
classes that at best correspond to partially defined sets.
As another problem, in the context of inheritance,
behavioral compatibility may be hard to arrive at,
and hence the notion of subtype
(which roughly corresponds with the subset relation)
may be too restrictive.
In practice, we may further encounter
one-of-a-kind objects, for which it is simply cumbersome
to construct an independent class.
In a by now classical paper, Prototypes -- exemplars
Design issues
State
Creation
Delegation
Implementation techniques -- Self
A major concern of software developers
is (often) the runtime efficiency of the
system developed.
An order of magnitude difference in execution speed
may, indeed, mean the difference between acceptance and rejection.
Improving performance
Dynamic compilation
The language Self
is quite pure and simple in design.
It supports objects with slots
(that may contain both values and code, representing methods),
shallow cloning,
and implicit delegation (via a designated parent slot).
Moreover, the developers of Self have introduced a number
of techniques to improve the efficiency
of prototype-based computing.
Self -- prototypes
Dynamic compilation -- type information
Meta-level architectures
Another weakness of the classical object model
(or perhaps one of its strengths)
is that the concept of a class easily
lends itself to being overloaded with additional
meanings and features such as class variables
and metaclasses.
These notions lead to extensions to
the original class/instance scheme
that are hard to unify in a single elegant
framework.
In this section
we will study a proposal based on a reflexive
relation between classes and objects.
Depending on one's perspective, a class may either
be regarded as a kind of abstract data type
(specifying the operational interface of its object instances)
or, more pragmatically, as a template for object creation
(that is, a means to generate new instances).
The class concept
Class variables
Clearly, the use of class variables
violates what we have called the distribution boundary
in section multi-paradigm,
since it allows objects to reach out of their
encapsulation borders.
Class variables may also be employed in C++ and Java
by defining data members as static.
Apart from class variables, Smalltalk also supports
the notion of class methods,
which may be regarded as routines having the class
and its instances as their scope.
Class methods in Smalltalk are typically used
for the creation and initialization of new instances of the class
for which they are defined.
In C++ and Java, creation and initialization is taken care
of by the constructor(s) of a class,
together with the (system supplied) new
operator.
Class methods, in C++ and Java, take the form
of static member functions that are like
ordinary functions
(apart from their restricted scope and their calling syntax,
which is of the form in C++ and in Java).
Contrary to classes in C++ and Java,
classes in Smalltalk have a functionality
similar to that of objects.
Classes in Smalltalk provide
encapsulation (encompassing class variables and
class methods) and message passing
(for example for the creation and initialization
of new instances).
To account for this object-like behavior,
the designers of Smalltalk have introduced the notion
of metaclass of which a class is an instance.
Metaclasses
In the classical object model, two relations
play a role when describing the
architectural properties of a system.
The first relation is the instance relation
to indicate that an object O is an instance of a class
C.
The second (equally important) relation is
the inheritance relation,
which indicates that a class C is a subclass
(or derived from) a given (ancestor) class P.
When adopting the philosophy
everything is an object
together with the idea that
each object is an instance of a class
(as the developers of Smalltalk did),
we evidently get into problems when we try to explain
the nature (and existence) of a class.
Reflection
Postulates -- class-based languages
Reflective definition of Class
Summary
The object paradigm
Comparing Smalltalk, Eiffel, C++ and Java
Design dimensions of object-oriented languages
Prototypes -- delegation versus inheritance
Meta-level architectures
Questions
Further reading
Many applications, for example in the area of telecomputing, banking and multimedia (but also in high performance computing and operating systems), require support for distribution and concurrency. Due to their complexity, these applications are likely candidates for an object-oriented approach. However, with regard to their distributed nature, some marriage between object-oriented computing and distributed computing must be realized.
Additional keywords and phrases:
(D)COM, Java, CORBA, OLE, persistent objects, ODMG, workgroup
Let us look at the definition of software component
given in
A software component is a unit of composition
with contractually specified interfaces and
explicit context dependencies only.
A software component can be deployed independently
and is subject to composition by third parties.
This definition was the result of an ECOOP96 Workshop on Component-oriented Computing. Notice that the definition itself contains a reusability requirement, in mentioning composition by third parties. The requirements of explicit context dependencies, the absence of inherent state and contractually specified interfaces only strengthen this.
In contrast, an object is not a unit of deployment,
but a unit of instantiation.
Objects are not used in isolation.
Objects, do have state and identity.
Deploying an object or a collection of objects, moreover,
is often subject to (implicit) assumptions
concerning the interaction of objects.
Components as better objects
Reconsidering the definitions given,
I tend to think of the distinction between
components and objects as a distinction between
perspectives.
From a deployment perspective we need components.
From a developer's perspective we might prefer to speak
about objects.
Unfortunately, matters are not that easy.
But we need to take a closer look at the technology
to find out why.
The technology matrix
The component technology field is currently dominated
by three players:
Microsoft (D)COM,
OMG CORBA,
and (the youngest player)
Sun Microsystems Java.
When comparing these technologies with respect to attributes
such as
distribution,
mobility,
language and platform independence, and
reflective capabilities,
we see that there are many differences.
First of all, notice that component technology does
not automatically mean distribution.
For example, JavaBeans and Microsoft COM do not support
distribution.
Secondly, whereas language independence seemed to be
of importance in the pre-Java era, that is for (D)COM and CORBA,
it is not so for the Java-based solutions.
Finally, platform independence is hard to achieve.
But, fortunately, it is on the agenda of all three
technologies, including (D)COM.
It is worth mentioning that the three major technologies have a rather different origin. Microsoft (D)COM is primarily a desktop technology, with Office as its killer application, whereas CORBA originated from the need to have an enterprise-wide solution for distributed objects. Java is a special case. It started as a Web-based language, but rapidly took position in the desktop and enterprise world as well.
Java distinguishes itself from the other technologies
both with respect to mobility and reflection.
As a Web-based language, Java allows for downloading
code dynamically, that is class descriptions for instantiating new
objects.
True mobile objects,
that is instantiated objects that migrate themselves,
are only possible when using a system such as Voyager,
or any of the other Java-based agent ORBs.
Java also provides a powerful Reflection API,
which allows for various kinds of meta-programming,
including the creation of new classes.
In comparison, meta-programming facilities of
the two other technologies are limited
to querying the availability and functionality of interfaces,
dynamic method invocation
and some dynamic typing.
Trends -- interoperability
An interesting project in this respect is the
K-Office project,
which aims at developing an Office Application Suite for the
Unix/X11 desktop.
It is built upon the KDE GUI environment,
and employs a CORBA-based component technology,
(nick)named KOM, to interconnect
(embed and link) the various document components
and their associated tools. See http://koffice.kde.com .
if semantical issues can be resolved
development becomes more complex
it affects performance significantly
wrong, it is an evolution from OO and C/S
unknown source
And, as indicated in slide myths,
do not underestimate the complexities
of developing such applications.
Given the failure of many OO projects, as described in
Yet, in conclusion,
just as object orientation may be regarded as
a natural evolution from data-oriented approaches,
we may look at component-oriented approaches
as a natural evolution from object orientation into
the realm of distributed systems.
The object-linking technology allows
the user to maintain a link from one application
to another,
so that for example a text processor may
directly employ the results of a spreadsheet.
Moreover, object linking is dynamic and allows
any updates in the spreadsheet application
to be reflected
directly in the outcome of the text processor.
In contrast, object embedding works more as
the traditional cut and paste techniques
in that it results in including only a copy
of the material.
To be embedded or linked,
applications must satisfy a standard programmatic interface.
In effect, the interface must provide the facility
to request an update of the display
of the information contained in the application.
In this respect, the OLE technology may be characterized
as document-centered.
In this section, we will look at the Java platform from the
perspectives listed in slide Perspectives.
The rhetoric in these reflections must not be misunderstood
as dismissing Java, but as a way to get hold
of the issues that play a role in adopting the Java technology,
or any of the competing technologies, for business-critical
applications.
Now back to the reality of software development.
Many Internet applications are written in script languages.
Perl for server-side scripting and
Javascript on the client-side.
Given the efficiency problems of Java
(which are a consequence of the virtual machine approach)
and the long download times for applets,
Java runs the risk of being too light for
heavy-duty servers and too heavy for lightweight clients.
In terms of research, what Java offers is not really new.
Personally, I am somewhat disappointed
that the attention is focused on another third-generation
language,
whereas to my mind, we should have a declarative programming
technology on our research agendas,
a technology that supports the development of reliable and
verifiable software.
Nevertheless, Java appears to be an excellent platform for
Web-related research.
An example of a business application framework
is the IBM San Francisco framework,
discussed in
section San Francisco,
which offers generic solutions for the realization
of business processes, such as order management and quality control.
Nevertheless, crucial issues in this area do not seem
to concern technology per se,
but rather the standardization of such notions
as business objects, business processes and, of course,
business logic.
This is also an area of active OMG interest.
The object model guiding the implementation
is depicted in \sliref{object-model}.
The most important notion in our system is that of an agent,
which is defined as a representative of a user which can perform
simple operations on the user's behalf, such as sending messages
to and making appointments with other agents.
Each user participating is represented by a personal
agent, which has an agenda and a message queue. Each agenda consists
of several appointments. A workgroup has a membership list consisting
of zero or more agents present in the system. An agent can be
a member of zero or more workgroups.
Possible operations on agents are sending messages
to and making appointments with other agents, subscribing to a
workgroup, making an appointment with a workgroup or sending a
message to all workgroup members.
All interaction of the user with the system is done
via Java applets, which combine a graphical user interface with
Internet access and CORBA functionality. The server is implemented
in C++. We used Visigenic's (formerly PostModern Computing)
CORBA 2.0-compliant product ORBeline
to implement the server and BlackWidow from the same vendor to
implement the Java clients. The server can run on most UNIX platforms,
as well as on Windows NT, while the Java applets, running on top
of a Java Virtual Machine, can theoretically run on every platform
that Java supports. In addition, a C++ browser/monitor was developed
that can be used to interrogate agents and workgroups and to monitor
message flows.
The Java client applets are first transferred to
the client by the HTTP server using a web page and then contact
the object server through IIOP, the Internet Inter-ORB Protocol.
The client applets run in Netscape or HotJava and contain the
CORBA client communications software as well as the client application
code. The object server can reside on a different machine than
the Web server and is running an IIOP gatekeeper, for instance
on {\sc tcp} port 15000, that is part of the ORB. All further communication
is done through this gatekeeper.
The most important problem we faced was the distinction
between clients and servers, where distributed objects can only
reside on a server. If a client wants to make its objects available
to other programs, it should be configured as a server as well,
with an ORB running on the same host. This causes problems regarding
overhead and licensing and is not a feasible situation for the
distribution of (Java) clients over the Internet.
The same architecture limitation prevents servers from notifying
clients using callbacks.
This feature makes it impossible for clients to pass
distributed object references back to the server, forcing them
to work with other identification mechanisms such as object IDs
or human-readable strings.
Other minor problems we observed concern the lack
of support for existing non-CORBA objects in a distributed environment.
Possible solutions are converting existing classes to CORBA and
making their interface available through IDL, or to write CORBA
object wrappers around these objects.
Finally, not all parts of a CORBA system are compatible among
ORBs at the source-code level.
This problem should, however,
be alleviated with the Portable Object
Adapter (POA).
For giving access to objects within a particular hush
component, we have provided dots (distributed object tables)
for both hush and the widgets components.
Using the dot the client can access an object of a given type by
the name it is given by the server.
The object must already exist in the server.
In case clients are allowed to create objects within
the server, a factory is provided for creating hush
or widget objects.
To allow clients the use of CORBA IDL classes wherever one
of the original hush classes is expected,
client-side adaptors have been provided for each of the hush
or widgets IDL classes.
An additional advantage of client-side adaptors is that
they allow for overcoming the `weaknesses' of IDL with respect to
overloading member functions,
parametrized types and operator definitions.
Typically, client-side adaptors have their corresponding hush class
as a base class and simply delegate method invocations
to the CORBA object they encapsulate.
Nevertheless, in our opinion events should be used in a very restricted manner.
Events tend to break the `crisp' object interfaces that
are one of the benefits of an object-oriented
approach to design.
For the hush CORBA extensions, we have chosen for retaining the original hush
object interfaces.
Note however that the IDL interfaces are somewhat more
abstract than the corresponding C++ interfaces.
Nevertheless, the event interface is part
of the hush module.
Together with the dispatch function of
the handler interface incoming events resulting from user
actions may be dispatched directly to
remote components.
This work
shows how to integrate CORBA functionality
with an already existing framework.
In particular the need for client-side adaptors
for resolving the type clash between the `native' classes
and the CORBA IDL classes has been amply demonstrated.
Enriching hush with CORBA makes crush
a potential competitor of Fresco, the CORBA based GUI toolkit
derived from the Interviews library.
I recommend Component myths
Component software engineering may be characterized
as an approach that relies on the availability of
reusable `off-the-shelf' components that may be composed
into applications.
This includes applications for banking,
medical services,
corporate management,
entertainment,
etcetera.
Components: myths and reality
Software engineering perspectives
In addition, we need to deal with the practical aspects
of developing component-oriented applications,
that is master the distributed (object) technology involved,
and manage multi-tier architectures.
Standards for interoperability
subsections:
The potential of an object-oriented approach, obviously,
lies in the opportunities for reuse,
both of code and design.
However, reuse requires a common understanding of
the basic principles underlying the technology and its application.
More particularly, the reuse of code requires
(a much more strict)
agreement with respect to the components from which
an application will be constructed and the
language constructs used to implement them.
In this section, we will look at
the object linking and embedding facilities
offered by Microsoft OLE,
and the standardization efforts
undertaken by the OMG (Object Management Group)
directed towards the interoperability of object components.
In addition, we will look at the efforts of the
ODMG (Object Database Management Group)
undertaken to develop a standard for persistent objects.
Object linking and embedding -- COM
Reuse is not necessarily code sharing.
In effect, there seems to be a trend
towards sharing components at a higher level
of granularity, as possibly independent applications.
This approach has, for example, been taken by the
Microsoft object linking and embedding
facility (OLE), which offers support for
embedding (a copy) of a component in a (container)
component, for including a link to another component,
and for storing compound objects.
See slide 11-ole.
Object-enabling technology
Features
Alternatives
Object Request Brokers -- CORBA
The ultimate goal of object technology
may be phrased as the development of
plug-compatible software that allows one
to construct a particular application
from off-the-shelf components.
To achieve this goal, it is necessary to
develop standards with respect to object
interaction and communication interfaces
that support information sharing
between distinct components.
Such standards are developed by
the OMG (the Object Management Group, in
which the leading vendors of software systems
participate, including Digital Equipment Corporation,
Hewlett-Packard Company, HyperDesk Corporation, NCR Corporation,
Object Design Inc. and Sunsoft Inc.).
The OMG aims at defining standards for information
sharing in widely distributed, heterogeneous
(multi-vendor) networks
to support the reusability and portability of
commercially available components,
and more generally, to develop the technology and
guidelines that allow the interoperability of applications.
See slide 11-standards.
Standardization --
Object Management Architecture -- interface standards
Object Services
Future
Persistent objects -- ODMG
In a similar vein as the OMG,
a number of vendors of object database management
systems (including SunSoft,
Object Design, Ontos, Technology, Versant,
Objectivity, Hewlett Packard, POET Software,
Itasca, Intellitic, Digital Equipment Corporation,
Servio, Texas Instruments)
have participated in the ODMG
(Object Database Management systems Group)
to develop a standard for the definition and manipulation
of persistent objects.
The standards proposal of the ODMG encompasses
an object definition language ODL,
which is intended as an extension of the OMG/IDL
standard,
an object manipulation language, OML
and an object query language, OQL,
that provides SQL-like facilities for
the retrieval of information.
The advantage of employing an object database
system over employing a relational database system
is that, in principle, the application programmer
may work within a unified type system,
encompassing both persistent and transient objects.
See slide 11-ODMG.
Persistent objects
Object Definition Language
Object Manipulation Language
Object Query Language
Design principles
Language binding
Discussion
C++ODL/OML binding -- future
Modifications to C++
Standardization efforts -- de facto market share
The Java platform -- technology innovation
Java is the newest wave of technology.
It offers a distributed object-oriented platform
for the development of Web-aware applications.
However, as with any wave of technology,
we must ask ourselves whether it really does provide
an answer to our questions.
And, in the line of Perspectives
Internet applications
Software engineering
Language design
System development
Computer science
IT (in) business
Global village
Discussion
An Internet-based workgroup application
The goal of the project described in this section
was to develop a prototype
for an Internet-based
workgroup architecture using CORBA technology.
CORBA provides a means for developing distributed
object applications,
while the Internet provides a standard and widely accessible network
infrastructure. Users should not need special client software
to use the system, other than a Java-enabled browser such as Netscape.
Discussion
Crush -- extending hush with CORBA
This section describes how the hush toolkit
has been extended with CORBA functionality.
The nickname for this effort was crush.
The major problem that arises when extending
a given toolkit or framework, such as hush,
with CORBA IDL interfaces and classes to
implement these interfaces is to provide for
a seamless integration of the already existing
code with the CORBA-based extensions.
In crush we have included facilities
for object creation and access, as well as client-side
adaptors for the hush CORBA objects,
to resolve the type clash between the original hush
class hierarchy and the hush CORBA object classes.
Extending a framework with CORBA
The legacy problem -- integrating CORBA
Object creation and access
Client-side adaptors
Events versus object method invocation
Interfaces
The hush module
event dispatch( in event data );
};
attribute long type;
attribute long x;
attribute long y;
};
void source(in string file);
void eval(in string command);
string result();
widget root();
};
string path();
void eval( in string cmd );
void configure( in string options );
void pack( in string options );
};
void move( in long x, in long y );
};
Iterators and lists
Object next();
};
long length();
Object first();
Object next();
Object current();
iterator walk();
};
Factories and distributed object tables
hush::kit kit(in string name);
hush::event event(in long type);
};
hush::kit kit(in string name);
hush::container container(in string name);
hush::iterator iterator(in string name);
hush::factory hush(in string name);
};
The widgets module
canvas create( in hush::widget anc, in string path );
hush::item circle( in long x, in long y, in long radius, in string options );
// other items ...
};
interface message : hush::widget {
message create( in hush::widget anc, in string path );
void text(in string txt);
};
interface factory : hush::factory {
widgets::canvas canvas(in string name, in string options);
widgets::message message(in string name, in string options);
};
interface dot : hush::dot {
widgets::canvas canvas(in string name);
widgets::message message(in string name);
widgets::factory widgets(in string name);
};
};
Examples
A remote interpreter kit
Evaluating logical queries
A remote canvas
public:
void plug(widgets::canvas* x) { draw = x; }
int operator()() {
hush::event* e = hush->event(_event->type());
cerr << "Getting event " << e->type() << endl;
e->x(_event->x()+10);
e->y(_event->y()+10);
hush::event::_duplicate(e); // CORBA 2.0
hush::event* res = draw->dispatch(e);
return canvas::operator()();
}
draw_clt(const widget* w, char* path ) : canvas(w,path) {
configure("-background white");
geometry(200,100);
self()->bind(this);
dragging = 0;
}
draw_clt(char* path ) : canvas(path) {
configure("-background white");
geometry(200,100);
self()->bind(this);
dragging = 0;
}
void press( event& ) { dragging = 1; }
void motion( event& e) {
if (dragging) {
self()->circle(e.x(),e.y(),2,"-fill black");
draw->circle(e.x(),e.y(),3,"-fill yellow");
}
}
void release( event& ) { dragging = 0; }
protected:
int dragging;
widgets::canvas* draw;
};
public:
draw_srv( const widget* w, char* path ) : canvas(w,path) {
geometry(200,100);
self()->bind(this);
dragging = 0;
}
void press( event& ) { dragging = 1; }
void motion( event& e) {
if (dragging) circle(e.x(),e.y(),10,"-fill black");
}
void release( event& ) { dragging = 0; }
protected:
int dragging;
};
Moving items
Discussion
Summary
Objects versus components
Standards for interoperability
The Java platform
An Internet-based workgroup application
Crush -- extending hush with CORBA
Questions
Further reading
To get an overall idea of the structure of a software system is intrinsically difficult. The notion of architecture has proven to be a powerful metaphor for describing the structure of a system, that is the components and their interrelations, in a sufficiently abstract way.
Additional keywords and phrases:
components,
information architecture,
multimedia information retrieval,
feature detection,
portability
A later definition, given in
An exhaustive list of definitions of
the notion of software architecture is given
at the Web site of the Software Engineering Institute (SEI),
of which the url is given in slide Elements.
At the time of writing, the most comprehensive book
concerning software architectures is
The software architecture of a program or computing system
is the structure of the system, which comprises
software components, the externally visible properties
of those components, and their interrelationships.
Note the stress on externally visible properties
here.
It is meant to express that both components and their
relations must be described at a suitable level of
abstraction.
Also note that the phrase relationships between components
may cover quite a lot.
For example, when considering the architecture of a
Web application,
issues such as communication protocols and
document standards must be considered as well.
In addition, the technological infrastructure,
elements of which are given in slide infrastructure,
must also be taken into account.
In the definition or rather collection of definitions, given
by the IEEE Architecture Working Group,
for the terms architect,
architectural description,
stakeholder and viewpoint,
utmost care is taken to suppress the phrase structure.
Instead, the notion of architecting is defined
as defining, maintaining, improving and
certifying proper implementation of an architecture,
and an architecture as a collection of views
relevant to the stakeholders of a system.
Explicit attention for the architecture of a system becomes
increasingly relevant as the complexity of the system grows.
As argued in (class hierarchies) (wrappers) (horizontal, vertical, metadata) (reference models, infrastructure, policies) (standards)
In business applications a distinction can be made
between horizontal components
(covering general functionality, such as GUI-aspects
and document interoperability),
vertical components
(covering domain-specific functionality for one
area of business, such as finance),
and meta-data,
representing the more volatile, knowledge-level
aspects of a system.
What are stored are not the actual multimedia objects
themselves, but structural descriptions of
these objects (including their location) that may be used for
retrieval.
The ACOI model is based on the assumption that
indexing an arbitrary multimedia object
is equivalent to deriving a grammatical structure
that provides a namespace to reason about the object
and to access its components.
However, there is an important difference with
ordinary parsing in that the lexical and grammatical items
corresponding to the components of the multimedia object
must be created dynamically by inspecting the actual object.
Moreover, in general, there is not a fixed sequence
of lexicals as in the case of natural or formal languages.
To allow for the dynamic creation of lexical
and grammatical items the ACOI framework supports both
black-box and white-box (feature)
detectors.
Black-box detectors are algorithms, usually
developed by a specialist in the media domain,
that extract properties from the media object
by some form of analysis.
White-box detectors, on the other hand,
are created by defining logical
or mathematical expressions over the grammar itself.
In this paper we will focus on black-box detectors only.
As an example, look at the (simple) feature grammar below,
specifying the structure of a hypothetical community.
A community has a name.
The actual purpose of this grammar is to select the persons
that belong to a particular community from the input,
which consists of names of potential community members.
Note that the grammar specifies three detectors.
These detectors correspond to functions that are invoked
when expanding the corresponding non-terminal in the grammar.
An example of a detector function is the
personDetector function partially specified below.
The companyDetector differs from the personDetector
in that it needs to inspect the complete parse tree
to see whether the (implicit) company predicate is satisfied.
When parsing succeeds and the company predicate is satisfied
a given input may result in a sequence
of updates of the underlying database, as illustrated below.
The overall architecture of the ACOI framework is depicted in slide acoi.
Taking a feature grammar specification, such as the simple
community grammar, as a point of reference, we see
that it is related to an actual feature detector
(possibly containing an embedded logic component)
that is invoked by the Feature Detector Engine (FDE)
when an appropriate media object is presented for indexing.
The feature grammar and its associated detector
further result in updating respectively the data schemas
and the actual information stored in the (Monet) database.
The Monet database, which underlies the ACOI framework,
is a customizable, high-performance, main-memory database
developed at the CWI and the University of Amsterdam, see
At the user end, a feature grammar is related to
a View, Query
and Report component,
that respectively allow for inspecting a feature grammar,
expressing a query, and delivering a response
to a query.
Some examples of these components are currently implemented as applets
in Java 1.1 with Swing, as described in
The grammar given below corresponds in an obvious way with
the structure depicted in slide midi-structure.
To extract relevant fragments of the melody we
use the melody detector, of which a partial listing is given below.
Parsing a given MIDI file, for example kortjakje.mid,
results in updating the Monet database.
The updates reflect the structure of
the musical information object that corresponds to
the properties defined in the grammar.
In this section we will look at some studies
(executed within the hush framework)
that exemplify a multi-paradigm and multi-lingual approach.
We will first look at the issues that arise when embedding
a logic (that is Prolog) interpreter.
Then we will extend the embedded logic with objects
that may correspond to (native) objects in the host language,
that is C++.
These sections may safely be skipped by readers
not interested in logic programming.
Finally, we will look at how to realize corresponding
collections of objects in (native) C++ and Java.
Traditionally, the information components are often
taken care of by a database that allows
for the formulation of views to obtain (possibly aggregate)
information.
Logic or logic programming is a strictly more powerful
mechanism to deal with information and knowledge.
In our group, we have been studying the use of logic programming
in knowledge-intensive software engineering applications.
The query tag is an element of one of the
text processing filters to provide
hypermedia
support for software engineering described
in
The query example was motivated by the need to maintain
Web pages for the administration of a colloquium
within our group.
The actual knowledge base consists of a list of people
and some rules to determine their affiliations
and email addresses.
The knowledge base is made available by
consulting the file
As concerns the implementation,
the Java fragment below indicates how to access the logic
programming interpreter from a (Java) program.
However, processing the information accessed by url
is still done locally.
So, the next step that may be suggested is to
distribute the knowledge processing itself,
for example by using CORBA.
Native bindings for these languages are available
only on the level of functions.
Even for Java, native methods of an object are defined as
functions that receive a handle to the invoking object.
Given a language with objects, possibly by
adopting an object extension for the languages
without objects,
the problem is to find a proper correspondence
between objects defined in the high-level (script)
language and the native objects defined in C/C++.
In this (sub)section we will first study an extension of Prolog
with objects, and then indicate
a solution to establish a close correspondence
between the (Prolog) objects and their native counterparts.
In the next (sub)section, we will apply this
approach to establish a correspondence between
Java and C++ objects.
Objects (or classes of objects, if you prefer)
are defined by a collection of clauses with a head predicate
of the form class_method(This,...),
specifying the class, method
and object identity parameter.
The actual invocation of the method takes
the form self(This):method(...), where
the colon acts as the familiar dot object access parameter.
Note that the identity parameter (This) does not
occur among the method parameters, but is instead contained
in the object specifier.
Instead of the keyword self,
we may also use a class name to enforce a cast to specific
object type when invoking the method.
In the actual object extension, we also support
object state instance variables, which are however not relevant
for our discussion here.
Object methods may be defined as native by including
a goal of the form native(Handler, Method, Result),
where Handler specifies the (native) handler
to be invoked, Method the actual request,
and Result a variable to store the possible
outcome of the request.
When the Handler parameter is left unspecified,
the handler defined for the object will be taken to
effect the native call.
Let's look at some examples first, to augment this
admittedly concise description.
As outlined in section Reactor, in the hush framework we use an event-based
mechanism to effect foreign language bindings.
This means that the information concerning the native call
is stored in an event object that is passed to a handler,
which invokes the operator function
on the occurrence of an event.
In the code fragment below it is shown how native method
dispatching is taken care of in the operator
function of a C++ kit_object, for which
a corresponding object in Prolog is assumed to exist.
The solution to establishing corresponding object class hierarchies
in Java and C++ that we have adopted relies on
storing a reference to the native C++ object in the Java object
and the conversion of this reference to a smart pointer
encapsulating access to the native C++ object.
Upcalls, which occur for example when Java handlers are invoked
in response to an event, require some additional machinery,
as will be explained shortly.
Each Java class in hush is derived from the obscure
class, which contains an instance variable _self
that may store a C++ object reference, encoded as an integer.
As an example, look at the (partial) Java class description for
kit below.
Each native method must be implemented as a function,
of which the name and signature are fixed by
the JNI conventions, as illustrated below.
In somewhat more detail,
the Java handler object is invoked through the
C++ handler object created in the bind method
of the kit.
The C++ handler is activated when an event occurs, or a Tcl
or Prolog command is given.
Activating the handler amounts to calling the dispatch
method with an appropriate event.
To decide whether the activation must be passed through
to the Java handler object,
the handler::dispatch
method checks for the availability of a smart pointer,
as illustrated below.
The Java smart pointer template class for the Java/C++
binding is derived from the smart pointer
template class introduced in the previous (sub)section.
Architectural choices lead to a particular decomposition
into components and a characterization of
the relation between components.
Classifying groups of software architectures,
we may speak of architectural styles,
which may be defined, following
In this section we will look at architectural styles
for distributed object systems.
Three styles will be introduced, and we will discuss
how these styles are related to technological constraints
imposed by particular component technologies.
Then we will investigate how these styles work out in practice,
by a simple case study in which we explore the consequences
of a particular style for the solution of a specific problem,
in our case the problem of dynamically changing a viewpoint
or perspective in an interactive visualization system.
The distributed objects style comprises software architectures
which consist of software components providing services to client
applications.
Each object is located at a single, fixed place.
Objects on different machines are connected by an ORB (Object Request Broker).
Example technologies supporting this architectural style
are CORBA and DCOM.
The second architectural style
is the (dynamically) downloadable code style.
Classes may be downloaded, to be used on client machines
for instantiating objects, which will run on the client machine.
Example technologies supporting this style are
Java applets, JavaBeans, and ActiveX controls.
Finally, in the mobile objects style,
objects may migrate from host to host, carrying both functionality
and data when they move.
Consequently, mobile objects may communicate
with the local objects of the host they currently reside on.
Mobile objects are a means to implement agents
which wander through a network,
collecting information, negotiating with other agents,
periodically reporting back results to the user who launched the agents.
Technologies supporting the mobile object style
are agent ORBs such as Voyager.
We may regard the location issues as the
prime discriminators of the architectural styles discussed.
Adopting the distributed objects architectural style,
new objects can be added at the server-side, where
they will stay for the remainder of their lifetime.
In contrast, adopting the downloadable code style,
objects may be created at the client-side,
from classes obtained from the server.
Most flexible is the mobile objects style,
which allows for objects to reside on either
server or client machines.
The location properties directly affect
the way that the system is extensible
with new functionality.
Clearly, the mobile code style offers the maximum
of flexibility and functional extensibility.
Nevertheless, as we will discuss shortly,
there are tradeoffs involved.
The maximum in flexibility and extensibility does not
necessarily offer the optimal solution!
Nevertheless, for other parts of the system we were
forced to choose a different solution.
For example, since we use a C++ simulation library for obtaining
the information, we had to use distributed objects (read CORBA)
for making the information available.
And for developing control applets, agent technology
seemed to be a bit of an overkill
so we restricted ourselves to plain Java technology,
that is the downloadable code style.
Generalizing, from our experience we can formulate
the following rules of thumb, listed in slide Guidelines.
distributed objects distributed objects downloadable code downloadable code mobile objects
When a large amount of clients is running
an application on a server,
the server can easily become overloaded.
In this case, moving the processing to the client,
by deploying dynamically downloadable classes,
is a natural solution.
Additionally, when (parts of) an application
are updated often, for example because of changing legislation,
architectures based on downloadable code
are much easier to keep up-to-date.
Clients are then automatically using the latest version
of the available software.
The latter guidelines hold for the mobile objects style
as well.
However, agent technology is much more complex.
And there is, generally, an efficiency price to pay.
So, it is reasonable to introduce agent technology
only when real benefits can be expected from the
migration of objects, for example when the communication
and negotiation with local objects is substantial.
Concluding,
we may state that the adoption of
a style will often be dictated by the technological constraints
a system must satisfy.
Nevertheless, a word of warning is in place here.
Choosing a style may well have consequences for
the overall complexity of the system.
Minimalism is to be strived for, in this respect.
For example, adopting the mobile object style,
that is the use of agents,
may significantly complicate the semantics of the system,
and consequently induce
an increased verification and validation effort.
Unix vs NT
Considering the opportunities for platform-independent
or cross-platform development, we may distinguish between
three approaches:
When it comes to porting applications from Unix to
Windows 95/98/NT,
we may look at AT&T U/WIN, which provides a POSIX
extension for Win32, or Cygnus GNU-win32 support,
which offers many of the GNU utilities and libraries
for the Windows platform.
Similar functionality, as well as support for Motif/X11
GUI capabilities, is offered by the (commercial)
NuTCracker environment.
(A detailed discussion of the technical merits of the
various offerings is beyond the scope of this book.
However, the interested reader may find more information in the online
version of this book.)
The Windows platform is not only popular with end-users
but also with many developers,
who enjoy using the Microsoft Visual Studio suite of tools
and (object-oriented) frameworks such as MFC.
Recently, toolkits have entered the market
that allow for porting Microsoft technologies
(including Visual Basic, ActiveX and MFC applications)
to the Unix platform,
in particular Wind/U from bristol.com
and Mainwin from mainsoft.com.
As a word of warning, these toolkits are still terribly
expensive.
Yet for more information, consult the online version of this book.
For those who wish to develop directly on the Unix platform,
but using Microsoft Visual Studio, there is Tributary,
from bristol.com,
which offers a Unix-server and client-extensions to
Visual Studio.
An excellent book on software architectures is Elements of architecture
Models and views
Definitions
Technological infrastructure
Distributed object architectures
When considering the architecture of a system,
invariably the technological infrastructure
plays a role.
In particular, when considering client/server
or distributed object systems the choice for
respectively a particular
client and server platform, middleware and
communication infrastructure
may to a large extent determine the characteristics
of the software architecture.
Distributed object patterns
Case study -- multimedia feature detection
In this section, we will look at the indexing and retrieval
of musical fragments.
This study is primarily aimed at establishing the
architectural requirements for the detection of musical features
and to indicate directions for exploring the
inherently difficult problem of finding proper discriminating
features and similarity measures in the musical domain.
In this study we have limited ourselves to the analysis
of music encoded in MIDI, to avoid the technical difficulties
involved in extracting basic musical properties
from raw sound material.
Currently we have a simple running prototype for
extracting higher level features from MIDI files.
In our approach to musical feature detection,
we extended the basic grammar-based ACOI framework
with an embedded logic component to facilitate
the formulation of predicates and constraints over
the musical structure obtained from the input.
The ACOI framework
put name(person) on tokenstream
putAtom(tks,"name",t);
}
...
}
Formal specification
The anatomy of a MIDI feature detector
Implementation status
Queries -- the user interface
Crossing boundaries
subsections:
It is futile to hope for a single language or paradigm
to solve all problems.
Therefore, as our small case study concerning
multimedia feature extraction indicates,
components may differ in how they are realized.
Some components are better implemented using
knowledge-based systems technology,
whereas other components require the use of a systems programming
language such as C++.
Even within components it may be necessary to transgress
the language boundary. For example in Java applications,
wrapping legacy applications or operating system-dependent
code is usually done using the native language interface.
Embedded logic -- crossing the paradigm boundary
Knowledge is a substantial ingredient in many applications.
By knowledge we mean information and rules
operating on that information, to obtain derived information.
As in any (software) engineering effort, maintenance,
that is knowledge maintenance, is of crucial importance.
When we do not avoid the dispersion of knowledge and information
in the actual code of the system, maintenance will
be difficult.
Put differently, for reasons of flexibility and maintenance
we need to factor out the (volatile) knowledge and information
components.
pl.eval("X:assistant(X)");
String res = null;
while ( (res = pl.result()) != null ) {
System.out.println(" Distributed knowledge servers
:- source('www.cs.vu.nl/~eliens/db/se/people.pl').
:- source('www.cs.vu.nl/~eliens/db/se/institute.pl').
:- source('www.cs.vu.nl/~eliens/db/se/property.pl').
:- source('www.cs.vu.nl/~eliens/db/se/query.pl').
void source(in string file);
long eval(in string cmd);
string result(in long id);
oneway void halt();
};
Native objects -- crossing the language boundary
Embedding (script) language interpreters is becoming
standard practice, as testified by the existence
of embeddable interpreters for Tcl,
Perl, Python, Javascript, Java, and Prolog.
Each of these languages also supports calling native
code, that is code written in C or C++,
to allow for accessing system resources or simply
for reasons of efficiency.
Objects in Prolog
In our solution, objects are represented by dynamic
fact clauses, containing a Handler,
indicating how native calls are to be dealt with,
a Class, and object identity ID,
possibly a reference REF to a native C/C++ object,
and a list of Ancestors.
:- use(library(midi:[midi,lily,music,process])).
:- declare(midi:object,class(midi),[handler]).
midi_midi(This) :- // constructor
midi(This):handler(H), // gets Handler from class
declare(H,new(midi(This)),[],[],_).
native methods
midi_read(This,F) :- native(_,This,read(F),_).
midi_analyse(This,I,O) :- native(_,This,analyse(I,O),_).
midi_open(This,F) :- native(_,This,open(F),_).
midi_header(This,M) :- native(_,This,header(M,0,480),_).
midi_track(This,X) :- native(_,This,track(X),_).
midi_tempo(This,X) :- native(_,This,tempo(X),_).
midi_event(This,D,C,M,T,V) :-
native(_,This,event(D,C,M,T,V),_).
C++ bindings
public:
vm(event* e) {
int p = 0;
char* id = e->option("ref");
if (id) {
p = atoi(id);
}
_self = (T*) p;
}
virtual inline T* operator->() { return _self; }
private:
T* _self;
};
Combining Java and C++
The designers of the Java language have created an elegant
facility for incorporating native C/C++ code
in Java applications, the Java Native Interface (JNI).
Elegant, since native methods can be mixed freely
with ordinary methods.
When qualifying methods as native,
the implementer must provide a dynamically loadable library
that contains functions, of which the names and signatures
must comply with the JNI standard,
defining the functionality of the methods.
Nevertheless, the JNI does not provide for
generic means to establish a direct correspondence
between an object class hierarchy in C++
that (partially) implements a corresponding object class
hierarchy in Java.
In this section, we will study how
such a correspondence is realized in the hush framework,
using the Java Native Interface.
public int _self; // peer object pointer
...
};
public kit() { _self = init(); }
protected kit(int x) { }
private native int init();
public native void source(String cmd);
public native void eval(String cmd);
public String result() {
String _result = getresult();
if (_result.equals("-")) return null;
else return _result;
}
private native String getresult();
public native void bind(String cmd, handler h);
...
};
include @lt;hush/hush.h>
include @lt;hush/java.h>
include @lt;native/hush_dv_api_kit.h>
#define method(X) Java_hush_dv_api_kit_##X
JNIEXPORT jint JNICALL method(init)(JNIEnv *env, jobject obj)
{
jint result = (jint) kit::_default; // (jint) new kit();
if (!result) {
kit* x = new kit("tk");
session::_default->_register(x);
result = (jint) x;
}
return result;
}
public:
java_vm(JNIEnv* env_, jobject obj_) {
_env = env_;
_obj = obj_;
_self = self();
}
...
event* dispatch(event* e) { java dispatch
call("dispatch",(int)e);
return e;
}
T* operator->() { return _self; }
T* self() {
jfieldID fid = fieldID("_self","I");
return (T*) _env->GetIntField( _obj, fid);
}
void call(const char* md, int i) { // void (*)(int)
jmethodID mid = methodID(md,"(I)V");
_env->CallVoidMethod(_obj, mid, i);
}
private:
JNIEnv* _env;
jobject _obj;
T* _self;
};
Discussion
Architectural patterns and styles
subsections:
When constructing a system, how does one determine an appropriate
style?
There is no simple answer to this question.
According to From technology to style
We distinguish between three different architectural styles:
This distinction is arbitrary, in the sense that other
distinctions are conceivable.
However, the distinction above is well motivated
by the technology matrix introduced in
section Technology,
as reflected in the feature-based description given
below.
Features
Case study -- perspectives in visualization
To determine which architectural style to use,
or which mix of styles,
is to a large extent determined by practical experience.
Nevertheless, at the end of this section,
we will discuss some rules of thumb that may guide you
in the choice of a particular style.
However, first we will look at an example that
illustrates the consequences of the choice of a particular style.
The example comes from
the distributed visualization architecture (DIVA)
that is explained in more detail in
section DIVA.
DIVA is being developed in cooperation
with ASZ/GAK, the largest
social security provider in the Netherlands,
for experimenting with business visualization to support
decision making.
Our case study focuses on how to support
the sharing of perspectives in visualizing shared information.
For example, one of the users discovers
a new way to display information, uncovering aspects
that would otherwise remain hidden.
This new perspective must then be shared with
other users to coerce them, so to speak,
to this new point of view.
What we will look at, here,
is how the choice of a particular style
affects the solution for the sharing of perspectives
problem.
Distributed objects style
New functionality can be added by creating a new object at the server.
In this case, slide Perspectives(a),
the user discovering a new perspective acts as the server.
Then, assuming that the discovery of a new perspective
is somehow announced to the other users,
a user can connect to the server and request
for that particular perspective (1).
Then, a new visualization object is created (2),
which is made accessible to the user requesting for
the new perspective (3).
Downloadable code style
When a new visualization perspective is discovered,
a class is created that can be downloaded by the
interested user, slide Perspectives(b).
The user connects to the server that contains the new visualization class (1),
downloads the class, and instantiates
a new visualization object (2).
Finally, the information is retrieved from
the shared information server and accordingly visualized (3).
Mobile objects style
Similar as in the downloadable code style,
the new visualization perspective
is downloaded from a server to the client, slide Perspectives(c).
However, in this case, when a user requests for a new
perspective (1), it is not a class, but an object,
actually a clone of the object residing at the server,
that is transferred to the client's machine (2).
The clone, which contains all relevant information,
does not have to contact the shared information server
to update the user's visualization with a fresh viewpoint.
Guidelines for selecting a style
Rules of thumb -- selecting an architectural style
Cross-platform development
Platform dependencies form an important category
of architectural constraints.
In particular, the opportunities offered by
one platform may prohibit the deployment of software
on other platforms.
Nowadays, there are a number of (flavors of) competing
platforms, as there are the Unix flavors (of which Linux is
becoming a strong contender) and the Windows family,
including 3.1 (almost extinct), Windows 95, NT, 98
and (in beta release) Windows 2000.
Unix (for example Sun Solaris and SGI IRIX) has by tradition
a strong position in the server market.
However, Windows NT is growing rapidly in importance.
The Windows family, clearly, dominates the (client) desktop market.
Cross-platform development
Research/GNU
Commercial
As we have discussed previously,
many of the open standards, such as OMG CORBA,
and proprietary standards such as Sun Java,
aim at platform independence.
Also, there are numerous GUI toolkits
available that offer platform-independent support.
A possible disadvantage of this approach is that the
platform specific technology can usually not be profited from.
Discussion
Summary
Elements of architecture
Case study -- multimedia feature detection
Crossing boundaries
Architectural patterns and styles
Cross-platform development
Questions
Further reading
The history of programming languages may be characterized as the genesis of increasingly powerful abstractions to aid the development of reliable programs.
Additional keywords and phrases:
control abstractions, data abstractions,
compiler support,
description systems,
behavioral specification,
implementation specification
The kind of abstraction provided by ADTs can be supported
by any language with a procedure call mechanism
(given that appropriate protocols are developed
and observed by the programmer). A foundational perspective -- types as constraints
Object-oriented programming may be regarded as
a declarative method of programming,
in the sense that it provides a computation model
(expressed by the metaphor of encapsulation and message
passing) that is independent of a particular implementation model.
In particular, the inheritance subtype relation may
be regarded as a pure description of the relations
between the entities represented by the classes.
Moreover, an object-oriented approach favors the
development of an object model that bears close resemblance
to the entities and their relations living in the
application domain.
However, the object-oriented
programming model is rarely introduced with the
mathematical precision characteristic of descriptions
of the other declarative styles,
for example
the functional and logic programming model.
Criticizing,
empty
right append
left append
concatenation
lifting
multiple arguments
attribute
method
function n : ctr -> nat
method incr : ctr -> ctr
axioms
n(new(C)) = 0
n(incr(C)) = n(C) + 1
end
list* r = cons(1,cons(2,nil())); while (!empty(r)) { cout << head(r) << endl; r = tail(r); }
list<int>* r = new cons<int>(1, new cons<int>(2, new nil<int>)); while (! r->empty()) { cout << r->head() << endl; r = r->tail(); } delete r;
Discussion
(under)
(right)
(over)
(type)
(signature)
(classes)
There is a vast amount of literature on the algebraic
specification of abstract data types.
You may consult, for example,
From a theoretical perspective, object orientation may be characterized as combining abstract data types and polymorphism. These notions may be considered as the theoretical counterparts of the more operational notions of encapsulation and inheritance.
Additional keywords and phrases:
exceptions, type calculi,
parametric types, coercion, ad hoc polymorphism,
universal types, existential types,
unfolding, intersection types
Incremental system evolution is in practice non-monotonic!
The meaning of is-a and is-not relations in a knowledge representation inheritance graph may equivalently be expressed as predicate logic statements. For example, the statements
In Inheritance as incremental modification
Example:
Independent attributes: M disjoint from P
Overlapping attributes: M overrules P
Proof: take and , then
A simple type calculus
In our first version of a type calculus
we will restrict ourselves to a given set
of basic types (indicated by the
letter ) and function types (written
, where stands for the domain
and for the range or codomain).
This version of the typed lambda calculus
(with subtyping) is called in
Bounded polymorphism
Our next extension, which we call , involves
(bounded) universal quantification.
For technical reasons we need to introduce a
primitive type Top, which may be considered as
the supertype of all types (including itself).
Also we need type abstraction variables, that we will
write as and .
Our notation for a universally quantified (bounded) type
is , which denotes the type
with the type variable replaced by any subtype of .
In a number of cases, we will simply write ,
which must be read as .
Recall that any type is a subtype of Top.
Observe that, in contrast to and ,
the calculus is second order (due to the quantification
over types).
In addition to the (value) expressions found in the two
previous calculi, introduces a type abstraction
expression of the form and a type instantiation
expression of the form .
The type abstraction expression
is used in a similar way as the function abstraction expression,
although the abstraction involves types and not values.
Similar to the corresponding type expression,
we write as an abbreviation for .
The (complementary) type instantiation statement is written
as , which denotes the expression e in which
the type identifier is substituted for the type variable
bound by the first type abstractor.
public:
virtual T value() = 0;
};
class Int : public A<int> { //
OK
, \zline{(contravariance)}
where and
Simple typing --
Delayed --
We have \zline{(more information)}
where
where
However \zline{(subtyping error)}
F-bounded constraint \n
Object instantiation:
for \n
We have because
with recursive types
Valid, because
However
Discussion -- Eiffel is not type consistent
int b;
public:
C() { ... }
bool eq(C& other) { return other.i == i && other.b == b; }
bool eq(P& other) { return other.i == i; }
};
2
3
4
5
6
As further reading I recommend
Ultimately, types are meant to specify behavior in an abstract way. To capture behavioral properties, we will generalize our notion of types as constraints to include behavioral descriptions in the form of logical assertions.
Additional keywords and phrases:
behavioral subtypes, state transformers,
correctness formulae, assertion logic,
transition systems, invariants, formal specification
The answer to the first question is suggested by the observation that we may also express the constraints imposed by the signature by means of logical formulae that state the constraints as assertions which must be satisfied.
When we have, for example, an assignment then we have as the corresponding transition where , that is is like except for the variable i for which the value 5 will now be delivered. Whenever we have a sequence of actions then, starting from a state we have corresponding state transformations resulting in states as intermediary states and as the final state. Often the states and are referred to as respectively the input and output state and the program that results in the actions as the state transformer modifying into .
class as {
int t;
T a[MAX];
public:
as() { t = 0; }
void push(T e) {
require(t
To establish the correctness of the operation pop,
we must prove that the pre-condition specified
for the abstract operation is indeed stronger
than the pre-condition specified for the concrete
operation, as expressed by the formula
The correspondence relation
Behavioral refinement is not restricted
to the realization of abstract specifications.
We will now look at a definition of behavioral
refinement, following
Contracts as protocols of interaction
subject : model supports [
state : V;
value( val : V ) $|->$ [state = val]; notify();
notify() $|->$ $\forall v \e $views $\bl$ v.update();
attach( v : view ) $|->$ v $\e$ views;
detach( v : view ) $|->$ v $\not\e$ views;
]
views : set
Adopting an object-oriented approach is ultimately motivated by the need to develop applications. In this chapter we will look at business applications.
Additional keywords and phrases:
business objects, business logistics, frameworks,
object-oriented simulation
connecting components
Given the importance of participant communication in a business process redesign effort, embedding business simulation models in hypermedia provides in a natural way the information context needed for exploring alternatives and taking decisions. With Intranets becoming more popular, the World Wide Web has proven to be a popular access point for many applications.
The hush library and its extensions offer
a rich environment for developing
hypermedia applications and dynamic Web documents
The hush library originated as a C++
interface to the GUI scripting environment
provided by Tcl/Tk
By defining a script interface for application-specific C++ classes, application functionality may be embedded in a Web page as an applet. As illustrated in the example, our approach to providing hypermedia and Web support employs this feature, both to reduce the time involved in modelling and to present a graphical representation of the model and its execution to the user.
connecting components
Given the importance of participant communication in a business process redesign effort, embedding business simulation models in hypermedia provides in a natural way the information context needed for exploring alternatives and taking decisions. With Intranets becoming more popular, the World Wide Web has proven to be a popular access point for many applications.
The hush library and its extensions offer
a rich environment for developing
hypermedia applications and dynamic Web documents
The hush library originated as a C++
interface to the GUI scripting environment
provided by Tcl/Tk
By defining a script interface for application-specific C++ classes, application functionality may be embedded in a Web page as an applet. As illustrated in the example, our approach to providing hypermedia and Web support employs this feature, both to reduce the time involved in modelling and to present a graphical representation of the model and its execution to the user.
The explosive growth of the Web is perhaps the single most important event in the history of computing technology. What started as an information infrastructure is now turning into an infrastructure encompassing both information and applications, and is becoming the backbone for the commercial deployment of the Internet.
Additional keywords and phrases:
Web Objects, XML, Java, CORBA, multimedia,
software architecture
Although many of the ideas and concepts underlying agents have been a topic of research for decades, notably issues concerning distributed systems and programming language design, the notion of agents itself hase become a major research topic only recently. A survey of agent theories, architectures and languages is given in [ Survey]. As concerns agent theories, the main focus of attention lies on a logical analysis of intentional notions underlying the behavior o research groups Interne t,Softbots,Software,Architecture,Survey. However, one may observe a divergence between purely formal approaches and purely pragmatical approaches, that provide an operational realization of agents lacking a solid foundation.
Although many of the ideas and concepts underlying agents have been a topic of research for decades, notably issues concerning distributed systems and programming language design, the notion of agents itself hase become a major research topic only recently.:u single agents, whereas for agent architectures issues of communication and cooperation between agents have been addressed in more detail. In [ Survey], concurrent l ogic programming languages are considered to be the ancestors of agent languages. Of immediate relevance for our own work is the Oasis programming language described in [ Oasis], which bears a close resemblance to the distributed logic programming language DLP [ DLP]. Recently, commercial agent systems have been developed for the Web. For example, General Magic has launched its Telescript Agent System, a system that employs mobile agents defined in General Magic's Telescript language. Also in [ Internet], a number of agent-like programs are described. We are reluctant to qualify these systems as 'agents', however, since they are defined in a purely operational way. We find, for complex agents anyway, a more explicit representation of the intentional aspects desirable.
In a recent issue of the CACM( 37(7), July 1994), some current trends and applications of agent research are presented. From our perspective we consider as important representatives of ongoing research: [ Software], that describes the agent communication language KQML (Knowledge Query and Manipulation Language); [ Architecture], which describes the complexity of integrating collections of agents; and [ Softbots], which describes a number of simple agents for facilitating access to the Web.
(C) Æliens 23/08/2009
You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.