#lang scribble/manual @(require "vkp-scrbl/unnumbered.rkt" "vkp-scrbl/utils.rkt") @title{Lecture: Methods for unions} @para[#:style "boxed"]{ Design methods for unions of classes of data.@linebreak{} Practice using wish lists. Wish lists.@linebreak{}@linebreak{} @hspace[4] @link["IShape.java"]{IShape.java} @hspace[2]} @section*{Lecture outline} @itemlist[ @item{Methods for unions of classes} @item{Reinforce the use of the @emph{Design Recipe}}] @section{Designing Methods for Unions of Classes} Here are our two classes that represent shapes - we defined them to @tt{implement} the common @tt{interface IShape}. The class diagram below shows our ambitious program - of designing five methods for these classes. @verbatim{ +---------------------------------+ | IShape | +---------------------------------+ +---------------------------------+ | double area() | | boolean distTo0() | | IShape grow(int inc) | | boolean biggerThan(IShape that) | | boolean contains(CartPt pt) | +---------------------------------+ | / \ --- | ------------------------------- | | +---------------------------------+ | | Circle | | +---------------------------------+ | +-| CartPt center | | | | int radius | | | | String color | | | +---------------------------------+ | | | double area() | | | | boolean distTo0() | | | | IShape grow(int inc) | | | | boolean biggerThan(IShape that) | | | | boolean contains(CartPt pt) | | | +---------------------------------+ | | | | +--------------------------------+ | | Square | | +--------------------------------+ | +-| CartPt nw | | | | int size | | | | String color | | | +--------------------------------+ | | | double area() | | | | boolean distTo0() | | | | IShape grow(int inc) | | | | boolean biggerThan(IShape that)| | | | boolean contains(CartPt pt) | | | +--------------------------------+ | | +----+ +--------------+ | | v v +--------+ | CartPt | +--------+ | int x | | int y | +--------+ } We want to design the following mtehods that would work for any shape - the two we have defined now, and any other shape class we may define in the future (for example a @tt{Square} class. @itemlist[ @item{ @verbatim{ // to compute the area of this shape public double area(); } } @item{ @verbatim{ // to compute the distance form this shape to the origin public double distTo0(); } } @item{ @verbatim{ // to increase the size of this shape by the given increment public IShape grow(int inc); } } @item{ @verbatim{ // is the area of this shape is bigger than the area of the given shape? public boolean biggerThan(IShape that); } } @item{ @verbatim{ // does this shape (including the boundary) contain the given point? public boolean contains(CartPt pt); } }] To compute the area of a shape in DrRacket the function purpose, contract and header would have been: @verbatim{ ;; to compute the area of the given shape ;; area : Shape -> Number (define (area ashape) ...) } and the template would have been: @verbatim{ ;; to compute the area of the given shape ;; area : Shape -> Number (define (area ashape) (cond [(circle? ashape) ...] [(square? ashape) ...])) } In Java the methods that deal with each type of object are defined within the class definition for that class of objects. So the @tt{area} method that computes the area of a circle is defined in the @tt{Circle} class and the @tt{area} method that computes the area of a square is defined in the @tt{Square} class. A union of several classes in Java is represented by an @tt{interface} type. We would like to assure that every class that implements our @tt{IShape} interface does indeed define the method #@tt{area}. The interface definition, that until now looked likejust an empty shell of a code, indeed can define method headers for some methods. That defines a contract between the interface and the classes that implement the interface. Every class that implements the interface is required to implement all methods that the interface specifies. In turn, if we use any object of the type specified by the interface, we are guaranteed that a method defined in the interface can be invoked by this object. So, our code would look like this: @verbatim{ // to represent a geometric shape interface IShape { // to compute the area of this shape public double area(); } // to represent a circle class Circle implements IShape { CartPt center; int radius; String color; Circle(CartPt center, int radius, String color) { this.center = center; this.radius = radius; this.color = color; } /* TEMPLATE FIELDS: ... this.center ... -- CartPt ... this.radius ... -- int ... this.color ... -- String METHODS ... this.area() ... -- double */ // to compute the area of this shape public double area() { return Math.PI * this.radius * this.radius; } } // to represent a square class Square implements IShape { CartPt nw; int size; String color; Square(CartPt nw, int size, String color) { this.nw = nw; this.size = size; this.color = color; } /* TEMPLATE FIELDS: ... this.nw ... -- CartPt ... this.size ... -- int ... this.color ... -- String METHODS: ... this.area() ... -- double */ // to compute the area of this shape public double area() { return this.size * this.size; } } class ExamplesShapes { ExamplesShapes() {} IShape c1 = new Circle(new CartPt(50, 50), 10, "red"); IShape s1 = new Square(new CartPt(50, 50), 30, "red"); // test the method area in the classes that implement IShape boolean testIShapeArea(Tester t) { return t.checkInexact(this.c1.area(), 314.15, 0.01) && t.checkInexact(this.s1.area(), 900.0, 0.01); } } } @bold{A challenge:} See if you would be able to define the following methods for the classes @tt{Circle} and @tt{Square}: @verbatim{ // to represent a geometric shape interface IShape { // to compute the area of this shape public double area(); // to compute the distance form this shape to the origin public double distTo0(); // to increase the size of this shape by the given increment public IShape grow(int inc); // is the area of this shape is bigger than the area of the given shape? public boolean biggerThan(IShape that); // does this shape (including the boundary) contain the given point? public boolean contains(CartPt pt); } } @emph{Well,} here is the solution. Study if carefully, especially the places where we decided to use helper methods in the class @tt{CartPt}. Also look carefully at the way we extend the basic template when the method argument is an instance of the class in which the method is defined --- in that case we have access to all fields of the argument object and the methods these fields can invoke. @verbatim{ // to represent a circle class Circle implements IShape { CartPt center; int radius; String color; Circle(CartPt center, int radius, String color) { this.center = center; this.radius = radius; this.color = color; } /* TEMPLATE FIELDS ... this.center ... -- CartPt ... this.radius ... -- int ... this.color ... -- String METHODS ... this.area() ... -- double ... this.distTo0() ... -- double ... this.grow(int inc) ... -- IShape ... this.biggerThan(IShape that) ... -- boolean ... this.contains(CartPt pt) ... -- boolean METHODS FOR FIELDS: ... this.center.distTo0() ... -- double ... this.center.distTo(CartPt) ... -- double */ // to compute the area of this shape public double area(){ return Math.PI * this.radius * this.radius; } // to compute the distance form this shape to the origin public double distTo0(){ return this.center.distTo0() - this.radius; } // to increase the size of this shape by the given increment public IShape grow(int inc){ return new Circle(this.center, this.radius + inc, this.color); } // is the area of this shape is bigger than the area of the given shape? public boolean biggerThan(IShape that){ /*--------------------------------------------------- // TEMPLATE for this method: ... this.center ... -- CartPt ... this.radius ... -- int ... this.color ... -- String ... this.area() ... -- double ... this.distTo0() ... -- double ... this.grow(int inc) ... -- IShape ... that.center ... -- CartPt ... that.radius ... -- int ... that.color ... -- String ... that.area() ... -- double ... that.distTo0() ... -- double ... that.grow(int inc) ... -- IShape ---------------------------------------------------*/ return this.area() >= that.area(); } // does this shape (including the boundary) contain the given point? public boolean contains(CartPt pt){ /*--------------------------------------------------- // TEMPLATE for this method: public returnType methodName() ... this.center ... -- CartPt ... this.radius ... -- int ... this.color ... -- String ... this.area() ... -- double ... this.distTo0() ... -- double ... this.grow(int inc) ... -- IShape ... this.center.distTo0() ... -- double ... this.center.distTo(CartPt x) ... -- double ... pt.distTo0() ... -- double ... pt.distTo(CartPt x) ... -- double ---------------------------------------------------*/ return this.center.distTo(pt) <= this.radius; } } } @verbatim{ // to represent a square class Square implements IShape { CartPt nw; int size; String color; Square(CartPt nw, int size, String color) { this.nw = nw; this.size = size; this.color = color; } /* TEMPLATE FIELDS ... this.nw ... -- CartPt ... this.size ... -- int ... this.color ... -- String METHODS ... this.area() ... -- double ... this.distTo0() ... -- double ... this.grow(int inc) ... -- IShape ... this.biggerThan(IShape that) ... -- boolean ... this.contains(CartPt pt) ... -- boolean METHODS FOR FIELDS: ... this.nw.distTo0() ... -- double ... this.nw.distTo(CartPt) ... -- double */ // to compute the area of this shape public double area(){ return this.size * this.size; } // to compute the distance form this shape to the origin public double distTo0(){ return this.nw.distTo0(); } // to increase the size of this shape by the given increment public IShape grow(int inc){ return new Square(this.nw, this.size + inc, this.color); } // is the area of this shape is bigger than the area of the given shape? public boolean biggerThan(IShape that){ /*--------------------------------------------------- // TEMPLATE for this method: ... this.nw ... -- CartPt ... this.size ... -- int ... this.color ... -- String ... this.area() ... -- double ... this.distTo0() ... -- double ... this.grow(int inc) ... -- IShape ... that.nw ... -- CartPt ... that.size ... -- int ... that.color ... -- String ... that.area() ... -- double ... that.distTo0() ... -- double ... that.grow(int inc) ... -- IShape ---------------------------------------------------*/ return this.area() >= that.area(); } // does this shape (including the boundary) contain the given point? public boolean contains(CartPt pt){ /*--------------------------------------------------- // TEMPLATE for this method: public returnType methodName() ... this.nw ... -- CartPt ... this.size ... -- int ... this.color ... -- String ... this.area() ... -- double ... this.distTo0() ... -- double ... this.grow(int inc) ... -- IShape ... this.nw.distTo0() ... -- double ... this.nw.distTo(CartPt x) ... -- double ... pt.distTo0() ... -- double ... pt.distTo(CartPt x) ... -- double ---------------------------------------------------*/ return (this.nw.x <= pt.x) && (pt.x <= this.nw.x + this.size) && (this.nw.y <= pt.y) && (pt.y <= this.nw.y + this.size); } } } @verbatim{ /* +--------+ | CartPt | +--------+ | int x | | int y | +--------+ */ // to represent a Cartesian point class CartPt { int x; int y; CartPt(int x, int y) { this.x = x; this.y = y; } /* TEMPLATE FIELDS: ... this.x ... -- int ... this.y ... -- int METHODS: ... this.distTo0() ... -- double ... this.distTo0(CartPt) ... -- double */ // to compute the distance form this point to the origin public double distTo0(){ return Math.sqrt(this.x * this.x + this.y * this.y); } // to compute the distance form this point to the given point public double distTo(CartPt pt){ return Math.sqrt((this.x - pt.x) * (this.x - pt.x) + (this.y - pt.y) * (this.y - pt.y)); } } } @verbatim{ class ExamplesShapes { ExamplesShapes() {} CartPt pt1 = new CartPt(0, 0); CartPt pt2 = new CartPt(3, 4); CartPt pt3 = new CartPt(7, 1); IShape c1 = new Circle(new CartPt(50, 50), 10, "red"); IShape c2 = new Circle(new CartPt(50, 50), 30, "red"); IShape c3 = new Circle(new CartPt(30, 100), 30, "blue"); IShape s1 = new Square(new CartPt(50, 50), 30, "red"); IShape s2 = new Square(new CartPt(50, 50), 50, "red"); IShape s3 = new Square(new CartPt(20, 40), 10, "green"); // test the method distTo0 in the class CartPt boolean testDistTo0(Tester t) { return t.checkInexact(this.pt1.distTo0(), 0.0, 0.001) && t.checkInexact(this.pt2.distTo0(), 5.0, 0.001); } // test the method distTo in the class CartPt boolean testDistTo(Tester t) { return t.checkInexact(this.pt1.distTo(this.pt2), 5.0, 0.001) && t.checkInexact(this.pt2.distTo(this.pt3), 5.0, 0.001); } // test the method area in the class Circle boolean testCircleArea(Tester t) { return t.checkInexact(this.c1.area(), 314.15, 0.01); } // test the method area in the class Square boolean testSquareArea(Tester t) { return t.checkInexact(this.s1.area(), 900.0, 0.01); } // test the method distTo0 in the class Circle boolean testCircleDistTo0(Tester t) { return t.checkInexact(this.c1.distTo0(), 60.71, 0.01) && t.checkInexact(this.c3.distTo0(), 74.40, 0.01); } // test the method distTo0 in the class Square boolean testSquareDistTo0(Tester t) { return t.checkInexact(this.s1.distTo0(), 70.71, 0.01) && t.checkInexact(this.s3.distTo0(), 44.72, 0.01); } // test the method grow in the class Circle boolean testCircleGrow(Tester t) { return t.checkExpect(this.c1.grow(20), this.c2); } // test the method grow in the class Square boolean testSquareGrow(Tester t) { return t.checkExpect(this.s1.grow(20), this.s2); } // test the method biggerThan in the class Circle boolean testCircleBiggerThan(Tester t) { return t.checkExpect(this.c1.biggerThan(this.c2), false) && t.checkExpect(this.c2.biggerThan(this.c1), true) && t.checkExpect(this.c1.biggerThan(this.s1), false) && t.checkExpect(this.c1.biggerThan(this.s3), true); } // test the method biggerThan in the class Square boolean testSquareBiggerThan(Tester t) { return t.checkExpect(this.s1.biggerThan(this.s2), false) && t.checkExpect(this.s2.biggerThan(this.s1), true) && t.checkExpect(this.s1.biggerThan(this.c1), true) && t.checkExpect(this.s3.biggerThan(this.c1), false); } // test the method contains in the class Circle boolean testCircleContains(Tester t) { return t.checkExpect(this.c1.contains(new CartPt(100, 100)), false) && t.checkExpect(this.c2.contains(new CartPt(40, 60)), true); } // test the method contains in the class Square boolean testSquareContains(Tester t) { return t.checkExpect(this.s1.contains(new CartPt(100, 100)), false) && t.checkExpect(this.s2.contains(new CartPt(55, 60)), true); } } }