Earlier, we had the following data definition:
A Shape is on of:
a Circle: consisting of a center and a radius, where the center is a Posn and the radius is an integer
a Rectangle: consisting of a nw, a width, and a height, where the nw is a Posn, and the width and the height are integers
a Dot: consisting of a location, where the location is a Posn
When computing the distance of a shape to the origin, we noticed that each of the three variants contained a field that in some manner represented the location of this shape. We may decide to give these fields the same name (location instead of center or nw). Furthermore, because this field is common to all shapes, and we expect any further variants of the shapes to have some distinct point that specifies its location, we can lift this field to be a field in the abstract class. The class diagram and the class definitions will then be:
/* +---------------+ | AShape | +---------------+ | Posn location |----------------+ +---------------+ | / \ | --- | | | ----------------------------------- | | | | | +-------------+ +------------+ +---------+ | | Circle | | Rect | | Dot | | +-------------+ +------------+ +---------+ | | Int radius | | int width | +---------+ | +-------------+ | int height | | +-------+ +------------+ | | Posn |<---- ----------------------------------+ +-------+ | int x | | int y | +-------+ */ // to represent one point on a canvas class Posn ... we omit the rest of this definition ... // to represent geometric shapes abstract class AShape { Posn location; } // to represent a circle class Circle extends AShape { int radius; Circle(Posn center, int radius) { this.location = location; this.radius = radius; } } // to represent a rectangle class Rect extends AShape { int width; int height; Rect(Posn nw, int width, int height) { this.location = location; this.width = width; this.height = height; } } // to represent a dot class Dot extends AShape { Posn location; Dot(Posn location) { this.location = location; } }
We now want to compute the distance to origin for each of these shapes.
We only need a purpose statement for the abstract method.
// compute the distance of this Shape to the origin abstract int distTo0();
We develop each method separately, but show them here concurrently. We only show the templates and the programs, as all the examples and tests are the same as before.
There is a separate template for each of the three subclasses.
/* TEMPLATE for the Circle class: int distTo0() { ... this.location ... ... this.location.x ... ... this.location.y ... ... this.radius ... } */ /* TEMPLATE for the Rect class: int distTo0() { ... this.location ... ... this.location.x ... ... this.location.y ... ... this.width ... ... this.height ... } */ /* TEMPLATE for the Dot class: int distTo0() { ... this.location ... ... this.location.x ... ... this.location.y ... } */
Again, we have three different methods, one for each class.
// PROGRAM for the class Circle: int distTo0() { return this.location.distTo0(); } // PROGRAM for the class Rect: int distTo0() { return this.location.distTo0(); } // PROGRAM for the class Dot: int distTo0() { return this.location.distTo0(); }
Observing that the bodies of all three methods are the same, and do not rely on any information specific to these classes, we can lift the method to become a concrete method in the abstract class. So, we end up with the abstract class defined as follows - with no methods in the concrete classes:
// to represent geometric shapes abstract class AShape { Posn location; // compute the distance of this Shape to the origin int distTo0() { return this.location.distTo0(); } }