import draw.*; /* --- CSU213 Spring 2005 Lecture Notes --------- Copyright 2005 Viera K. Proulx Lecture 7: All together now ... */ /* Goals: - learn to define and use methods in unions of classes - abstract versus concrete method in an abstract class We now learn to design methods that manipulate data represented by a union of classes. Let us first define the classes and examples of instances of these classes. We are interested in representing data that defeines geometrical shapes, such as dots, circles, and rectangles. We plan to add additional shapes later. For each shape we need to know its location in the drawing window. For each circle, we need to know its radius, for a rectangle we need to know its width and height. We could add color as well, but for now it is not necessary. The data definition and the classes that represent geometric shapes wil then be: /* +---------------+ | AShape | +---------------+ | Posn location | +---------------+ / \ --- | --------------------------------- | | | +-----+ +------------+ +------------+ | Dot | | Circle | | Rectangle | +-----+ +------------+ +------------+ +-----+ | int radius | | int width | +------------+ | int height | +------------+ */ /* // to represent a geometric shape * abstract class AShape { * Posn location; * /* * // ** DRAFT TEMPLATE ** Edit as needed. * // purpose statement * abstract ??? mmm(); * * / * } * * // to represent a dot * class Dot extends AShape { * * Dot(Posn location) { * this.location = location; * } * * /* * // ** DRAFT TEMPLATE ** Edit as needed. * ??? mmm() { * } * * / * } * * // to represent a circle * class Circle extends AShape { * int radius; * * Circle(Posn location, int radius) { * this.location = location; * this.radius = radius; * } * * /* * // ** DRAFT TEMPLATE ** Edit as needed. * ??? mmm() { * ... this.radius ... * } * * / * } * * // to represent a rectangle * class Rectangle extends AShape { * int width; * int height; * * Rectangle(Posn location, int width, int height) { * this.location = location; * this.width = width; * this.height = height; * } * * /* * // ** DRAFT TEMPLATE ** Edit as needed. * ??? mmm() { * ... this.width ... * ... this.height ... * } * * / * } */ /* Here are some examples of shapes: */ /* class Examples { Examples(){} AShape d1 = new Dot(new Posn( 40, 60)); AShape d2 = new Dot(new Posn( 90, 30)); AShape c1 = new Circle(new Posn( 40, 60), 30); AShape c2 = new Circle(new Posn( 140, 60), 50); AShape r1 = new Rectangle(new Posn( 40, 60), 40, 60); AShape r2 = new Rectangle(new Posn( 90, 160), 70, 30); } */ /* Problem 1: Find out whether some point lies within the bounds of some shape. 1. Problem analysis: For each of the three shapes, the method needs to consume the Posn that represents the point in question, and in each case the method returns a boolean value, even though the computation will be different for each of the three cases. 2. Purpose and header: // determine whether the given point lies within this shape boolean isWithin(Posn that){...} 3. Examples: We define separate examples for each of the three shapes: Posn p = new Posn(40, 60); d1.isWithin(p) --> true d2.isWithin(p) --> false c1.isWithin(new Posn(60, 50)) --> true c1.isWithin(p) --> true c2.isWithin(p) --> false r1.isWithin(new Posn(60, 70) --> true r1.isWithin(p) --> true r2.isWithin(p) --> false 4. Templates class Dot class Circle class Rectangle --------- ------------ --------------- ... this.location ... ... this.location ... ... this.location ... ... this.location.x ... ... this.location.x ... ... this.location.x ... ... this.location.y ... ... this.location.y ... ... this.location.y ... ... this.radius ... ... this.width ... ... this.height ... 5. Method bodies: class Dot --------- return this.location.x == that.x && this.location.y == that.y; class Circle ------------ return Math.sqrt( (this.location.x - that.x) * (this.location.x - that.x) + (this.location.y - that.y) * (this.location.y - that.y)) < this.radius; class Rectangle --------------- return (this.location.x <= that.x) && (that.x <= this.location.x + this.width) && (this.location.y <= that.y) && (that.y <= this.location.y + this.height); Math.sqrt(x), Math.abs(x), Math.sin(a) evaluate the typical mathematical functions we may need. (Ignore the complexities of the syntax for now.) We will include separately a table of the most common ones, together with the purpose statements and the contracts, so you can use them appropriately. Interdiction: Before we can write the tests, we must make sure that the abstract class AShape makes sure that every subclass contains the method // determine whether the given point lies within this shape boolean isWithin(Posn that){...} Although we know what the purpose and the header will be, there is nothing we can say about the body of the method -- indeed we know it will be different for each of the subclasses. To indicate this, we must declare the method in the abstract class AShape as 'abstract' -- with no body: // determine whether the given point lies within this shape abstract boolean isWithin(Posn that); 6. Tests: Let us now put together all the methods and the tests. Looking at the method for the class Rectangle, we see that our tests are not sufficient - for completeness we should have tests that cover each of the nine situations shown below. For practice, determine which of these cases are covered by our examples and add all the missing ones. * * * +---------------+ * | * | * +---------------+ * * * The classes and the examples will then be: */ // to represent a geometric shape abstract class AShape { Posn location; // determine whether the given point lies within this shape abstract boolean isWithin(Posn that); /* // ** DRAFT TEMPLATE ** Edit as needed. // purpose statement abstract ??? mmm(); */ } // to represent a dot class Dot extends AShape { Dot(Posn location) { this.location = location; } // determine whether the given point lies within this shape boolean isWithin(Posn that){ return this.location.x == that.x && this.location.y == that.y; } /* // ** DRAFT TEMPLATE ** Edit as needed. ??? mmm() { } */ } // to represent a circle class Circle extends AShape { int radius; Circle(Posn location, int radius) { this.location = location; this.radius = radius; } // determine whether the given point lies within this shape boolean isWithin(Posn that){ return Math.sqrt( (this.location.x - that.x) * (this.location.x - that.x) + (this.location.y - that.y) * (this.location.y - that.y)) < this.radius; } /* // ** DRAFT TEMPLATE ** Edit as needed. ??? mmm() { ... this.radius ... } */ } // to represent a rectangle class Rectangle extends AShape { int width; int height; Rectangle(Posn location, int width, int height) { this.location = location; this.width = width; this.height = height; } // determine whether the given point lies within this shape boolean isWithin(Posn that){ return (this.location.x <= that.x) && (that.x <= this.location.x + this.width) && (this.location.y <= that.y) && (that.y <= this.location.y + this.height); } /* // ** DRAFT TEMPLATE ** Edit as needed. ??? mmm() { ... this.width ... ... this.height ... } */ } class Examples { Examples(){} AShape d1 = new Dot(new Posn( 40, 60)); AShape d2 = new Dot(new Posn( 90, 30)); AShape c1 = new Circle(new Posn( 40, 60), 30); AShape c2 = new Circle(new Posn( 140, 60), 50); AShape r1 = new Rectangle(new Posn( 40, 60), 40, 60); AShape r2 = new Rectangle(new Posn( 90, 160), 70, 30); Posn p = new Posn(40, 60); // test the method isWithin boolean test() { return this.d1.isWithin(this.p) == true && this.d2.isWithin(this.p) == false && this.c1.isWithin(new Posn(60, 50)) == true && this.c1.isWithin(this.p) == true && this.c2.isWithin(this.p) == false && this.r1.isWithin(new Posn(60, 70)) == true && this.r1.isWithin(this.p) == true && this.r2.isWithin(this.p) == false; } } /* Note: When designing the methods that compute the area and the perimeter of these shapes, it is clear that these methods produce values of the type 'double' - at least in the case of Circle. Please, consult the notes on 'Testing equality of double values' to learn how to design tests for these methods. */