2.5 Variation of Frameworks
2.5.3 Combining Framework Instances and Variations
Framework instances can be seen as variations of the framework functionality, but frame- works may also have variations on their own. Certain functionality of the framework may be optional or alternative. For example, in a graphical editing framework the edit- ing functionality may be optional, allowing to produce a read-only viewer if necessary. Variations of framework may capture various platform dependencies, such as customiza- tions for specific physical environments, operation system services, integrations to other applications, and so on.
It may be necessary to reuse framework instances with different variations of the frame- work, because these variations may also be necessary in the applications using the frame- work. For example, an application may need both editors and viewers of a particular graphical model. Such application would make use of the optionality of the editing fea- tures in the graphical editing framework. Similarly, an application may reuse variations of platform dependencies in order to support more platforms or be easier to adapt to them.
In Sec. 2.2, we explained how variations of a group of objects can be modularized by subclassing the classes of its members. The same technique can also be applied for modularization of variations of a framework: we can modularize the varying code in subclasses of the framework classes. In order to reuse an instance of a framework with a
1 class GraphicalEditorWithMenus extends GraphicalEditor {
2 Menu getContextMenu(Point pt) {
3 Graphic graphic = graphicAtPoint(pt);
4 return (graphic == null) ? getEditorMenu() :
5 (GraphicWithMenus)graphic.getContextMenu(pt);
6 } ...
7 }
8
9 abstract class GraphicWithMenus extends Graphic {
10 abstract Menu getContextMenu(Point pt);
11 }
Figure 2.22: Extension of the graphical editor with context menus
framework variation defined in such a way, we must combine the subclasses defined for the framework instance with the subclasses implementing the variation.
For an illustration, consider an optional feature of the graphical editor for support of context menus. The new feature can be implemented in a subclass of GraphicalEditor, e.g., GraphicalEditorWithMenus in Fig. 2.22. We may also need to extend other classes of the framework; e.g., we need to extendGraphicwith an abstract method getContextMenu
(line 10), so that each graphic object is asked to construct a menu to be shown when the user clicks on that object (line 5).
In the extension with context menus, we can observe the typing problems discussed in Sec. 2.4.1, e.g., on line 5 we need to cast the selected graphical object toGraphicWithMenus
for accessing its extended interface. A peculiarity of framework extensions, however, is that they can extend not only the implementation of the framework, but also the requirements to the instances of the framework. For example, in the extension with context menus we extend the requirements to the instances ofGraphic, because they must additionally implement the methodgetContextMenu specifying their context menu. Fig. 2.23 demonstrates how the framework instance for logic circuits can be extended with support for context menus. For each class instantiating Graphic for a certain type of circuit objects (i.e., each class from Fig. 2.20), we must define a class instantiating
GraphicWithMenusfor the same type of objects. For example,WireGraphicWithMenus(line 1) instantiatesGraphicWithMenusfor wires. It inherits fromGraphicWithMenusand defines the context menu for wires by implementinggetContextMenu. It also inherits fromWireGraphic
to reuse the instantiation of the base framework functionality for wires. Analogously, we define subclasses ofGraphicWithMenusfor logic components (lines 5, 9 and 18).
Then we must also define a new class CircuitEditorWithMenus (line 22), which repre- sents the circuit editors with context menus. This class inherits from CircuitEditor, the base circuit editor, and GraphicalEditorWithMenus to combine their functionality. In
1 class WireGraphicWithMenus extends WireGraphic & GraphicWithMenus {
2 Menu getContextMenu(Point pt) { ... }
3 }
4
5 abstract class LogicCompGraphicWithMenus extends NodeGraphic & GraphicWithMenus {
6 Menu getContextMenu(Point pt) { ... }
7 } 8
9 class LEDGraphicWithMenus extends LEDGraphic & LogicCompGraphicWithMenus {
10 Menu getContextMenu(Point pt) {
11 Menu menu = super.getContextMenu(pt)
12 menu.addAction(”Change Value”, changeValueAction());
13 return menu;
14 } 15 ...
16 } 17
18 class AndGateGraphicWithMenus extends AndGateGraphic & LogicCompGraphicWithMenus { }
19 20 ...
21
22 class CircuitEditorWithMenus extends CircuitEditor & GraphicalEditorWithMenus {
23 LogicCompGraphic createLogicGraphic(LogicComponent comp) {
24 if (comp instanceof LED) {
25 return new LEDGraphicWithMenus((LED)comp, this);
26 }
27 else if (logic instanceof AndGate) {
28 return new AndGateGraphicWithMenus((AndGate)comp, this);
29 } 30 ...
31 else {
32 throw new InconsistencyException(”Unknown type of logic component”);
33 }
34 } 35 ...
36 }
framework instance. For example, we redefine the methodcreateLogicGraphic(line 23), re- sponsible for instantiation of an adapter matching the type of the given logic component, so that it instantiates the classes with support for context menus.
The main problem with the design above is that it cannot guarantee that the additional requirements of a framework extension are indeed implemented in the instances of the framework that are used in combination with this extension. For example, the type system would not complain if we forget to instantiate GraphicWithMenus for any of the logic circuit objects, i.e., forget to implement one of the classes from Fig. 2.23. It also does not check whether we consistently update all methods instantiating the adapter classes, e.g., it would not complain if we remove the methodcreateLogicGraphicon line 23 or any of the cases within the method.
In fact, the type system does not prevent us from using the subclasses ofGraphicdefined for basic circuit editors (Fig. 2.20) in combination with a graphical editor with context menus. However, such use is not safe, because framework extensions assume the extended requirements to the framework instances. For example, the type cast at the line 5 of Fig. 2.22 would fail for the classes from Fig. 2.20, because they do not inherit from
GraphicWithMenus.
Another problem is the redundancy in the instantiation code. In the presented design, for every variation of a framework abstraction, e.g., support for menus in graphical ob- jects, we must redefine all classes instantiating that abstraction, even if they do not need to introduce any specific code. For example, the graphical object for AndGate does not need any specific context menu, thus the class AndGateGraphicWithMenus just combines
AndGateGraphic with LogicCompGraphicWithMenus without introducing any new function- ality. Redefinition of the methods like createLogicGraphic instantiating adapters to the framework can also be seen as a redundancy, because they are completely analogous to the overridden methods, just instantiate the corresponding redefined adapter classes.
In a lot of cases, framework variations can also define default behavior for the affected framework abstractions. For example, GraphicWithMenus could provide a default imple- mentation of getContextMenu, and some of the framework instances may not need to specialize this default behavior. However, we would still have to redefine the classes of the framework instances so that they inherit from the extended framework class, e.g., we would still have to redefine all application-specific instances ofGraphicto inherit from
GraphicWithMenus, which means that we would have a set of classes that do not introduce any new functionality and serve only as a glue code.