<< Mixin Composition | Contents | Dependent Types >>
Virtual Classes
Overridable Classes
Caesar classes as well as plain Java classes can contain inner classes. The difference is that Caesar considers all inner classes as virtual classes, which can be overridden in the subclasses of the enclosing class. What does it mean and why would we need that?
Let’s consider a possible implementation of a
Button
class. It defines inner classes
FlatBorder
and
PushedBorder
to implement different border drawing for different button states and inner class
Border
as their common abstraction:
class Button {
private Border _border;
/* ... */
public void push() {
setBorder(new PushedBorder());
}
public void release() {
setBorder(new FlatBorder());
}
public void display(Graphics g) {
g.drawText(posX, posY, buttonText);
_border.display(g);
}
class Border {
public abstract void display(g);
public Color getBaseColor() {
/* retrieve system color for buttons */
}
}
class FlatBorder extends Border {
public void display(Graphics g) {
/* draw raised border */
}
}
class PushedBorder extends Border {
public void display(Graphics g) {
/* draw pushed border */
}
}
}
Let’s say I want to reuse this class in my application, but I want to display
PushedBorder
in a different way. I can define a new version of
PushedButton
in my subclass of
Button
:
class MyButton extends Button {
class PushedBorder extends Border {
public void display(Graphics g) {
/* draw pushed border in my way */
}
}
}
Such solution won’t work in Java, because the new
PushedBorder? class version won’t be used by the methods of
Button
. Therefore I also have to override
push()
and all other methods where
PushedBorder
is instantiated. Basically if I don’t want to have problems we will have to define factory methods and use them single points for instantiation inner classes.
Now let’s imagine I need to provide possibility to customize user interface colors in my application.
Border.getBaseColor()
should return a color from application settings rather than from the system. I would like to redefine the Border class with the new functionality:
class MyButton extends Button {
class Border {
public Color getBaseColor() {
/* retrieve border color from application settings */
}
}
}
However, we already know that this won’t work in Java and there is no quick work around. I have to reimplement both
FlatBorder
and
PushedBorder
so that they now inherit from the new
Border
class and then override all methods, which instantiate these classes. Another alternative is directly changing
Border
class in the
Button
, so that it supports my application requirements, but directly changing reusable classes is not a very good idea.
As we can see we already run into extensibility problems with such relatively simple example. Imagine that you want to adapt to your needs a large object-oriented framework with rich inheritance hierarchies without changing it. You will definitely have much more severe extensibility problems.
This problem does not exist in Caesar. The both examples above will work in Caesar (with keyword
class
replaced to
cclass
), because all inner Caesar classes are considered as virtual classes and can be overridden. The overridden class is automatically used in all places where its old version was used. That means that
push()
of
MyButton
will instantiate
PushedBorder
of
MyButton
and
PushedBorder
of
MyButton
will inherit from
Border
of
MyButton
.
Those, who are curious how a class can be replaced in inheritance relationships, should remember the previous chapter about mixins. As we already know, all class declarations can be considered as mixins – classes with parameterizable superclass. In this way the classes
Button.FlatBorder
and
Button.PushedBorder
are also considered as mixins, which can be combined with any subtype of
Button.Border
. On the other hand all overridden classes are considered as subtypes of their older versions, so
MyButton.Border
is a subtype of
Button.Border
and, therefore, can replace
Button.Border
in its subclasses.
Features of Virtual Classes
Let's look to another example to study the possibilities of virtual classes. We will use virtual classes to extend a collaboration with a totally new funtionality. Let’s say we have a core data model to represent expressions:
public cclass ExprModel {
abstract public cclass Expr {
}
public cclass Constant extends Expr {
protected int _val;
public Constant(int val) {
_val = val;
}
}
abstract public cclass BinaryExpr {
protected Expr _left;
protected Expr _right;
public BinaryExpr(Expr left, Expr right) {
_left = left;
_right = right;
}
}
public cclass Add extends BinaryExpr {
}
public cclass Mult extends BinaryExpr {
}
}
The collaboration defines
Expr
as the base class for all expressions, concrete classes to represent constants, addition and multiplication. Class
BinaryExpr
implements the common functionality of all expressions with two operands. Note that the current version of Caesar does not support constructors with parameters and abstract methods in
cclass
. The code below demonstrates how sample expressions can be built using such collaboration:
public model.Expr buildSampleExpr(final ExprModel model) {
model.Expr const1 = model.new Constant(-3);
model.Expr const2 = model.new Constant(2);
model.Expr op1 = model.new Mult(const1, const2);
model.Expr const3 = model.new Constant(5);
model.Expr op2 = model.new Add(op1, const3);
return op2;
}
The collaboration defines
Expr
as the base class for all expressions, concrete classes to represent constants, addition and multiplication. Class
BinaryExpr
implements the common functionality of all expressions with two operands.
There are a lot of different functionality related with expressions: their evaluation, formatting expressions to simple text in infix or postfix order, various consistency checks, lookups and transformations. We want to keep all this specific functionality separated from each other and from the core data model. This can be achieved with the help of virtual classes. For example, the collaboration below extends the core model with simple expression formatting functionality:
public cclass ExprFormat extends ExprModel {
abstract public cclass Expr {
abstract public void String format();
}
public cclass Constant {
public void String format() {
return _val < 0 ? “(“ + _val + “)” : “” + _val;
}
}
abstract public cclass BinaryExpr {
public void String format() {
return “(” + _left.format() + getOperSymbol()
+ _right.format() + “)”;
}
abstract public void String getOperSymbol();
}
public cclass Add {
public void String getOperSymbol() { return “+”; }
}
public cclass Mult {
public void String getOperSymbol() { return “*”; }
}
}
This short example demonstrates various features of virtual classes:
- There is no need to repeat inheritance relationships between virtual classes if they are already defined in the supercollaboration. For example
ExprModel
defines Constant
as subclass of Expr
. It means that Constant
is implicitly assumed as subclass of Expr
in ExprFormat
as well.
- Virtual classes can use the fields and methods defined in their older versions. For example
ExprFormat.BinaryExpr
can use fields _left
and _right
defined in ExprModel.BinaryExpr
.
- The functionality defined in the overridden virtual classes can be accessed without type casts. For example, fields
_left
and _right
of BinaryExpr
were initially declared with type Expr
of ExprModel
, which does not have method format(), but in the context of ExprFormat
the new version of Expr
is assumed as the type of _left
and _right
. So format()
can be called without any type casts.
- The methods introduced in the overridden virtual classes can be again overridden in the new versions of subclasses. For example overridden
Expr
introduces method format()
, which can be overridden in BinaryExpr
. While Add
and Mult
do not override this method further, they inherit the format()
of BinaryExpr
.
Besides the demonstrated properties, the overridden virtual classes can also
- introduce new data fields,
- implement new interfaces,
- introduce new inheritance relationships.
<< Mixin Composition | Contents | Dependent Types >>