**************************************************************** Extended example. //////// // // Shape.java // //////// public interface Shape { // interface declaration double area (); } class Circle implements Shape { // class declaration Circle (double diameter) { // constructor this.diameter = diameter; } private static final double PI = Math.PI; // constant private double diameter; // instance variable public double area () { // dynamic method double radius = diameter / 2.0; return PI * radius * radius; } } class Rectangle implements Shape { Rectangle (double side1, double side2) { this.side1 = side1; this.side2 = side2; } private double side1; // length of one side private double side2; // length of the other side public double area () { return side1 * side2; } } class Square extends Rectangle implements Shape { Square (double side) { super (side, side); } } class Triangle extends Rectangle implements Shape { Triangle (double altitude, double base) { super (altitude, base); } public double area () { return 0.5 * super.area(); } } //////// // // TestShape.java // //////// public class TestShape { public static void main (String[] args) { Shape s1 = new Circle (3.0); Shape s2 = new Rectangle (3.0, 4.0); Shape s3 = new Square (5.0); Shape s4 = new Triangle (3.0, 4.0); System.out.println (s1.area()); System.out.println (s2.area()); System.out.println (s3.area()); System.out.println (s4.area()); } } **************************************************************** Execution rules. Example. Execution of TestShape.main. The output is 7.0685834705770345 12.0 25.0 6.0 Example. As a programming exercise to review our understanding of Java, we will transform the Shape example by precomputing the area creating an abstract ShapeClass from which the four classes that implement Shape can inherit creating an abstract area method implementing Shape in ShapeClass adding a (non-private) instance variable named area to ShapeClass removing the private instance variables named area creating a common area() method eliminating the specific area methods removing the superfluous initializations of unused variables removing the unused variables **************************************************************** Precomputing the area. These particular classes have the property that, once an instance has been created, its area never changes. If that is a genuine property of these classes, and not something that we're likely to change in the future, then it's kind of silly to recompute the area every time the area method is called. We could compute the area when an instance is created, and remember the area in an instance variable. With this change, the code for Circle and Rectangle becomes: class Circle implements Shape { Circle (double diameter) { this.diameter = diameter; double radius = diameter / 2.0; this.area = PI * radius * radius; } private static final double PI = Math.PI; private double diameter; private double area; // area of circle public double area () { return area; } } class Rectangle implements Shape { Rectangle (double side1, double side2) { this.side1 = side1; this.side2 = side2; this.area = side1 * side2; } private double side1; // length of one side private double side2; // length of the other side private double area; // area of rectangle public double area () { return area; } } **************************************************************** Creating an abstract base class. Both the Circle and Rectangle classes now have an instance variable named area, of type double. They also both have a method named area that takes no arguments and returns the value of that instance variable. As written, both the Circle and Rectangle classes use Object as their base class. When two classes that inherit from the same base class have things in common, we can create a new base class that sits between those classes and their original base class. The common features of the Circle and Rectangle classes can then be put into this new base class. To make this transformation as simple as possible, we will first insert the new base class, which I will call ShapeClass. To prevent clients from creating instances of ShapeClass, we will declare it abstract. abstract class ShapeClass { } class Circle extends ShapeClass implements Shape { ... } class Rectangle extends ShapeClass implements Shape { ... } **************************************************************** Creating an abstract area method. Notice that ShapeClass does not implement the Shape interface, not only because it doesn't claim to, but also because it doesn't implement the area method. All of its subclasses do implement the area method, however, and it is impossible to create an instance of ShapeClass itself (because it is declared abstract), so every value of type ShapeClass does implement the area method. We might as well declare that ShapeClass implements the Shape interface. Before we can do this, we'll have to declare an abstract area method within ShapeClass. abstract class ShapeClass { abstract public double area (); } **************************************************************** Implementing Shape in ShapeClass. Now we can declare that ShapeClass implements the Shape interface. abstract class ShapeClass implements Shape { abstract public double area (); } Once we have declared this, it is no longer necessary to declare that each subclass of ShapeClass implements the Shape interface, because that is implied by the fact that ShapeClass implements the interface. So we can remove the implements clause from the declarations of the Circle, Rectangle, Square, and Triangle classes. class Circle extends ShapeClass { ... } class Rectangle extends ShapeClass { ... } class Square extends Rectangle { ... } class Triangle extends Rectangle { ... } **************************************************************** Adding an instance variable. We can add an instance variable named area, of type double, to ShapeClass. This does no harm, because all of its subclasses will just ignore it, as they continue to use their own instance variable of that name. abstract class ShapeClass implements Shape { double area; abstract public double area (); } **************************************************************** Removing an instance variable. Of course, the reason we added this instance variable is that we intend to change the subclasses so they will no longer ignore it. That is why we didn't declare it private; we want it to be visible within subclasses. (If we want it to be visible within all subclasses, not just subclasses within this package, then we should make it less protected by declaring it protected.) To get the subclasses to use the instance variable named area in ShapeClass instead of their own instance variables named area, all we have to do is to remove that instance variable from the Circle and Rectangle classes. **************************************************************** Creating a common area() method. Now we can change the abstract area method of ShapeClass to a method that just returns the value of an instance variable. The subclasses of ShapeClass all override this area method, so it won't actually be called, but we'll change that in a moment. abstract class ShapeClass implements Shape { double area; public double area () { return area; } } **************************************************************** Eliminating the specific area methods. The area methods of the Circle and Rectangle classes are exactly the same as the area method in their base class, so they might as well inherit that method from the base class instead of overriding it. Thus we can remove the area methods from the Circle and Rectangle classes: class Circle extends ShapeClass { Circle (double diameter) { this.diameter = diameter; double radius = diameter / 2.0; this.area = PI * radius * radius; } private static final double PI = Math.PI; private double diameter; } class Rectangle extends ShapeClass { Rectangle (double side1, double side2) { this.side1 = side1; this.side2 = side2; this.area = side1 * side2; } private double side1; // length of one side private double side2; // length of the other side } **************************************************************** Removing superfluous initializations of unused variables. The instance variables named diameter, side1, and side2 are initialized but never used. There isn't much point to initializing a variable that is never used, so we can eliminate the initialization code. class Circle extends ShapeClass { Circle (double diameter) { double radius = diameter / 2.0; this.area = PI * radius * radius; } private static final double PI = Math.PI; private double diameter; // unused } class Rectangle extends ShapeClass { Rectangle (double side1, double side2) { this.area = side1 * side2; } private double side1; // unused private double side2; // unused } **************************************************************** Removing unused variables. Once we have eliminated the initialization of a variable, it is very poor style to leave the uninitialized variable in the program, so we should eliminate the uninitialized variables as well. class Circle extends ShapeClass { Circle (double diameter) { double radius = diameter / 2.0; this.area = PI * radius * radius; } private static final double PI = Math.PI; } class Rectangle extends ShapeClass { Rectangle (double side1, double side2) { this.area = side1 * side2; } } **************************************************************** Composition of transformations. Each of these transformations was simple enough for us to see that the transformation preserves the behavior of the program. The composition of all these transformations gives us the code shown below. Note that the area of a Triangle is not fully precomputed. Precomputing that area is left as an exercise for the reader. //////// // // Shape.java // //////// public interface Shape { // interface declaration double area (); } abstract class ShapeClass implements Shape { double area; public double area () { return area; } } class Circle extends ShapeClass { Circle (double diameter) { double radius = diameter / 2.0; this.area = PI * radius * radius; } private static final double PI = Math.PI; } class Rectangle extends ShapeClass { Rectangle (double side1, double side2) { this.area = side1 * side2; } } class Square extends Rectangle { Square (double side) { super (side, side); } } class Triangle extends Rectangle { Triangle (double altitude, double base) { super (altitude, base); } public double area () { return 0.5 * super.area(); } } **************************************************************** Was this a good idea? Although these transformations preserve the behavior of the program, they do not preserve the flexibility of the program with respect to the kind of changes that we might need to make to the program in the future. For example, an instance of the Rectangle class no longer knows the dimensions of its sides. All it knows is its area. If we need to add a new feature to the program that requires knowing the dimensions of the sides of a Rectangle, then we will need to undo the transformations that eliminated the side1 and side2 instance variables and their initializations. Software design and development is an engineering discipline, with both art and (computer) science. Computer science tells us that each of these transformations preserves the correctness of the program, but judging which of these transformations are good and which are bad for any particular program is an art. ****************************************************************