3.2 Virtual Classes in a Nutshell
3.2.1 Large-Scale Inheritance
In object-oriented languages, the body of a subclass can be seen as a description of a difference to its superclass. The functionality of the subclass is intended to be equivalent to that of the class produced by changing the superclass according to that description: the new members are added and existing members are replaced. In this way extending a class by inheritance is almost3 as easy and flexible as changing the class directly. In Java, a subclass can replace the methods of its superclass, but not its inner classes. Declaration of an inner class in a subclass does not have the same effect as changing the corresponding inner class of the superclass. Instead, it is considered as a totally new unrelated class only accidentally having the same name.
The semantics of virtual classes in CaesarJ can be seen as generalization of the principle of treating a subclass as a description of the difference from its superclass for classes with inner classes. However, differently from methods, redeclaration of a virtual class in an heir family is not considered as a replacement for the virtual class with the same name of the parent family, but is again considered as a description of the difference from it. Thus, a redeclared virtual class can be seen as an extension or a refinement of the corresponding virtual class in the parent family. We call the former as a furtherbinding and the latter as its furtherbound.
For an illustration, recall the example of variation of menu functionality introduced in Sec. 2.4.1: A menu is a composite structure consisting of objects for various kinds of menu items and cascade menus. Each menu structure can be considered as a family of its constituent objects. In order to extend the functionality of these objects together, we group their classes by declaring them as virtual classes of one family class, as shown in Fig. 3.1.
We can see that the classes remain in principle unchanged, compared to the Java classes of Fig. 2.9, except that they are declared with the keywordcclass, which denotes that they are treated by the CaesarJ semantics. In contrast, the classes declared with keyword
classare considered as standard Java classes, for which the Java semantics is completely preserved, e.g., inner classes withclasskeyword are not considered as virtual. In this way full compatibility with Java code is preserved. The inheritance hierarchies of these two kinds of classes are strictly separated: a class declared with the keyword cclass cannot inherit from a class declared with the keywordclass, and the other way around.
Fig. 3.2 shows the extension of menus with support for accelerator keys, introduced in Sec. 2.4.1: each menu item can be associated with an accelerator key, which is used as a shortcut to execute the action of the menu item. We define this extension in a new family classMenusAccel, declared as a subclass ofBasicMenus.
3
1 cclass BasicMenus {
2 cclass MenuItem {
3 String label; Action action;
4 MenuItem(String label, Action action) {
5 this.label = label; this.action = action;
6 } 7 String displayText() { 8 return label; 9 } 10 void draw(Graphics g) { 11 ... displayText() ... 12 } 13 ... 14 } 15
16 cclass CascadeMenuItem extends MenuItem {
17 PopupMenu menu;
18 void addItem(MenuItem item) {
19 menu.addItem(item);
20 } 21 ...
22 } 23
24 cclass CheckMenuItem extends MenuItem { ... }
25
26 cclass RadioMenuItem extends MenuItem { ... }
27
28 abstract cclass Menu {
29 List<MenuItem> items; 30 MenuItem itemAt(int i) { 31 return items.get(i); 32 } 33 int itemCount() { 34 return items.size(); 35 }
36 void addItem(MenuItem item) {
37 items.add(item);
38 }
39 void addAction(String label, Action action) {
40 items.add(new MenuItem(label, action)); 41 }
42 ...
43 } 44
45 cclass PopupMenu extends Menu { ... } 46
47 cclass MenuBar extends Menu { ... }
48 }
1 cclass MenusAccel extends BasicMenus { 2 cclass MenuItem {
3 KeyStroke accelKey;
4 boolean processKey(KeyStroke ks) {
5 if (accelKey != null && accelKey.equals(ks)) {
6 performAction(); 7 return true; 8 } 9 return false; 10 } 11 void setAccelerator(KeyStroke ks) { 12 accelKey = ks; 13 } 14 void draw(Graphics g) { 15 super.draw(g); 16 displayAccelKey(); 17 } 18 ... 19 } 20 21 cclass CascadeMenuItem { 22 boolean processKey(KeyStroke ks) { 23 if (menu.processKey(ks)) { 24 return true; 25 } 26 return super.processKey(ks); 27 } 28 } 29
30 abstract cclass Menu {
31 boolean processKey(KeyStroke ks) {
32 for (int i = 0; i1 < itemCount(); i1++) {
33 if (itemAt(i).processKey(i1)) { 34 return true; 35 } 36 } 37 return false; 38 } 39 ... 40 } 41 }
The contents of MenusAccel are analogous to the functionality of accelerator keys pre- sented in Fig. 2.10. MenuItem is extended with fields and methods to maintain the associated accelerator key. MethodprocessKeyis introduced in menu items and menus to process the given input key by recursively traversing the menu structure and triggering the action of the menu item with the accelerator matching the key.
As can be seen, the classMenusAccel defines only the functionality related to support for menu accelerators. The overhead of defining an extension compared to a corresponding direct change is very minimal: the only additional code in MenusAccel is the headers of the family class and the refined virtual classes.
According to our intuitive definition of the semantics of virtual classes, the contents of MenusAccel can be seen as a description of a difference from BasicMenus, where the virtual classes of MenusAccel describe differences from the corresponding virtual classes of BasicMenus. The functionality of MenusAccel is equivalent to a class produced by taking BasicMenus and extending its classes MenuItem, CascadeMenuItem and Menu with the members of these classes from MenuAccel, whereas already existing class members are replaced.
Since the functionality of an heir family is equivalent to that of the class obtained by a corresponding invasive change of the parent family, extending a group of classes becomes almost as easy and flexible as changing the classes directly. In this way virtual classes support Open-Closed Principle [Mey97, Mar03] at the scale of a group of classes. Ac- cording the principle, modules should open for extension, but closed for change. In this way they can be reused in other contexts with different requirements without destabi- lizing the existing clients. From the perspective of variability management, this means that we can close the module implementing the functionality that is common to different clients and implementing client-specific variations as extensions to that module.