The UML-F tags in the above sections are useful for any object-oriented model including frameworks. This section presents basic UML-F tags that are only useful in the context of frameworks. They are used by the framework developer to document the intended use of certain framework concepts for the application developer. These tags therefore give guidance to the application developer through the framework adaptation process. Furthermore, they give framework developers guidance for the enhancement of a framework, as they also allow one to document intentions to codevelopers. It is important to bear in mind the fact that framework developers are the masters of the framework and can therefore change the framework and its decorating tags, whereas application developers may not.
Many frameworks come together with prefabricated application classes that do not belong to the framework itself. These additional classes can be studied in order to understand the standard usage of a framework by examining and adapting their code, whereas the framework classes themselves are usually not subject to change.
The tag application marks application-specific classes. During the framework adaptation process, this tag can mark newly introduced classes as well. The framework tag marks classes and interfaces that are generally adapted through the definition of new subclasses and the implementation of interfaces. A third category of classes belongs to the utility level, as these classes are provided as basic classes by utility libraries or the runtime system. These may be tagged utility . Figure 3.11 exemplifies their usage. If a package is marked with one of these tags, then all of its classes and interfaces are implicitly marked as such. Table 3.8 summarizes the meaning of these UML-F tags.
Tag name | Applies to | Value type | Description |
---|---|---|---|
application | Class, package, interface | Boolean | The class/interface/package does not belong to a framework, but to an application. |
framework | Class, package, interface | Boolean | The class/interface/package belongs to the framework. |
utility | Class, package, interface | Boolean | The class/interface/package belongs to a utility library or the runtime system. |
One important principle of framework design underlies the tags presented in this section: no framework element should be directly changed for an application by modifying its source code. This principle is necessary to enable reuse of the framework in different applications. However, this principle can only be applied if the framework is mature because the development of a framework usually goes hand-in-hand with the development of a number of applications.
In framework, classes, methods, and interfaces are adapted through the definition of new (sub)classes and the implementation of interfaces. The tags in Table 3.9 express whether an element is fixed (tag fixed ) or whether it can be adapted in subclasses. Two kinds of adaptations are distinguished: those made during runtime of a system (tag adapt-dyn ); and those made during the design or evolution of a system (tag adapt-static ). The following subsections discuss these tags in more detail.
Tag name | Applies to | Value type | Description |
---|---|---|---|
fixed | Class, method, generalization | Boolean | The element is fixed. Methods may not be changed in subclasses. In a generalization relation new subclasses may not be added. |
adapt-static | Interface, class, method, generalization | Boolean | The element can be adapted during design time through subclassing or interface implementation. A method can be overridden in subclasses. New methods and attributes may be added to the subclass and existing methods may be overridden. During runtime the element is fixed. |
adapt-dyn | Interface, class, method, generalization | Boolean | The interface, class, method can be changed at runtime, for example through dynamic loading of new subclasses. Additional subclasses typically override methods. |
Dynamic adaptations during runtime typically require extra effort and need special treatment. Not every implementation language and/or runtime system allows a dynamic adaptation. Java supports it through its meta-information system and the dynamic loading of classes.
This section discusses the above tags in the context of methods. Methods can be fixed , or can have the adapt-static or adapt-dyn tag attached to them.
For many methods a framework already provides an implementation. To keep that implementation flexible, the method can be adapted through hook methods[12] that are called in the methods body. A method can be explicitly tagged as a fixed method if it should not be changed through adaptation or overriding, neither during runtime nor during design time.
[12] Hook methods are described in detail in Chapter 4.
If the framework itself provides more than one possible implementation of a fixed method in several subclasses, the application developer may choose (or the framework chooses) which implementation to use, but adding new implementations of that method are not allowed. Here, the fixed tag differs from the Java modifier final, which does not even allow framework subclasses to override a method. UML-F allows overriding of a fixed method within the framework, but the fixed tag also holds for the overridden subclass methods so that the application components cannot override it. Figures 3.12 and 3.13 illustrate the semantics of the UML-F tag fixed .
The adapt-static tag allows the application developer to modify a method in a new subclass during design time (see Figure 3.14). This tag marks methods in framework classes that are intended to serve as a connection to the application. Such methods are often abstract, or provide only a dummy implementation. It is also possible that the default implementation of the method is rather elaborate and should be used through a super()-call if overridden.[13]
[13] As the adapt-static tag corresponds to overriding of methods in subclasses, we were tempted to introduce the tag override instead, but for the sake of uniformity we chose adapt-static .
On rare occasions implementations vary widely, even during system operation. Then it might not be possible to think of all cases up front. Instead, system designers offer a dynamic adaptation of the system. If the framework is designed in such a way that new implementations of a method can be added during runtime then that method can be marked as adapt-dyn . Such a dynamic implementation occurs, for example, if the system should run without shutdowns.
Dealing with this form of adaptation is straightforward in languages such as Smalltalk and the Common Lisp Object System (CLOS). Java also provides the flexibility through its meta-information system and the dynamic loading of classes. A class that overrides an adapt-dyn method can be loaded while the system is running. A command interpreter is a workaround for dynamic adaptation in languages without an appropriate meta-information system (such as C++) and with runtime systems which lack dynamic loading capabilities. The dynamic adaptation is achieved by changing the command string.
Dynamic loading of methods introduces some overhead. So one should check carefully if the application of this technique is necessary. Furthermore, if the framework tags a method with adapt-dyn , the framework itself should provide either a mechanism to load respective subclasses dynamically or an adaptable command interpreter as part of the framework.
Methods in framework classes are marked as fixed by default this adds a security level to framework use. If the framework developer wants to allow adaptation, this has to be enabled explicitly. Furthermore, in larger frameworks it appears that more than the half of the methods are meant to be fixed . For methods, the fixed tag does carry over to subclasses, whereas the other two tags do not. Therefore an adapt-dyn method may be overridden by a fixed method in a subclass, but not vice versa.
A framework may have a large number of methods tagged as adapt-static or adapt-dyn . It is not a requirement to actually adapt all these methods. Instead, the decisions as to which methods to adapt depend on the application being developed. The methodological guidelines that accompany a good framework for example, in the form of a cookbook should provide hints about adaptations for application-specific requirements.
From a conceptual viewpoint, classes and interfaces share the characteristic that they define a type through their signature. During system modeling, classes and interfaces can be treated in a rather uniform way. Nevertheless, it makes no sense to apply the fixed tag to interfaces because an interface cannot provide a method implementation and any class implementing the interface therefore must be allowed to define its own method implementation.
If none of the methods that a class provides are to be redefined in an application, then the class can be marked with the fixed tag (see Figure 3.15). The fixed tag for classes thus serves as a shortcut for attaching the tag to each of its methods. This allows the addition of new subclasses and the definition of new methods within these subclasses, but it does not allow the modification of any inherited methods. Although a class may be fixed , the framework may provide a number of subclasses where methods are already overridden.
As a consequence of being a shortcut for attaching the tag to all methods, the fixed tag for classes does not carry over to its subclasses directly. Instead, it propagates only to all methods that are inherited (see Figure 3.16). The practical use of this tag is restricted to core classes in the inheritance hierarchy, whose methods are not intended for adaptations, and to highly specialized classes that have no subclasses at all (e.g. the String class in Java).
Marking a class or an interface with the adapt-static tag acts as a shortcut for marking all its methods as adapt-static .[14] In this case, all its methods can be adapted by the application developer through adding (sub)classes. However, the adapt-static tag can be overruled for individual methods in the class. If a method is tagged as fixed , then this method may not be adapted explicit method tags overrule class tags when they are more restrictive than the class tag (see Figure 3.17). This means that if a class has the tag adapt-static , then all its methods must be adapt-static or fixed .
[14] For the sake of readability, the following presentation mostly refers to 'classes' synonymously to 'interfaces', although a replacement of the terms 'class' by 'interface' would imply further changes of the description. For example, classes implement interface methods instead of overriding them.
Finally, the adapt-dyn tag can be attached to a class or an interface to express the possibility of dynamic adaptation during runtime. The use of adapt-dyn for a class/interface reveals the existence of at least one method with the same tag, thus encouraging dynamic loading of classes during runtime. The tag adapt-dyn applied to a class promotes to all its methods that are not marked differently. In practice, however, such classes often have only one or a few methods intended for dynamic adaptation.
So far, the focus has been on tags for methods, classes and interfaces; and the tags for classes and interfaces as shortcuts for method tags. All these tags specify the degree of adaptability of particular methods in classes or interfaces. Thus, during a framework adaptation it is of interest to understand where classes should be added. For this purpose, UML-F provides the tags fixed , adapt static , and adapt dyn as attributes of generalization relationships.
The adapt-static tag is applied to a generalization to express the idea that adding application-specific classes during design time is allowed. This tag allows the addition of new subclasses with application-specific features. In particular, a generalization relationship should be marked by adapt-static if the corresponding superclass should be adapted in subclasses. Figure 3.18 illustrates the use of this UML-F tag in the context of a generalization. As there is no detailed information about how and why to extend the generalization relationship, the information conveyed with the adapt static tag is somewhat weak. Therefore, for example, the tags defined for the Composite pattern are based on the adapt static tag, but include additional information on how to add classes to adapt the inheritance structure, as shown in Chapter 4.
Sometimes a framework is designed in such a way that no direct subclass is permitted to be added to a given class. Figure 3.19 illustrates the constraint imposed by the fixed tag.
As a second example, in the Visitor pattern (Figure 3.20, adapted from Gamma et al. 1995), an instance of class Visitor navigates through an object structure. The classes representing the objects that form the object structure should incorporate a fixed generalization if no additional subclasses of class Element are expected to be added. Another reason to prevent a generalization to be extended by the application developer is the existence of appropriate mechanisms to adapt its classes by composition or parameterization.
As demonstrated above, the UML-F tag fixed expresses a restriction on the generalization relationship. The application developer cannot add direct subclasses, but can still add application-specific subclasses to already existing framework subclasses. The class diagram in Figure 3.21 exemplifies the abstract syntax of a programming language. Though the upper level generalization relationship is fixed , the generalization relations between the subclasses of Nonterminal and their subclasses have the attribute adapt static , which allows language-specific extensions in the predefined Nonterminal categories Expression and Statement.
If the tag adapt-dyn is attached to a generalization, subclasses may be added during runtime. If a language and its runtime system support such extensions, an adaptation during runtime carries a risk of behavioral errors and exceptions that needs to be dealt with. Usually, the adapt dyn tag is attached to a method, its defining class, and the generalization relationship altogether (see Figure 3.22). Omissions are possible because an attachment to the method is sufficient to convey the desired information.
If no tag is explicitly specified for a generalization, then the tag that implicitly applies depends on the tag of the superclass in that generalization. If the superclass is marked as adapt dyn then the generalization is marked as adapt dyn . If the superclass is fixed or adapt static then the generalization is adapt static . An explicit tag for the generalization, such as adapt static , always overrides the (invisible) promoted default.
As a convention, every class has a subclass tree and is therefore the root of a generalization relationship. If the subclass tree is empty, the generalization is empty too, but it still exists. Figure 3.23(a) shows how to mark a generalization relationship so that no subclasses can be defined. However, for this special case, UML-F provides a more intuitive notation, as shown in Figure 3.23(b). This is in accordance with the Java language which provides the modifier 'final' applicable to classes, indicating three constraints:
the class is fixed
the attached generalization relationship is fixed
the generalization is empty that is, no subclasses exist.
Standard UML already provides some constraints on generalizations.[15] The complete constraint roughly corresponds to UML-F's fixed tag, but with one important difference the complete constraint applied to a class carries over to all subclasses and, therefore, does not allow the framework developer to decide which subclasses are adaptable. Thus, the inheritance structure of Figure 3.21 cannot be expressed within standard UML. The effect of the complete constraint, on the other hand, can be recreated by applying the fixed tag to all subclasses as well.
[15] It was previously explained that the distinction between constraints, stereotypes, and tagged values is not sufficiently clear in the standard UML. UML-F therefore only uses tags even for concepts similar to UML constraints.