The essence of most module systems consists in a separation between
interfaces and implementations to enable true modularity in combination
with separate compilation. Keris uses this principle as an abstraction
mechanism even for classes. It destinguishes between class interfaces,
class implementations and class fields, which associate
the two.
The following program defines an interface for modules that provide a
Point class abstraction. It defines an interface IPoint
which specifies a signature consisting of two methods
getX() and getY(), and an object constructor. The second
declaration states that modules implementing POINTS also have
to define a class field which implements this interface.
|
module interface POINTS {
interface IPoint {
IPoint(int x, int y);
int getX();
int getY();
}
class Point implements IPoint;
}
|
We now look at a concrete implementation of POINTS. Module
MYPOINTS below defines a class implementation CPoint
that implements IPoint (which gets inherited from the
implemented module interface POINTS). Furthermore it
defines class field Point by separately specifying its
interface and implementation: The interface is given by IPoint
whereas CPoint acts as the concrete implementation (or the
runtime representation of class field Point).
|
module MYPOINTS implements POINTS requires INOUT {
class CPoint implements IPoint {
int x, y;
public CPoint(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
class Point implements IPoint = CPoint;
Point root() {
return new Point(0, 0);
}
void print(Point p) {
INOUT.write("(" + p.getX() + ", " + p.getY() + ")");
}
}
|
The implementations of the functions print and root show
that class fields behave just like regular Java classes: They define new types,
they can be instantiated, and members of corresponding objects can be accessed.
The main difference to regular Java classes is that class fields are virtual and
therefore can be covariantly overridden in refined modules. Covariant
overriding of class fields includes the extension of the set of implemented
interfaces as well as the ability to specify a new class field implementation.
Here is another module interface that describes a possible color aspect of points.
Please note that this new module interface COLORPOINTS is not at all
related to POINTS. It simply also specifies a class field Point,
which is, this time, constrained by interface IColor.
|
module interface COLORPOINTS requires COLOR {
interface IColor {
void setColor(COLOR.Color color);
COLOR.Color getColor();
}
class Point implements IColor;
}
|
We now refine the original MYPOINTS module by additionally implementing the new
COLORPOINTS module interface. Please note that MYCOLORPOINTS
implicitly requires module COLOR since its implemented module interface
MYPOINTS does so.
|
module MYCOLORPOINTS refines MYPOINTS implements COLORPOINTS {
import COLOR.*;
class CCPoint extends CPoint implements IColor {
Color color;
public CCPoint(int x, int y) {
super(x, y);
}
public void setColor(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
}
class Point implements IPoint, IColor = CCPoint;
void print(Point p) {
super.print(p);
INOUT.write(" col = " + p.getColor());
}
}
|
Refinement COLORPOINTS specifies that class field Point
now also supports the IColor interface and is implemented by the
CCPoint class. Furthermore, print is overridden to include the
color in the output. At this point, one might wonder what happens to method
root of the original module POINTS which instantiates class field
Point. In fact, for the refined module it now returns a colored point
since we were overriding class field Point.
The ability to covariantly refine types (or class fields in our case) is
essential for extending object-oriented software. Most object-oriented
languages support interface and implementation inheritance. But inheritance
alone does not support software refinement or software specialization well.
Existing code refers to the former type and often cannot be overridden
covariantly in a type-safe way to allow access to extended features.
6 Class Dependencies
|
|
The previous section explained how to declare and how to evolve
class abstractions. The presented mechanism does not allow to relate
different class fields to each other; every class field defines an own
independent interface/implementation pair. We need a mechanism similar
to subclassing/subtyping that introduces dependencies between class
fields. Since implementation inheritance is difficult to handle
modularly (with full abstraction), Keris only supports the explicit
declaration of subtype relationships between class fields. The
following code illustrates this.
|
module SHAPES requires POINTS {
interface IShape {
boolean inShape(POINTS.Point pt);
}
abstract class Shape implements IShape;
private java.util.Set shapes = new java.util.HashSet();
void registerShape(Shape s) {
shapes.add(s);
}
}
module BOXES requires SHAPES, POINTS {
import POINTS.*;
import SHAPES.*;
interface IBox {
IBox(Point topleft, Point botright);
}
class Box extends Shape implements IShape, IBox = {
Point topleft, botright;
public Box(Point topl, Point botr) {
topleft = topl;
botright = botr;
SHAPES.registerShape(this);
}
public boolean inShape(Point pt) {
return pt.getX() > topleft.getX() &&
pt.getX() < botright.getX() &&
pt.getY() > topleft.getY() &&
pt.getY() < botright.getY();
}
}
}
|
In this program, we define an abstract class field Shape within
module SHAPES. Abstract class fields are like abstract classes: They
simply define types, and cannot be instantiated. Thus, there is no need
to specify implementations for abstract class fields. Module
BOXES defines a class field Box which extends
SHAPES.Shape. This extends declaration defines
Box to denote a subtype of Shape requiring
that Box implements at least all the interfaces implemented by
Shape. The type checker has to make sure that it is not possible to
link refinements of BOXES and SHAPES where this invariant
is broken. Thus, such subtype dependencies between class fields promote
the consistent refinement or specialization of class field hierarchies.
Here is an example which successfully links modules BOXES and
SHAPES in the context of module LEGAL.
|
module LEGAL requires POINTS {
module BOXES;
module SHAPES;
}
|
The following "refinement" of module LEGAL is not well-formed,
because it breaks the dependency established between the class fields
Shape and Box.
|
module NUSHAPES refines SHAPES {
interface INuShape {
void scale(int factor);
}
abstract class Shape implements IShape, INuShape;
}
module ILLLEGAL refines LEGAL {
module NUSHAPES;
}
|
|