4.2 Dependent Classes in a Nutshell
4.2.3 Multi-Dispatch of Classes
To address the limitations of virtual classes, we propose a generalization of virtual classes, which we call dependent classes. A dependent class is a class whose structure depends on arbitrarily many objects; this dependency is expressed explicitly over class parameters, rather than by nesting. In a sense, dependent classes can be seen as a combination of virtual classes with multi-dispatch.
Figure 4.2 shows the implementation of LogicCompGraphic, the class of the graphical objects for logic components, as a dependent class. Instead of nesting declarations of the class within the classes of logic components, we declare that the class takes a logic component as a parameter. Such parameters are bound like conventional constructor parameters during instantiation of the class. They are also available during the lifetime of an object as immutable fields. For example, LogicCompGraphic takes two constructor parameters, a reference to the logic component it represents and a reference to the editor instance. These references can be accessed from the object over the fields comp
and editor.
Dispatch of a class is expressed through multiple declarations of the class for different types of parameters. In Fig. 4.2 we can see three declarations of LogicCompGraphic. The declaration of LogicCompGraphic with comp of type LogicComponent defines func- tionality common to the graphical object of all logic components. The declaration of
1 class CircuitEditor extends GraphicalEditor { ... }
2
3 class LogicCompGraphic(LogicComponent comp, CircuitEditor editor) extends NodeGraphic {
4 ConnectionGraphic(editor) getConnectionAt(int i1) { ... }
5 ...
6 }
7
8 class LogicCompGraphic(LED comp, CircuitEditor editor) {
9 Color valueColor;
10 void paint(Graphics g) { ... comp.getValue(); ... }
11 void setValueColor(Color c) { ... }
12 ...
13 } 14
15 class LogicCompGraphic(Subcircuit comp, CircuitEditor editor) extends CompositeGraphic {
16 Graphic(editor) getChildAt(int i1) { ... }
17 ...
18 }
Figure 4.2: Dependency of graphical objects on circuit component types via dependent classes
LogicCompGraphic withcomp of type LEDcontains the functionality specific to graphical objects ofLEDcomponents. It provides a specific implementation ofpaintmethod, as well as introduces new fields and methods, such as valueColor and setValueColor. Like in case of virtual classes, refinements of a dependent class can also introduce new inheritance relations, e.g., LogicCompGraphicforSubcircuitadditionally inherits from CompositeGraphic
and implements its methods.
In an analogous way, dependent classes can also be used to express dependency of graph- ical objects on the variations of their editors. For example, in an extension of a graphical editor with support for context menus, its graphical objects must additionally specify their context menus. Such a design is shown in Fig. 4.3. Class Graphic, the base class of all graphical objects, is parameterized by a reference to the editor. To extend the interface of graphical objects with support for context menus, the declaration ofGraphic
witheditorof typeGraphicalEditorWithMenusadditionally declares a methodgetContextMenu
(line 27), which is intended to be implemented in concrete subclasses.
Differently from virtual classes, dependent classes can be dispatched by all constructor parameters. For example, the graphical objects for circuit components depend both on the types of the components, as well as on the variations of the editor. In Fig. 4.2 we have seen specializations of the class LogicCompGraphicwith respect to the construc- tor parameter representing the circuit component. Analogously we can specialize the class by the second constructor parameter representing the editor as shown in Fig. 4.4. Class LogicCompGraphicwitheditor of typeCircuitEditorWithMenus (line 3) defines the spe- cific functionality of instances of the class in editors with support for context menus.
1 class GraphicalEditor { 2 ...
3 } 4
5 abstract class Graphic(GraphicalEditor editor) {
6 abstract void paint(Graphics g);
7 ...
8 } 9
10 abstract class NodeGraphic(GraphicalEditor editor) extends Graphic {
11 abstract ConnectionGraphic(editor) getConnectionAt(int i1);
12 ...
13 } 14
15 abstract class CompositeGraphic(GraphicalEditor editor) extends Graphic {
16 abstract Graphic(editor) getChildAt(int i1);
17 ...
18 } 19
20 abstract class ConnectionGraphic(GraphicalEditor editor) extends Graphic { ... }
21
22 class GraphicalEditorWithMenus extends GraphicalEditor {
23 ...
24 } 25
26 abstract class Graphic(GraphicalEditorWithMenus editor) {
27 abstract Menu getContextMenu(Point pt);
28 }
Figure 4.3: Expressing dependency of graphical objects on variations of editor functionality
1 class CircuitEditorWithMenus extends CircuitEditor, GraphicalEditorWithMenus { ... }
2
3 class LogicCompGraphic(LogicComponent comp, CircuitEditorWithMenus editor) {
4 Menu getContextMenu(Point pt) { ... } 5 }
6
7 class LogicCompGraphic(LED comp, CircuitEditorWithMenus editor) {
8 Menu getContextMenu(Point pt) { ... }
9 }
1 class CircuitEditor extends GraphicalEditor {
2 LogicCompGraphic(comp, this) graphicForComp(LogicComponent comp) {
3 LogicCompGraphic(comp, this) graphic = logicAdapters.getVal(comp);
4 if (graphic == null) {
5 graphic = new LogicCompGraphic(comp, this);
6 logicAdapters.putVal(comp, this);
7 }
8 return graphic;
9 }
10 void highlightValue(LED led) {
11 graphicForComp(led).setValueColor(HIGHLIGHT COLOR);
12 }
13 ...
14 }
Figure 4.5: Using the dependent classLogicCompGraphicin the circuit editor
In this class declaration we specify a default context menu for circuit components by implementing the methodgetContextMenu.
A dependent class can also be specialized by multiple parameters at once. For example,
LogicCompGraphicon line 7 of Fig. 4.4, declared withcomp of typeLEDand editor of type
CircuitEditorWithMenus, contains the functionality specific to graphical objects represent- ing LED components in editors with context menu. It provides a specific implementa- tion of getContextMenu for LED components, overriding the default implementation of the method.
Due to the double role of classes as templates for object instantiation and object types, the effect of class dispatch is also twofold: a class can be dispatched both dynamically during its instantiation and statically for determining interfaces of types based on the class. In the following, we will take a closer look at the semantics of the different aspects of using dependent classes.
Instantiation and Dynamic Dispatch. During object creation, class dispatch can be seen as collecting all declarations of a class matching the given constructor parameters. The created object inherits the fields and methods of all these declarations. This kind of dispatch depends on the runtime values of the constructor parameters. Note that differently from method dispatch, which selects a unique method implementation to be executed, the created object inherits all class declarations collected by class dispatch. For example, the definition of the instance of LogicCompGraphic created within method
graphicForComp on line 5 of Fig. 4.5 depends on the values of comp and this, i.e., the logic component given as a parameter to the method and the enclosing editor in- stance. If comp is an instance of LED, then the constructed object inherits the dec-
laration of LogicCompGraphic for LED (declared on line 8 of Fig. 4.2), its methods and fields. Analogously, if this is of type CircuitEditorWithMenus, then the created instance of LogicCompGraphic would inherit declaration of LogicCompGraphic with editor of type
CircuitEditorWithMenus (line 3 of Fig. 4.4). The declaration of LogicCompGraphic for LED
and CircuitEditorWithMenus (line 7 of Fig. 4.4) would be inherited by the constructed object, if both compand this are of respective types.
Types and Static Dispatch. In addition to the dispatch for the construction of objects, dependent classes also support parameterization and dispatch of types. Like in the case of virtual classes, a type can be a path or a class type (See Sec. 3.3.3). A path is a singleton type containing only the object referenced by the path expression. A class type can be parameterized by types of parameters of that class.1 For example, method
graphicForCompon line 2 takes a circuit component and returns a graphical object for the given component in the current editor. This contract of the method is specified by its return typeLogicCompGraphic(comp, this), which tells that the returned object is not only an instance of LogicCompGraphic, but also that its first parameter (component) is equal to the value of compgiven to the method, and its second parameter (editor) is equal to
this, i.e., the current editor.
Parameterization of types by paths allows abstracting from the variations represented by the paths without losing precision. The method graphicForComp is generic with respect to variations of the editor and the types of logic components, but typing precision is not lost, because it is dependently typed with respect to the polymorphic variables this and
comp, which hide the respective variations. For a specific method call, the return type will be specialized based on the types of the parameter bindings of the method call. For example, the call to graphicForComp on line 11 binds argument compto expressionled of type LED, while the type of the target object remains the same, i.e., this. The type of the method call is obtained, by substituting its parameters with specific bindings, i.e., its type would be LogicCompGraphic(LED, this).
In order to determine the interface of an instance of a type based on a dependent class, we must perform static dispatch of the class for the given parameters, i.e., we must collect all declarations of the class matching the statically known types of the parameters. The interface provided by the type is the sum of all these declarations. For example, to determine the interface of the object returned by the method call graphicForComp(led)
on line 11, we must perform dispatch of its type LogicCompGraphic(LED, this) for this
of type CircuitEditor, i.e., we must collect all declarations of the class LogicCompGraphic
matching comp of type LED and editor of type CircuitEditor. Since the declaration of
LogicCompGraphic on line 8 of Fig. 4.5 matches the type parameters, the methods and
1
1 abstract class Graphic(GraphicalEditor editor) {
2 Dimension size;
3 void setSize(Dimension size) { this.size = size; update(); }
4 void update() { repaint(); }
5 ...
6 }
7 8 ...
9
10 class GraphicalEditorWithLayout extends GraphicalEditor {
11 ...
12 }
13
14 abstract class CompositeGraphic(GraphicalEditorWithLayout editor) {
15 void layoutChildren() { ... }
16 void update() { layoutChildren(); repaint(); }
17 ...
18 } 19
20 class CircuitEditorWithLayout extends CircuitEditor, GraphicalEditorWithLayout {
21 void updateLayout(Subcircuit sc) {
22 graphicForComp(sc).layoutChildren();
23 } 24 ...
25 }
Figure 4.6: Circuit editor with support for layout
fields of this declaration are available on the returned object, which allows us to safely callsetValueColor on the returned object.
Inheritance and Its Role in Dispatch. The third way of using a class, besides instan- tiation and describing types, is inheriting from it. Inheritance affects both the dynamic and the static dispatch of classes, introduced above. During object construction, the constructed object inherits not only the declarations of the directly instantiated class, but also the matching declarations of its superclasses. Analogously, the interface of a type based on a dependent class also includes matching declarations of the superclasses of the class.
Like in case of virtual classes, a dependent class inherits variations of its superclasses. For example, on line 15 of Fig. 4.2,LogicCompGraphicis declared as subclass ofCompositeGraphic
for a component of type Subcircuit and an editor of type GraphicalEditor. The class
CompositeGraphic is also declared as a dependent class of an editor (See line 15) of Fig. 4.3). Thus, the dispatch by theeditorparameter is propagated fromLogicCompGraphic
toCompositeGraphic.
ple of Fig. 4.6, which shows an extension of graphical editors with support for automatic layout within composite graphical objects. The declaration of class
CompositeGraphicon line 14 extends the class for an editor of typeGraphicalEditorWithLayout
with layout functionality. It adds method layoutChildren to perform layout of the children, and overrides method update to recompute layout as a part of the up- date.2 This specialization of CompositeGraphic is implicitly propagated to subclasses of CompositeGraphic. For example, it is inherited by graphical objects for subcir- cuit components in a circuit editor with layout support, i.e., by instances of type
LogicCompGraphic(CircuitEditorWithLayout, Subcircuit).
Such implicit inheritance is achieved by propagation of (static and dynamic) class dispatch of classes over inheritance relations, e.g., an instance of type
LogicCompGraphic(CircuitEditorWithLayout, Subcircuit) matches the declaration of
LogicCompGraphic of line 15 of Fig. 4.2 and, therefore, is also an instance of
CompositeGraphic. Because of propagation of dispatch over inheritance, we also collect declarations ofCompositeGraphicmatchingeditorof typeCircuitEditorWithLayoutand so determine that the object inherits methods from the declaration of CompositeGraphic
of line 14 of Fig. 4.6. As a result, the update call on line 3 in the context of this of dynamic type LogicCompGraphic(CircuitEditorWithLayout, Subcircuit) would be bound to the implementation of updatefrom line 16. Also, we can safely call methodlayoutChildren on an instance ofLogicCompGraphic(CircuitEditorWithLayout, Subcircuit) on line 22.