/* --- CSU213 Spring 2006 Lecture Notes --------- Copyright 2006 Viera K. Proulx Lecture 8: How Do We Compare??? Goals: - Designing methods that produce instances of a variant in a union - Comparing instances of a union Introduction: The following class diagram represents geometric shapes - such as we may use to draw scenes in a game. The square is given by its North West corner and its size, the trianlge will always be a right-angle triangle with the given South West corner and the given width and height: |\ | \ | \ ----- Here are some example of shapes: Posn p1 = new Posn(100, 100); Posn p2 = new Posn(80, 120); Shape s1 = new Square(this.p1, 20); Shape c1 = new Circle(this.p1, 30); Shape t1 = new Triangle(this.p2, 40, 50); To program simple animations we need to be able to move a shape - to produce the another shape of the same size at a new location. The purpose and the header for this method is: // produce a new shape by moving this shape a given distance Shape move(int dx, int dy) Here are some example of the use of the move method: s1.move(10, 20) ---> new Square(new Posn(110, 120), 20); c1.move(-20, 20) ---> new Circle(this.p2, 30); t1.move(20, 10) ---> new Triangle(new Posn(100, 130), 40, 50); Here are the templates for the methods in each class: // Template for the class Square: ... this.nw ... -- Posn ... this.size ... -- int // Template for the class Circle: ... this.center ... -- Posn ... this.radius ... -- int // Template for the class Triangle: ... this.sw ... -- Posn ... this,height ... -- int ... this.width ... -- int In each case we need a new Posn, moved by the given distance, but there is no such data in the template. We need to delegate to the class Posn to produce a new Posn moved by a given distance. (Imagine, if the location of the Posn was recorded in polar coordinates -- it is not our business to worry about that - the class Posn needs to be in control.) We leave it to the reader to design the method 'move' in the class Posn. We get: // move this Posn by the given distance Posn move(int dx, int dy){ return new Posn(this.x + dx, this.y + dy); } with the following examples: this.p1.move(10, 20) ---> new Posn(110, 120) this.p1.move(-20, 20) ---> this.p2 this.p2.move(20, 10) ---> new Posn(100, 130) To test this method we need to compare two instances of the Posn class. We need to make sure the values of the two fields are the same. This suggest we need the method 'samePosn' that compares this Posn with that given Posn: // is this Posn the same as the given Posn? boolean samePosn(Posn that) { return this.x == that .x && this.y == that.y; } We can now test our examples: // tests for the method samePosn in the class Posn boolean testSamePosn1 = this.p1.samePosn(this.p2) == false; boolean testSamePosn2 = this.p1.samePosn(new Posn(100, 100)) == true; // tests for the method move in the class Posn boolean testMovePosn1 = this.p1.move(10, 20).samePosn(new Posn(110, 120)); boolean testMovePosn2 = this.p1.move(-20, 20).samePosn(this.p2); boolean testMovePosn3 = this.p2.move(20, 10).samePosn(new Posn(100, 130)); Now we can get back to designing the 'move' method for the classes that represent Shape-s. Here are the templates for each class --- taking into account the fact that the Posn class now has a methods 'move' and 'samePosn': // Template for the class Square: ... this.nw ... -- Posn ... this.size ... -- int ... this.nw.move(int, int) ... -- Posn ... this.nw.samePosn(Posn) ... -- boolean // Template for the class Circle: ... this.center ... -- Posn ... this.radius ... -- int ... this.center.move(int, int) ... -- Posn ... this.center.samePosn(Posn) ... -- boolean // Template for the class Triangle: ... this.sw ... -- Posn ... this.height ... -- int ... this.width ... -- int ... this.sw.move(int, int) ... -- Posn ... this.nw.samePosn(Posn) ... -- boolean and the method bodies follow: // produce a new shape by moving this square a given distance Shape move(int dx, int dy) { return new Square(this.nw.move(dx, dy), this.size); } // produce a new shape by moving this circle a given distance Shape move(int dx, int dy) { return new Circle(this.center.move(dx, dy), this.radius); } // produce a new shape by moving this triangle a given distance Shape move(int dx, int dy) { return new Triangle(this.sw.move(dx, dy), this.height, this.width); } Of course, we need to rewrite the examples as tests. This is what we expect: // tests for the method move in the Shape classes boolean testMoveSquare = this.s1.move(10, 20).sameShape(new Square(new Posn(110, 120), 20)); boolean testMoveCircle = this.c1.move(-20, 20).sameShape(new Circle(this.p2, 30)); boolean testMoveTriangle = this.t1.move(20, 10).sameShape(new Triangle(new Posn(100, 130), 40, 50)); However, just as in the case of Posn-s, we need to design the method 'sameShape' that compares this shape with the given shape and determines whether they represent the same shape. The method header and purpose are easy: // is this shape the same as that given shape boolean sameShape(Shape that) We recall our examples of shapes and add a couple more: Shape s1 = new Square(this.p1, 20); Shape c1 = new Circle(this.p1, 30); Shape t1 = new Triangle(this.p2, 40, 50); Shape s2 = new Square(this.p2, 20); Shape c2 = new Circle(this.p2, 50); Shape t2 = new Triangle(this.p2, 30, 50); We now can make examples for the method sameShape: s1.sameShape(s2) ---> false s1.sameShape(new Square(p1, 20)) ---> true c1.sameShape(c2) --> false c1.sameShape(new Circle(p1, 30)) ---> true t1.sameShape(t2) ---> false t1.sameShape(new Triangle(p2, 40, 50)) ---> true) s1.sameShape(c1) ---> false c1.sameShape(t1) ---> false t1.sameShape(s1) ---> false We now look at the templates for the three classes, that includes the argument to the method sameShape: // Template for the class Square: ... this.nw ... -- Posn ... this.size ... -- int ... this.nw.move(int, int) ... -- Posn ... this.nw.samePosn(Posn) ... -- boolean ... that ... -- Shape ... that.move(int, int) -- Shape ... that.sameShape(Shape) -- boolean // Template for the class Circle: ... this.center ... -- Posn ... this.radius ... -- int ... this.center.move(int, int) ... -- Posn ... this.center.samePosn(Posn) ... -- boolean ... that ... -- Shape ... that.move(int, int) -- Shape ... that.sameShape(Shape) -- boolean // Template for the class Triangle: ... this.sw ... -- Posn ... this.height ... -- int ... this.width ... -- int ... this.sw.move(int, int) ... -- Posn ... this.nw.samePosn(Posn) ... -- boolean ... that ... -- Shape ... that.move(int, int) -- Shape ... that.sameShape(Shape) -- boolean But the example hint that we need to first know whether we are comparing a square with a square, or a square with some shape, a circle with another circle, or with some other shape, etc. Let us see, whether adding a helper method that compares one Square with another Square can help. The design of the method is just like what we did for Posn and we get: // is this square the same as the given square? boolean sameSquare(Square s) { return this.nw.samePosn(that.nw) && this.size == that.size; } We need to convert the first two tests for sameShape to tests for the method sameSquare. However, in the test s1.sameSquare(s2) ---> false the only thing we can tell about s2 is that its type is declared to be 'Shape'. We could have defined its value to be an instance of a Circle, and that would make our test faulty. So, we must make sure that both pieces of data involved in the test can only be instances of the Square class. The valid tests will then be: s1.sameSquare(new Square(p2, 20)) ---> false s1.sameSquare(new Square(p1, 20)) ---> true The problem we face that the method sameSquare has to be invoked by an instance of the class Square, and the only kind of data available in the template is the Shape 'that'. We decide to define the method 'sameSquare' for all three classes (and include it in the interface Shape. Of course, comparing a Circle with a Square, of a Triangle with a given Square always results in false: // in the class Square: // is this square the same as the given square? boolean sameSquare(Square s) { return this.nw.samePosn(that.nw) && this.size == that.size; } // in the class Circle: // is this circle the same as the given square? boolean sameSquare(Square s) { return false; } // in the class Triangle: // is this triangle the same as the given square? boolean sameSquare(Square s) { return false; } We need to run all the tests to make sure these methods work as expected. Now that we have this method, let us look at the template for the class Square, when designing the method sameShape: // Template for the class Square: ... this.nw ... -- Posn ... this.size ... -- int ... this.nw.move(int, int) ... -- Posn ... this.nw.samePosn(Posn) ... -- boolean ... that ... -- Square ... that.move(int, int) -- Shape ... that.sameShape(Shape) -- boolean ... that.sameSquare(Square) -- boolean the last entry says that the object 'that' of the type Square can invoke the method sameSquare, as long as we can supply an argument of the type Square. But 'this' is a Square, and so that.sameSquare(this) compares the instance of Square represented by 'that' with the instance of Square represented by 'this' - which is our goal. If 'that' is a Circle or a Triangle, it will invoke the method sameSquar in the classes Circle or Triangle respectively, producing 'false' in either case. If both 'that' and 'this' are squares, a true comparison of the fields will be done to determine whether these two square shapes are the same. This completes the body of the method 'sameShape' in the class Square: // in the class Square: // is this shape the same as that shape? boolean sameShape(Shape that){ return that.sameSquare(this); } To complete the design, we need to do the same in the Circle class, and add the method 'sameCircle' to all classes, as well as add the method 'sameTriangle' to all classes. Do not forget the tests!!! The complete code with examples is shown below. Of course, we can now, finally, run the tests for the method 'move'. +--------------------------------------+ | interface: | | Shape | +--------------------------------------+ | Shape move(int dx, int dy); | | boolean sameShape(Shape that); | | boolean sameSquare(Square that); | | boolean sameCircle(Circle that); | | boolean sameTriangle(Triangle that); | +--------------------------------------+ / \ | - - - - - - - - - - - - - - - - - - - - | | | +---------------------------+ | +---------------------------+ | Square | | | Triangle | +---------------------------+ | +---------------------------+ +-| Posn nw | | +--| Posn sw | | | int size | | | | int height | | +---------------------------+ | | | int weight | | | Shape move(...) | | | +---------------------------+ | | boolean sameShape(...) | | | | Shape move(...) | | | boolean sameSquare(...) | | | | boolean sameShape(...) | | | boolean sameCircle(...) | | | | boolean sameSquare(...) | | | boolean sameTriangle(...) | | | | boolean sameCircle(...) | | +---------------------------+ | | | boolean sameTriangle(...) | | | | +--------------------------+ | | +------+ | v | | +---------------------------+ | | | Circle | | | +---------------------------+ | | +---| Posn center | | | | | int radius | | | | +---------------------------+ | | | | Shape move(...) | | | | | boolean sameShape(...) | | | | | boolean sameSquare(...) | | | | | boolean sameCircle(...) | | | | | boolean sameTriangle(...) | | | | +---------------------------+ | +---+ | | | | +--------------------------------+ | | | v v v +-------+ | Posn | +-------+ | int x | | int y | +-------+ */ // to represent a shape interface Shape { /* // ** DRAFT TEMPLATE ** Edit as needed. // purpose statement ??? mmm(); */ // produce a new shape by moving this shape a given distance Shape move(int dx, int dy); // is this shape the same as that shape? boolean sameShape(Shape that); // is this shape the same as that square? boolean sameSquare(Square that); // is this shape the same as that Circle? boolean sameCircle(Circle that); // is this shape the same as that triangle? boolean sameTriangle(Triangle that); } // to represent a square class Square implements Shape { Posn nw; int size; Square(Posn nw, int size) { this.nw = nw; this.size = size; } /* // TEMPLATE: ??? mmm() { ... this.nw ... -- Posn ... this.size ... -- int ... this.nw.move(int, int) ... -- Posn ... this.nw.samePosn(Posn) ... -- boolean } */ // produce a new square by moving this square a given distance Shape move(int dx, int dy) { return new Square(this.nw.move(dx, dy), this.size); } // is this shape the same as that shape? boolean sameShape(Shape that){ return that.sameSquare(this); } // is this shape the same as that square? boolean sameSquare(Square that){ return this.nw.samePosn(that.nw) && this.size == that.size; } // is this shape the same as that Circle? boolean sameCircle(Circle that){ return false; } // is this shape the same as that triangle? boolean sameTriangle(Triangle that){ return false; } } // to represent a circle class Circle implements Shape { Posn center; int radius; Circle(Posn center, int radius) { this.center = center; this.radius = radius; } /* // TEMPLATE: ??? mmm() { ... this.center ... -- Posn ... this.radius ... -- int ... this.center.move(int, int) ... -- Posn ... this.center.samePosn(Posn) ... -- boolean } */ // produce a new circle by moving this circle a given distance Shape move(int dx, int dy) { return new Circle(this.center.move(dx, dy), this.radius); } // is this shape the same as that shape? boolean sameShape(Shape that){ return that.sameCircle(this); } // is this shape the same as that square? boolean sameSquare(Square that){ return false; } // is this shape the same as that Circle? boolean sameCircle(Circle that){ return this.center.samePosn(that.center) && this.radius == that.radius; } // is this shape the same as that triangle? boolean sameTriangle(Triangle that){ return false; } } // to represent a triangle class Triangle implements Shape { Posn sw; int height; int width; Triangle(Posn sw, int height, int width) { this.sw = sw; this.height = height; this.width = width; } /* // TEMPLATE : ??? mmm() { ... this.sw ... -- Posn ... this.height ... -- int ... this.width ... -- int ... this.sw.move(int, int) ... -- Posn ... this.nw.samePosn(Posn) ... -- boolean } */ // produce a new triangle by moving this triangle a given distance Shape move(int dx, int dy) { return new Triangle(this.sw.move(dx, dy), this.height, this.width); } // is this shape the same as that shape? boolean sameShape(Shape that){ return that.sameTriangle(this); } // is this shape the same as that square? boolean sameSquare(Square that){ return false; } // is this shape the same as that Circle? boolean sameCircle(Circle that){ return false; } // is this shape the same as that triangle? boolean sameTriangle(Triangle that){ return this.sw.samePosn(that.sw) && this.height == that.height && this.width == that.width; } } /* +-------+ | Posn | +-------+ | int x | | int y | +-------+ */ // to represent a location class Posn { int x; int y; Posn(int x, int y) { this.x = x; this.y = y; } /* // TEMPLATE: ??? mmm() { ... this.x ... ... this.y ... } */ // move this Posn by the given distance Posn move(int dx, int dy){ return new Posn(this.x + dx, this.y + dy); } // is this Posn the same as the given Posn? boolean samePosn(Posn that) { return this.x == that.x && this.y == that.y; } } class Examples { Examples() {} Posn p1 = new Posn(100, 100); Posn p2 = new Posn(80, 120); Shape s1 = new Square(this.p1, 20); Shape c1 = new Circle(this.p1, 30); Shape t1 = new Triangle(this.p2, 40, 50); Shape s2 = new Square(this.p2, 20); Shape c2 = new Circle(this.p2, 50); Shape t2 = new Triangle(this.p2, 30, 50); // determine whether two double values are almost the same boolean almostSame(double d1, double d2, double eps) { return ((d1 - d2) * (d1 -d2) < eps); } // tests for the method samePosn in the class Posn boolean testSamePosn1 = this.p1.samePosn(this.p2) == false; boolean testSamePosn2 = this.p1.samePosn(new Posn(100, 100)) == true; // tests for the method move in the class Posn boolean testMovePosn1 = this.p1.move(10, 20).samePosn(new Posn(110, 120)); boolean testMovePosn2 = this.p1.move(-20, 20).samePosn(this.p2); boolean testMovePosn3 = this.p2.move(20, 10).samePosn(new Posn(100, 130)); // tests for the method sameSquare in the class Square boolean testSameSquareS1 = this.s1.sameSquare(new Square(this.p2, 20)) == false; boolean testSameSquareS2 = this.s1.sameSquare(new Square(this.p1, 20)) == true; // tests for the method sameCircle in the class Circle boolean testSameCircleC1 = this.c1.sameCircle(new Circle(this.p1, 20)) == false; boolean testSameCircleC2 = this.c1.sameCircle(new Circle(this.p1, 30)) == true; // tests for the method sameTriangle in the class Triangle boolean testSameTriangleT1 = this.t1.sameTriangle(new Triangle(this.p2, 30, 50)) == false; boolean testSameTriangleT2 = this.t1.sameTriangle(new Triangle(this.p2, 40, 50)) == true; // tests for the method sameShape in the Shape classes boolean testSameShapeS1 = this.s1.sameShape(this.s2) == false; boolean testSameShapeS2 = this.s1.sameShape(new Square(this.p1, 20)) == true; boolean testSameShapeC1 = this.c1.sameShape(this.c2) == false; boolean testSameShapeC2 = this.c1.sameShape(new Circle(this.p1, 30)) == true; boolean testSameShapeT1 = this.t1.sameShape(this.t2) == false; boolean testSameShapeT2 = this.t1.sameShape(new Triangle(this.p2, 40, 50)) == true; boolean testSameShapeSC = this.s1.sameShape(this.c1) == false; boolean testSameShapeST = this.s1.sameShape(this.t1) == false; boolean testSameShapeCS = this.c1.sameShape(this.s1) == false; boolean testSameShapeCT = this.c1.sameShape(this.t1) == false; boolean testSameShapeTS = this.t1.sameShape(this.s1) == false; boolean testSameShapeTC = this.t1.sameShape(this.c1) == false; // tests for the method move in the Shape classes boolean testMoveSquare = this.s1.move(10, 20).sameShape(new Square(new Posn(110, 120), 20)); boolean testMoveCircle = this.c1.move(-20, 20).sameShape(new Circle(this.p2, 30)); boolean testMoveTriangle = this.t1.move(20, 10).sameShape(new Triangle(new Posn(100, 130), 40, 50)); }