Role modeling for framework design is a new technique, for
which no standard exists. Current industry design notations and
programming languages provide no direct support. While standard
design notations and programming languages provide classes and
objects, they do not support role types, role constraints, and
role models. However, standards are important, because they provide
developers with a shared vocabulary and tool support. This chapter
discusses how to extend industry-standard design notations and
programming languages with role modeling concepts. Extensions
are provided for UML, Java, C++, and Smalltalk.
5.1 Chapter overview and motivation
Role modeling for framework design introduces new concepts
and extends established ones. Effectively, it forms a new design
method. However, many of the concepts cannot be expressed directly
using industry-standard design notations and programming languages.
Some of the new or revised concepts map well on concepts of
current design notations and programming language standards. For
example, the concept of class maps well on the UML concepts of
interface or class. Other concepts, for example, the role model
concept, are more difficult to map, because no equivalent concept
exists.
Role modeling for framework design is only a part of what could
be a full-blown design method. It ignores many issues that are
needed for an industrial-strength design method. One possible
conclusion might therefore be to develop a new full-blown design
notation and programming language that directly supports role
modeling.
This approach has the following advantages:
- Developers can directly express framework designs. No mental
gap between the concepts behind a design and its expression using
a specific notation must be maintained.
- Given adequate tools, framework design and implementation
can be checked for conformance based on the new concepts. Such
checks may significantly reduce the error rate.
However, this approach also has the following disadvantages:
- Role modeling for framework design is an evolutionary addition
to existing approaches. It does not try to replace existing standards
and should therefore extend rather than replace them.
- The development of a full-blown industrial-strength modeling
approach requires a substantial amount of work. A new approach
would distinguish itself only through its new framework design
concepts.
- Developers would have to learn yet another new method. This
increases the time until they productively join a project and
makes it more difficult to find people that are willing to maintain
a system.
- No tool support or no tool support comparable to the support
for mainstream notations is available.
For practical purposes, it is preferable to extend existing
industry standards with the new role modeling concepts rather
than to introduce yet another approach that is incompatible with
the existing ones. An extended industry standard provides the
new concepts and still lets developers get to work quickly. This
approach faces less developer resistance, with tool support being
available right from the beginning.
A dedicated design notation and programming language for role
modeling can be introduced later, should role modeling for framework
design reach widespread acceptance. In fact, role programming
techniques like [Van97, VN96] and general purpose programming
language extensions like aspect-oriented programming [KLM+97]
already point into that direction.
This chapter discusses how to extend industrial-strength design
notations and programming languages with the new role modeling
concepts. First, it discusses requirements and properties common
to all these extensions. Then, the chapter introduces an extension
of one design notation, UML. Finally, it provides extensions of
the programming languages Java, C++, and Smalltalk.
5.2 Common properties
This section discusses how industry standards can be extended
with role modeling concepts, and how mappings between standards
ease the introduction of a new extension. The section also lists
properties that should be common to all role modeling extensions
of existing industry standards. It reviews the concepts that must
be provided by each such extension and introduces a simplified
version of the Figure framework example.
5.2.1 Extending an industry standard
A successful extension of an industry standard is only possible
if the extension blends in well with the existing concepts and
their idiomatic use. Otherwise, the extension would feel alien
to the developers and be rejected. Fortunately, role modeling
for framework design was intended to be an extension of known
concepts right from the beginning.
As a consequence, the industry standard will take on the dominant
role, and the role modeling concepts that extend it must come
natural to users of the standard. They must be expressed in terms
of the standard's concepts or extension mechanisms. Chapters 3
and 4 define and discuss the role modeling concepts in standard-neutral
terms to ease the extension of different standards and put them
onto a common basis.
Figure 5-1 illustrates how the role modeling concepts extend
the four standards discussed in this chapter (UML, Java, Smalltalk,
and C++).
-
- Figure 5-1: Extension of industry standards with role modeling
concepts.
Figure 5.1 illustrates two types of relationships: extensions
and mappings.
- Extensions are role modeling extensions of the respective
industry standard. For example, UML Role Modeling is an extension
of UML within the confines of UML. An extension is the definition
of role modeling concepts in terms of the existing standard's
concepts and extension mechanisms.
- Mappings are mappings between the general role modeling concepts
and a role modeling extension of a specific industry standard.
A mapping is a function that explains how a role modeling concept
is represented in a specific standard.
Being explicitly aware of the mappings is important, because
it helps introduce new industry standard extensions quickly and
without much overhead. For example, if a new programming language
is defined, and UML tool support for that language exists, the
extension of the new programming language with role modeling concepts
can be described as the composition of three functions MRM/UML,
EUML/RM, and MUML/PL:
- MRM/UML is the mapping from the general role modeling
concepts to the UML Role Modeling concepts.
- EUML/RM is the extension of the UML concepts with
the UML Role Modeling concepts.
- MUML/PL is the mapping between UML and the new
programming language.
A more detailed and direct extension of the programming language
can be introduced later.
The mappings are typically one-way mappings. Depending on the
target of the mapping, it cannot be guaranteed that an inverse
mapping exists. In any such mapping, information from the domain
is lost. This is one reason, why it is better to provide direct
extensions of the programming languages rather than using a mapping
detour through UML as just illustrated.
The other reason is that it is more convenient to use a direct
extension rather than carrying out the composition of three mappings.
Still, if a new programming language is introduced, for which
no direct extension exists yet, developers can use this mechanism.
5.2.2 General requirements
The extension of an industry standard with role modeling concepts
must not only be as precise as possible, but it must also match
the way developers work with the extended standard and the base
standard.
Ideally, the following requirements are fulfilled when defining
an extension:
- Robustness. A design or program based on the extended standard
must be robust with respect to interpretation and handling by
developers who do not understand the extension. Developers must
still be able to work on the design.
- Inconsistency. Also, a design that is inconsistent from the
extension's point of view must not block the system in any respect
(for example, browsing, type checking, or code generation). As
a corollary to the robustness requirement, developers must still
be allowed to work productively with an inconsistent design.
- Incompleteness. The design must allow evolutionary adding
of elements based on the extended standard rather than requiring
that the design be fully compliant with the extension right from
the start. Thus, the extension must allow for its partial application.
- Tool support. Non-trivial designs require tool support, such
as Rational Rose, Paradigm Plus, Visio, etc. The extended standard
must take into account how users handle designs visually. (This
is one reason why, for example, the UML notation guide describes
a concrete visual syntax rather than an abstract syntax only.)
- Bridge between design and implementation. An extended standard
should not complicate bridging between design and implementation.
A design usually keeps evolving close to its implementation.
Developers avoid making the gap between design and implementation
too large to reduce intellectual mismatch.
Providing robustness, dealing with inconsistency, and dealing
with incompleteness ensures a low entry hurdle to using role modeling.
Because designs may be incomplete, the initial investment may
scale from zero to the full application of the method. Because
a design may be inconsistent from a role modeling perspective,
the whole system (tool + design) stays operational at any point
in time.
Providing robustness is the hardest requirement. It prevents
that the mapping introduces additional complexity and uncommon
semantics into a design that is otherwise easily interpretable
using general knowledge of the underlying design notation.
5.2.3 Handling role types and role models
UML, Java, C++, and Smalltalk provide support for classes and
their packaging, next to a wealth of other concepts. Role modeling
for framework design requires support not only for classes, but
also for role types, role constraints, role models, class models,
and frameworks. None of the industry standards discussed in this
chapter provides native support for these concepts.
However, developers always have modeled and implemented class
models and frameworks. They typically use the standard's packaging
mechanism to define what goes into a class model or framework,
and they use the naming mechanism and conventions of the standard
to separate different models. We can therefore use the packaging
mechanism to define role models, class models, and frameworks.
Each section of this chapter on extending a specific standard
shows how the role modeling concepts of class, role model, class
model, and framework are represented using native concepts. The
representation is typically based on the standard's concept of
type, class, and packaging.
The primary challenge is to adequately extend a standard with
the role type, role constraint, and role model concept, and to
define how they relate to classes and class models. For each standard,
this is done differently. Role constraints do not require much
discussion, but role types do, due to the many different ways
of using them.
Let us assume that a standard provides a simple means to represent
a type, for example a UML or Java interface. A role type could
then be expressed using an interface. However, different kinds
of role types have different pragmatics, and it is questionable
whether one concept of interface fits all uses of the role type
concept. Moreover, given the high number of role types in a design,
the naive approach faces an explosion in the number of interfaces.
For expressing role types, we face the following options:
- Leave role types implicit in a class interface.
- Make role types explicit in a class interface by annotating
them (for example, through method categories).
- Make role types explicit as interfaces of their own.
For expressing role models, we face the following options:
- Leave role models implicit by leaving all of their role types
implicit.
- Make a role model explicit as part of a class model by packaging
role models with their class model.
- Make a role model explicit and reusable for different class
models by packaging it on its own.
Design and implementation use these options differently. In
design, typically every important design decision should be recorded
and expressed. In implementation, only all implementation decisions
should be explicit, but there is no need to directly reflect the
full design.
The design notation and the programming language sections introduce
their respective rules below.
5.2.4 Figure framework (example)
This chapter uses a simplified version of the Figure framework
as its example. Figure 5-2 shows its design.
-
- Figure 5-2: The (simplified) Figure framework.
The Figure framework comprises two classes (Figure and CompositeFigure),
builds on the Graphics class, and extends the Object framework.
The dynamics of the framework are defined by the ObjectProperty,
Figure, FigureChain, FigureObserver, FigureHierarchy, and Graphics
role models.
The framework is discussed in depth in Chapters 3 and 4. It
offers all constellations that we need to discuss.
- ObjectProperty is a reusable role model. All other role models
are non-reusable.
- Figure.Figure, FigureObserver.Subject, etc. are regular role
types without further qualification.
- FigureObserver.Observer is a free role type that has operations.
- Figure.Client, FigureHierarchy.Client, and Graphics.Client
are free no-op role types.
Sections 5.3 and 5.4 show how the framework is expressed in
UML and implemented in Java, C++, and Smalltalk.
5.3 Design notations
This section discusses how to extend a design notation. It
provides one example: the extension of UML with role modeling
concepts for framework design. The discussion omits the trivial
parts and focuses on the higher-level concepts of role type, role
constraint, class, role model, class model, and framework.
5.3.1 Extending design notations
A design notation must provide means to record and document
all relevant design decisions. In contrast to programming languages,
which usually let developers make only parts of a design explicit
in the implementation, should a design notation let developers
record all relevant information (whether they do so is another
question).
Therefore, the extension of a design notation must cater for
all new or revised modeling concepts, including role type, class,
role constraint, role model, class model, and framework. Also,
it should be possible to express variations of specific design
concepts, like free role types and no-operation role types.
This section does not discuss how to express the dynamic behavior
of objects acting according to a class type or role type. The
definition of such a notation is left to the available type specification
mechanisms. UML, for example, offers object and sequence diagrams
to illustrate runtime object behavior. Also, more elaborate approaches
like Extended-Event-Traces [BHKS97] or others can be used.
The rules for expressing role models, class models, and frameworks
on a design level are simple:
- Class model. A class model should be represented explicitly.
It gets at least one package. For structuring complex models,
more packages can be used.
- Reusable role model. A reusable role model should be represented
explicitly. It should be packaged independently of a specific
class model in which it is used, for example, in a package of
reusable role models.
- Non-reusable role model. A role model that is used once in
a class model need not be packaged on its own. It should go into
the class model's package. Within that package, it might get
its own diagram or not, depending on the significance of the
role model.
Role types of a reusable role model should always be made explicit,
both in design and implementation.
For non-reusable role models, the situation is more complicated.
As discussed, we can qualify role types as being free and/or no-op.
These properties lead to the following kinds of role types and
their rules for being left implicit or made explicit:
- Role types. Regular non-free role types with operations should
be made explicit, because they represent an important design
aspect that needs to documented explicitly.
- Free role types (both no-op and with operations). Free role
types should be made explicit, because they may be picked up
by more than one client class, and because they represent an
important design aspect.
- Non-free no-op role type. A non-free no-op role type may
or may not be made explicit. It is only used within the context
of the current class model; hence the number of uses is fixed.
Thus, role types of a non-reusable role model should be made
explicit if they are regular or free role types. If they are non-free
no-op role types, a developer may decide to not explicitly represent
a role type but to reduce it to an annotation of the class providing
the role type.
5.3.2 Extending UML with role modeling
UML offers a rich metamodel for modeling object systems, which
makes it easy to extend it with role modeling concepts. The extension
of UML with role modeling concepts relies only on three basic
UML concepts: Class, Interface, and Stereotype.
- A class maps either on a UML class or a UML interface. Whether
a class or an interface is chosen depends on the complexity of
implementing the class. A lightweight class is typically represented
as a UML class, while a heavyweight class, for which several
implementations exist, or which has many role types, is represented
as a UML interface.
- A role type maps on a UML interface tagged with the "RoleType"
stereotype. Such an interface is called a role-type interface.
A free role type is represented as a "FreeRoleType"
interface, a no-op role type as a "NoopRoleType" interface,
and a free no-op role type as a "FreeNoopRoleType"
interface.
- A role constraint between two role types is mapped on a textual
annotation of the relationship between the two role types. If
no annotation is provided, the default case (role-dontcare) is
assumed.
- A role model or a class model maps on a class diagram. There
is no need to make a class diagram for every role model, but
if a role model is reusable, it should get its own class diagram.
- A framework also maps on a class diagram. In addition, it
might be packaged as its own component, drawing visible boundaries
to the outside.
-
- Figure 5-3: The ObjectProperty role model as a UML class
diagram.
When working with UML Role Modeling, developers use UML classes
and interfaces as they are used to. If needed, they add additional
role type and role model information. They tag interfaces and
classes as specific kinds of role types, and make the role models
explicit by connecting role-type interfaces and classes.
Figure 5-3 shows the ObjectProperty role model using a UML
class diagram. It uses the color coding introduced earlier: free
role types have a light-gray background.
The ObjectProperty role model is a reusable role model that
should be packaged on its own, or, at least, should get its own
diagram. Property.Client, Property.Provider, and Property.Property
are role-type interfaces. Because the ObjectProperty role model
is a reusable role model, all of the role types are free. In addition,
the Property.Client and the Property.Property role types are no-op
role types.
-
- Figure 5-4: The Figure framework as a UML class diagram.
Please also observe the use of the class InvalidPropertyException
without any further qualification. This reflects the pragmatic
approach of robustly embedding the role modeling concepts into
an existing standard. From a UML perspective, the role-type interfaces
are just interfaces that relate to other interfaces and classes
without further qualification.
Figure 5-4 shows the Figure framework using a UML class diagram.
In addition to coloring free role types in light-gray, it colors
role modeling classes in dark-gray. Please note that Figure and
CompositeFigure are conceptually classes from a role modeling
perspective, but are represented as UML interfaces. As explained,
this decision is a matter of expected convenience of implementation.
Figure 5-4 shows how different kinds of role types are represented:
- Examples of role-type interfaces are FigureHierarchy.Child
and FigureChain.Predecessor.
- Examples of free role-type interfaces are FigureObserver.Observer
and ObjectProperty.Provider.
- There are no examples of non-free no-op role type interfaces.
- Examples of free no-op role type interfaces Figure.Client
and FigureHierarchy.Client.
Most of the role models bound to this class model are not made
explicit. They exist only in this diagram, and are implicitly
represented by the naming convention of their role types.
The role modeling classes Figure and CompositeFigure are represented
as UML interfaces, while the (simplified) helper class Request
is represented as a UML class.
5.3.3 Extension properties
The following discussion shows how the extension properties
asked for in Section 5.2 are realized by the UML role modeling
extension. It thereby describes the rationale behind the extension.
- Robustness. The interpretation of a UML Role Modeling class
diagram can be done by any developer who knows interfaces and
classes, and who is told that a roletype interface represents
a type that is part of the class type it is extended by. This
is the minimal effort required.
- Inconsistency. Standard UML allows classes and interfaces
to directly refer to each other. Thus, role-type interfaces may
refer to other UML classes and interfaces, without an intermediate
role-type interface. From a role modeling perspective, such direct
reference indicates that the design may not yet be done and that
further elaboration will provide the missing role types.
- Incompleteness. Designs may be inconsistent, and therefore
incomplete.
- Tool support. The mapping uses only basic UML concepts. These
concepts are the first to be provided by a UML based modeling
tool, so that tool support for UML Role Modeling is readily available,
even if the tool supports only a subset of standard UML. If code
generation needs to be supported in more detail, additional stereotyping
of interfaces can provide missing information and control back-end
code generation.
- Bridge to implementation. The mapping supports modeling close
to the implementation level. In an implementation, a UML interface
typically maps onto the interface concept of a programming language,
while a UML class directly maps on an implementation class. Mapping
a role modeling class on a UML interface or class lets developers
make this distinction on the design level and supports code generation.
The definition of the mapping uses the most common abstractions
only, because developers best know them, and because the UML definition
still evolves, as it is imprecise and unclear in many instances
[BHH+97].
5.4 Programming languages
Every framework is eventually implemented using a programming
language, independently of whether it was designed using role
modeling or not. Typically, programming languages provide only
meager support for representing design-level concepts that go
beyond individual classes. This section analyses which of the
role modeling concepts to map to a programming language and how
to do so. The section covers Java, C++, and Smalltalk.
5.4.1 Extending programming languages
Programming languages provide mechanisms for source code definition
and structuring. Designers of programming languages typically
do not include higher-level design concepts like role model, class
model, or framework in the language definition. These concepts
are left to the design level of software system development, as
opposed to the implementation level, the support of which is the
primary concern of programming language designers. All programming
languages covered in this section exhibit this property.
The distinction between design and implementation relaxes the
situation. While a design must capture all relevant design details,
an implementation must only provide a proper implementation of
the design, but not the design itself. Hence, there is no need
to extend a programming language with all role modeling concepts.
Rather, it is sufficient to provide those concepts that let us
implement a design in such a way that we find our way back to
the design documentation and do not have to bridge too far a mental
gap when doing so.
For identifying role models, class models, and frameworks,
the packaging mechanism of a given programming language can be
used. Implementation-level packaging follows the same rules as
design-level packaging:
- A class model gets its own package (at least one).
- A reusable role model gets its own package.
- A non-reusable role model becomes part of the package of
a class model.
Implementation-level role types can be expressed using interfaces
or equivalent concepts. All role types of reusable role models
should be explicit. For role types from non-reusable role models,
the following rules apply:
- Role type. A regular role type need not be made explicit
as an interface. It can be merged with the class interface. It
is helpful, though, to annotate the operations of a class interface
with the role type names.
- Free role type. A free role type with operations must be
represented explicitly, so that clients can pick up the role
type by inheriting from it or copying it.
- No-op role type. A no-op role type that is not free need
not be made explicit. However, it is helpful to annotate a class
as providing a particular no-op role type.
- Free no-op role type. A free no-op role type may or may not
be made explicit. Because the role type is free, it is important
to see it explicitly. Because it is no-op, simply tagging a client
class might be sufficient.
A class should list all of its role types, including no-op
role types, and it should annotate an operation with the role
type name from which it is derived. If a role type is explicit,
this happens automatically. If a role type is not made explicit,
developers need to do this by hand.
Role constraints are not made explicit. This feature of the
role modeling technique is most useful on the design level to
communicate the idea behind a role model. It is left to the implementation
of a class to ensure that a role constraint is maintained. The
role types or class interfaces involved may document the constraint,
though.
To support the annotation of class interface with role type
and role model information, specialized documentation tools can
be used. For example, Java's javadoc and its variants in other
programming languages provide lightweight annotation features
that support documentation, code generation, and round-trip engineering
between design and implementation.
5.4.2 Problems of programming language extension
The pragmatic extension of programming languages described
above is too weak for properly checking the conformance of a design
with its implementation. Such conformance checking is desirable,
because it lets us catch implementation errors as early as at
compile-time. However, conformance checking is typically done
only for very specialized areas of software system design. The
critique therefore applies to the whole of object-oriented modeling.
More immediate problems occur due to the composition of role
types to form a class type. Most programming languages do not
support type specification and composition. Eiffel is an exception
of a close-to-mainstream programming language that provides several
useful features of this kind, in particular a lightweight type
specification mechanism (design by contract [Mey91, Mey92b]),
and a renaming feature [Mey92].
The following problems arise from the need to compose role
types:
- Name clashes. Different role types might define two identical
operations. However, their semantics is different, and they are
used for different purposes. The operations must be distinguished,
and the naming conflict must be resolved.
- Redundant operations. Different role types might define operations
that have different signatures, but the same implementation.
In the Figure framework example, operations getParent and getSuccessor
always return the same object reference, making one implementation
redundant.
- Mapping of abstract state on implementation state. Each role
type comes with its own state space definition. When composing
role types, the state spaces of the role types must be mapped
on the single state space of the class type, for which a proper
implementation state space must be defined.
The first problem, name clashes, is a common problem that is
independent of the approach presented here. Some programming languages
offer explicit renaming features. If no such feature is available,
the names must be changed to avoid the conflict. Usually, the
operation names are pre- or postfixed to indicate their origin.
The second problem, redundant operations, is not a real problem,
but rather an issue of style. Should a redundant operation be
removed? Names are important, even if two operations do the same.
Each client expects operation names that are best suited for the
task at hand. Therefore, redundant operations should not be removed.
Rather, they should be named well and implemented using common
primitive operations.
The last problem, the definition of the implementation state
space of a class, results from the larger problem of type composition.
The programming languages considered here describe the semantics
of some operation using prose. The only aspects described formally
are the parameters and return values of the operations of a role
type. They define the abstract state space that clients see of
an object playing a role defined by the role type.
A developer of a class must define how the abstract state spaces
of the role types are composed to form the abstract state space
of the class. For the abstract state space of a class, the developer
must then find a suitable and efficient implementation. In practice,
the intermediate step of defining the abstract state space of
a class is not explicit, and a class is implemented by deriving
its implementation state space directly from the role types.
In the end, this is not a problem that can be treated on a
general level, and it is up to every developer to find a suitable
implementation state that lets him or her efficiently implement
the different role types. Frequently, a class internal set of
primitive operations is used by the role type implementations
to manipulate the common implementation state. Also, the implementations
of role type operations call each other to communicate a state
change. Implementing this is an issue of proper programming practice,
and not considered further here.
5.4.3 Extending Java with role modeling
Java provides interfaces, classes, and packages as concepts
that can be used to implement role-model-based designs. Java interfaces
and classes can be used to represent role types and classes, and
Java packages can be used to provide a namespace for role models,
class models, and frameworks.
A role type is represented as a Java interface. A class is
represented as a Java interface or class, depending on its expected
implementation complexity. Reusable role models and class models
are packaged as individual units.
Specification 5-1 shows the source code of the Java Figure
interface.
package org.riehle.diss.Figure;
import java.util.Enumeration;
import java.awt.Graphics;
import java.awt.Point;
import org.riehle.diss.ObjectProperty.*;
/**
A Figure object is a graphical figure object that
can be used in drawing editors. It provides the
following role types:
- Figure.Figure
- FigureHierarchy.Child
- FigureObserver.Subject
- FigureChain.Predecessor
- Graphics.Client
- ObjectProperty.Provider
**/
public interface Figure extends PropertyProvider {
/**
@roletype Figure.Figure
**/
public void draw(Graphics gc);
public void drawOutline(Graphics gc);
public Point getOrigin();
public void setOrigin(Point origin);
public Point getExtent();
public void setExtent(Point extent);
public void place(Point location);
public void move(int dx, int dy);
public void resize(int handle, int dx, int dy);
/**
@roletype FigureHierarchy.Child
**/
public boolean hasParent();
public CompositeFigure getParent();
public boolean maySetParent();
public void setParent(CompositeFigure parent);
/**
@roletype FigureObserver.Subject
**/
public boolean hasFigureListener(FigureListener listener);
public void addFigureListener(FigureListener listener);
public void removeFigureListener(FigureListener listener);
/**
@roletype FigureChain.Predecessor
**/
public CompositeFigure getSuccessor();
public boolean maySetSuccessor();
public void setSuccessor(CompositeFigure successor);
/**
@roletype Graphics.Client
@properties free noop
**/
}
- Specification 5-1: Definition of the Java Figure interface
to represent the Figure class.
A role type that is to be explicit is represented as a Java
interface. The use of abstract classes as a representation mechanism
of a free role type does not make sense, because Java classes
are restricted to single inheritance. The interface should be
named after the role type.
Using customary Java naming, Specification 5-2 shows the free
role type FigureObserver.Observer.
package org.riehle.diss.Figure;
import java.util.EventListener;
/**
This interface represents the FigureObserver.Observer role type.
A FigureListener is an object that is notified by a Figure object
about state changes of that Figure object.
@roletype FigureObserver.Observer
@properties free
**/
public interface FigureListener extends EventListener {
public void figureChanged(FigureEvent e);
public void figureRemoved(FigureEvent e);
public void figureInvalidated(FigureEvent e);
}
- Specification 5-2: Definition of the Java FigureListener
interface to represent the FigureObserver.Observer role type.
A free no-op role type of a class model may be represented
as an empty Java interface (or not at all). For example, the Figure
class provides the Graphics.Client role type. This is a free no-op
role type of the Graphics framework. The provision of the role
type can be annotated in the class interface as illustrated in
Specification 5-3:
public interface Figure extends PropertyProvider {
...
/**
@roletype Graphics.Client
@properties free noop
**/
}
- Specification 5-3: Role type Graphics.Client in the definition
of the Java Figure interface.
Examples of where it makes sense to explicitly represent a
free role type without operations are client role types for object
creation as presented in Specification 5-4. The comments in the
client interface describe the ordering criteria imposed on the
client of operation invocations on the newly created object.
package org.riehle.diss.Figure;
/**
This interface represents the GroupFigureCreation.Client role type.
@roletype GroupFigureCreation.Client
@properties noop
**/
public interface GroupFigureCreationClient {
// After calling a GroupFigure constructor, you may
// add and remove as many Child objects as you wish.
// You must finalize the initialization of a GroupFigure
// with a call to initDone(). After this, you cannot
// add or remove any Child object, unless you call
// reinitialize() to reopen the GroupFigure object.
// Calling reinitialize() will cause the GroupFigure
// to drop all contained Figure objects.
}
- Specification 5-4: Definition of the Java GroupFigureCreationClient
interface to represent the GroupFigureCreation.Client role type.
A role type of a reusable role model is represented as a Java
interface. The same argument that applies to a free role type
of a class model applies here as well. Specification 5-5 shows
the ObjectProperty.Provider role type.
package org.riehle.diss.ObjectProperty;
import java.util.Enumeration;
/**
This interface represents the ObjectProperty.Provider role type.
A Provider provides Object instances as Properties. A Property
has a name. A Client may ask for a property by name. It may set
a Property to the Provider using the Property's name. It may
request all names of accepted Property types and may test whether
a certain Property type is known and acceptable to the Provider.
@roletype ObjectProperty.Provider
@properties free
**/
public interface PropertyProvider {
public boolean hasProperty(String name);
public boolean acceptsProperty(String name, Object prop);
public Object getProperty(String name);
public Object getDefaultProperty(String name);
public Enumeration getProperties(); // collection of Object
public boolean acceptsPropertyType(String typeName);
public Enumeration getPropertyTypeNames(); // collection of String
public void setProperty(String name, Object prop)
throws InvalidPropertyException;
public void unsetProperty(String name);
}
- Specification 5-5: Definition of the Java PropertyProvider
interface to represent the ObjectProperty.Provider role type.
Classes are simpler to represent. They are expressed either
as a Java interface or a class. Which variant to choose depends
on the number of implementations of the design-level class [Rie97d,
Rie97e, RD99a, RD99b]. In the example, the class Figure is represented
as a Java interface, for which different Java classes exist as
different implementations.
In both cases, it is possible to tie in the different role
types that a class must provide. If the class is represented as
a Java interface, it inherits from the interfaces that represent
(some of) the role types it is to provide. If the class is represented
as a Java class, it simply implements them. In addition, non-reusable
role types are textually directly embedded into the Java interface
or the Java class.
Role models that are considered reusable should get their own
Java package. The package introduces a convenient namespace for
the role types of the role model. A framework should also get
its own package. It ties in reusable role models by means of import
statements. In the example, ObjectProperty is considered a reusable
role model and thus gets a package of its own. The Figure framework
also gets its own package, from which it imports these two other
packages.
5.4.4 Extending C++ with role modeling
In contrast to Java, C++ does not offer an explicit interface
concept. However, it provides the concept of pure virtual functions
(polymorphic operations without implementation) and multiple inheritance.
Taken together, these concepts can be used to emulate the Java
interface concept. This also meets the intent of the designers
of the C++ programming language [Str94].
The Java ObjectProperty interface from above is expressed in
C++ as shown in Specification 5-6.
#include "Object.h"
#include "String.h"
#include
/**
This interface represents the ObjectProperty.Provider role type.
A Provider provides Object instances as Properties. A Property
has a name. A Client may ask for a property by name. It may set
a Property to the Provider using the Property's name. It may
request all names of accepted Property types and may test whether
a certain Property type is known and acceptable to the Provider.
@roletype ObjectProperty.Provider
@properties free
**/
class PropertyProvider : public Object {
public bool hasProperty(string name) =0;
public bool acceptsProperty(string name, Object* prop) =0;
public Object* getProperty(string name) =0;
public Object* getDefaultProperty(string name) =0;
public vector<Object*>::iterator getProperties() =0;
public bool acceptsPropertyType(string typeName) =0;
public vector<string>::iterator getPropertyTypeNames() =0;
public void setProperty(string name, Object* prop)
throw InvalidPropertyException =0;
public void unsetProperty(string name) =0;
};
- Specification 5-6: Definition of the C++ PropertyProvider
interface to represent the ObjectProperty.Provider role type.
Using this substitute for Java interfaces, the whole Java discussion
also applies to C++. The Java implements relationship becomes
a regular inheritance relationship. C++'s inheritance mechanisms
allows multiple inheritance, so that there are no constraints
on the number of interfaces that might represent a role type and
that might be provided by a class.
Packaging in C++ is done using files and name spaces. The namespace
keyword lets developers scope classes with a name space so that
references to these classes must be qualified using the name of
the name space.
5.4.5 Extending Smalltalk with role modeling
The representation of the role modeling concepts in Smalltalk
differs from the one used for Java and C++. Smalltalk does not
provide interfaces, multiple inheritance, or name spaces. The
following discussion uses a generic Smalltalk dialect and ignores
vendor-specific extensions.
In Smalltalk, it is a convention to express abstract methods
by implementing them with the statement self subclassResponsibility.
When executed, this statement invokes the subclassResponsibility
method of the object, which will usually bring up the debugger
to indicate that a method was executed for which a subclass failed
to provide an implementation.
A class with only abstract methods is an abstract class. It
can be used to represent a role type. Specification 5-7 describes
the role type FigureObserver.Observer in Smalltalk.
"A FigureObserver is an object that is notified by a
Figure object about state changes of that Figure object.
@roletype FigureObserver.Observer
@properties free"
Object subclass: # FigureObserver
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
"FigureObserver publicMethods"
figureChanged: figureEvent
self subclassResponsibility
figureRemoved: figureEvent
self subclassResponsibility
figureInvalidated: figureEvent
self subclassResponsibility
- Specification 5-7: Definition of the abstract FigureObserver
class in Smalltalk to represent the FigureObserver.Observer role
type.
A class composes role types. Java and C++ do this using multiple
inheritance. Smalltalk does not provide multiple inheritance,
at least not the mainstream Smalltalk implementations VisualWorks
and VisualAge. Smalltalk's elaborate metalevel architecture makes
it possible to introduce multiple inheritance without breaking
the language. Schäffer presents an in-depth discussion of
how to do so [Sch98]. However, the introduction of multiple inheritance
is proprietary and frequently problematic.
As an alternative, developers can use tools to copy method
definitions from an abstract class representing a role type to
a regular class [RS95]. Such tools can emulate multiple inheritance,
but require additional maintenance efforts. However, tool support
is better than copying methods by hand, which requires an even
higher maintenance effort to keep definitions synchronized.
For structuring a class interface, Smalltalk uses method categories.
A method category is a (sub-)set of methods from the overall set
of methods of a class. Each class has a set of method categories.
A programmer assigns a method to a method category. It is customary
that a method is assigned to exactly one method category, but
most tools do not enforce this, and it is possible to have a method
be an element of more than one method category.
A method category can be used to group all methods of a particular
role type. On a design-level, the Figure class provides the role
types Figure.Figure, FigureHierarchy.Child, FigureObserver.Subject,
Graphics.Client, FigureChain.Predecessor, and ObjectProperty.Provider.
On a Smalltalk level, the Figure class provides the corresponding
method categories.
The method category GraphicsClient to represent the free no-op
role type Graphics.Client is empty, because no methods are assigned
to it. It is sensible to make a role type without methods explicit,
even if it leads to an empty method category. This is equivalent
to annotating the class interface with no-op role types.
It is a Smalltalk convention to have a method category called
"Accessing". This method category provides all methods
used for simple get and set access to instance variables of an
object. From a role modeling perspective, the Accessing method
category is problematic. It does not represent a role type but
rather cuts across the whole set of role types provided by a class.
The Accessing method category provides a good overview of the
abstract state of the class, even though it is not guaranteed
to give a complete overview.
It does not make sense to break this long-established convention.
However, it would be equally annoying, if a method category that
represents a role type would be incomplete, because its get and
set methods are assigned to the Accessing method category rather
than the role type's method category. It is therefore best to
keep the methods in both categories, serving both conventions
equally well.
Smalltalk does not provide name spaces. Rather, developers
use prefixes to tag classes as belonging to a particular name
space. This can be used to emulate name spaces. However, it is
fairly impractical to have a prefix of several words in front
of the actual class name, so that name spaces are only used on
a coarse-grained basis.
5.4.6 Extension properties
The following discussion shows how the extension properties
asked for in Section 5.2 are realized by the programming language
extensions. It thereby describes the rationale behind the extensions.
- Robustness. All three programming language extensions are
robust with respect to being used by developers who do not know
about the role modeling extension. The extensions use only such
features of the programming language that are widely understood.
In the case of Smalltalk, a metalevel extension could introduce
new role modeling concepts on top of the existing language. Such
an extension is not robust, because it requires developers to
understand the concepts and handle them appropriately. Section
5.1 discusses the pros and cons of this decision.
- Inconsistency. A program written in any of the three programming
languages may be inconsistent from the point of view of the role
modeling extension. However, if it is a proper program, it still
runs without problems. Therefore, developers can mix and match
role modeling with traditional concepts.
- Incompleteness. Source code may be inconsistent from a role
modeling perspective and therefore incomplete as well.
- Tool support. There is no dedicated tool support for role
modeling in current mainstream programming environments. These
environments only support the immediate language features. Therefore,
the programmer must maintain a role modeling perspective when
implementing designs.
However, maturing programming environments tend to introduce
new tools and concepts that reflect the pragmatics of using programming
language constructs better than could be derived from the language
definition. we can expect role modeling to provide significant
input to these tools.
- Bridge to design. Annotations provide sufficient means for
bridging back from implementation to design, both for developers
to look up a particular documentation aspect, and for tools for
code generation and round-trip software engineering.
Except for Smalltalk, none of the programming languages provides
an extension mechanism that lets us gracefully introduce new language
features. As a consequence, we take a conservative approach and
use basic language concepts only. Except for the area of tool
support, all desirable properties are achieved.
5.5 Summary
This chapter presents the extension of UML, Java, C++, and
Smalltalk with the role modeling concepts for framework design.
Using these extensions, developers can work in any of these design
notations or programming languages and apply the new role modeling
concepts where appropriate. The extensions are robust and can
cope with inconsistency and incompleteness.
By choosing to provide extensions of industry standards rather
than introducing a new isolated approach, Chapter 5 demonstrates
that role modeling for framework design is an evolutionary step
ahead in the definition of design methods. It adds to existing
methods, but does not replace them. It can be used without invalidating
existing investments.
This chapter is the last theory chapter. The following chapters
are case studies that show how role modeling works in practice.
|