CPE 102 Program 4 - Inheritance
 
Ground Rules

No collaboration is allowed on this program assignment. Your program must be an individual and original effort. Except for any situations explicitly identified in this assignment, if any, you may only receive help from your instructor or the tutors provided by the Computer Science Department. See the Syllabus for the significant consequences for disallowed collaboration and/or plagiarism.


Due Date and Submission Instructions

Notice that no tests are required to be handed in for this assignment. You are, however, strongly encouraged to test - especially any methods whose correctness that you are less than sure about.

On one of the CSL servers and using handin (see How To Use handin, as necessary) hand in the following file(s) as specified:

    File(s): Canvas.java, Circle.java, ConvexPolygon.java, Ellipse.java, Rectangle.java, Shape.java, Square.java, and Triangle.java

    touser: eaugusti

    subdirectory: 102-program04



Testing With the Provided Test Driver

  1. The test driver will be published on the first due date for the assignment, usually by 10:00am
  2. You should develop and use your own tests prior to using the provided test driver. Do not use the provided test driver until your solution is complete and you believe it is correct or you are likely to be overwhelmed with error messages and will spend unnecessary time just trying to understand the test driver - a frustrating and inefficient way to approach problem solving with computers!

  3. Using the save-as feature of your browser or wget/curl if you are a cool kid), not cut-and-paste, save P4TestDriver.java (to be published on the due date) in the same directory as your source files.

  4. Compile the P4TestDriver.java with your solution and run P4TestDriver (see How to Compile and Run From the Command Line, as necessary). Remember that your code will be graded on one of the CSL servers so, to avoid unpleasant grading surprises, be sure to test on one of CSL servers before handing it in!

  5. As with Program 3, I have provided a driver that lets you draw a canvas: Draw.java. It works the same as in Program 3 and supports all the shapes.

 

Objectives

 

  1. To become familiar with Java inheritance.
  2. To compare and contrast the features inheritance and interfaces.

  3. To become familiar with abstract classes and methods.

  4. To learn the difference between deep and shallow copies of objects.

  5. To learn how to properly overriding the equals() method of the Object class.

  6. To become familiar with the Comparable interface in the Java Standard Library.

  7. More exposure to the concept of polymorphism.

 


Orientation
  1. You will be modifying your Program 3 implementation to take advantage of your new-found skills with Java inheritance.  Now that you know about inheritance you realize there is a different and, perhaps, even better way to implement your solution to Program 3.  Much of the code and instance variables defined in the various classes that implement the Shape interface turned out to be identical.  You now realize that using inheritance would allow you to avoid duplicating this code and data so you decide to rewrite Shape as an abstract class and move all common data and methods to this new class.
  2. You notice that rectangles and triangles are both special kinds of convex polygons and that, using inheritance, you can support the Rectangle and Triangle classes by making them each a subclass of ConvexPolygon. This will result in the Rectangle and Triangle classes having no instance variables and much of their logic moving the the ConvexPolygon class! While this does require some thought and effort it will result in more easily maintained code. 

  3. You decide that you would like to have a Square class. With inheritance this should be relatively easy since, geometrically speaking, squares are a special type of rectangle. You decide to make the new Square class a subclass of Rectangle and, because a square is a rectangle, no instance variables are required in the Square class since the Rectangle class has all of the necessary data elements to represent a square. However, there is the little problem of what to do with the inherited setHeight and setWidth methods from the Rectangle class - you don't want a Square object that isn't square! In addition, you will have to handle the setVertex method inherited from Rectangle (and, indirectly, ConvexPolygon).

  4. You decide that you would like to have an Ellipse class. With inheritance this should be relatively easy since, geometrically speaking, circles are a special type of ellipse. You decide to make the new Ellipse class the super-class of Circle and, because a circle is an ellipse, no instance variables are required in the Circle class since the Ellipse class will have all the necessary data elements to represent a circle. However, there is the little problem of what to do with the setSemiMajorAxis and setSemiMinorAxis the Circle class inherits from the Ellipse class - you don't want a Circle object that isn't a circle!
  5. As a new feature, you decide you want to be able to order shapes, i.e., determine if one shape is less than, equal to, or greater than another shape. You will base order to two criteria, first on the shapes name and, second, on its area. The Java Standard Library has an interface called Comparable that you will implement to solve this requirement. Know that the String class also implements the Comparable interface so it will be easy to compare the shape names.

  6. Fianlly, you realize your Program 3 solution was vulnerable to having some of its instance variable data modfied by code outside the classes even though your instance variables were all private. This is because some of the instance variables are references to mutable objects and means anyone that has a reference to the same data as your private instance variables is able to modify the object your private reference points to. This is known as a shallow copy of an object. With the exception of Canvas, you will modify all of your code so that you store references to deep copies of all mutable objects and return references to deep copies of all mutable objects.  


Suggestions

  

  1. Be sure your Program 3 code passes its test driver (provided last week) before beginning Program 4. Ask you instructor for help if you are not sure how to resolve any remaining issues.  Remember - you may not collaborate on programs.

 

  1. Copy your Program 3 source to a separate directory (or project, if using an IDE) before making any changes - you may want your original (and working) Program 3 source as a reference. 

 

  1. Develop incrementally - this means implement abstract class Shape and extend one of your other classes with it making all the necessary changes.  Then test that code well before moving on to the other shapes.  This way you will avoid repeating the same mistakes in each shape.



Specifications

  1. Your source code must meet the Programming Guidelines.

  2. Your solution must pass all test of the provided test driver (link and instruction provided below) when compiled and run on unix1, 2, 3, or 4.

  3. Using the javadoc-style, document all public methods - including constructors - in the Canvas, Shape, and Square classes. See How to Write Javadoc Comments and How to Generate Javadoc Comments, as necessary).

  4. Shape and all of its subclasses (not Canvas) must make deep-copies of all references to mutable objects that are passed in as parameters and used to modify instance variables or that go out as return values to instance variables.

  5. Entirely remove the Shape interface (used in Program 3) from the Program 4 solution and replace it with a Java abstract class called Shape as follows:

      1. Defines the instance variables that are common to all of its subclasses. Specifically, this is the color of the shape and its filled state. Even though all shapes have a position, each class treats position differently enough that storing that information in Shape would complicate the code for a very small savings in memory usage. For this reason we will choose simplicity and readability over a small savings in memory use.

      2. Has a single constructor that accepts one parameter for each of the instance variables of the class - the order of parameters is up to you.

      3. Explictly specifies exactly the same methods as the Shape interface from Program 3 with exactly the same signatures (names, return types, and parameters). If a method can be wholly or partially implemented in this class then do so, otherwise include the method signature and make the method abstract. You declare an abstract method as follows:

      4.     access-specifier abstract return-type method-name(parameter(s));

        for example:

            public abstract int someMethod(double someParameter);
      5. Implements all the methods that have common behavior (partially or wholly) in all of the subclasses, i.e, Circle, Rectangle, Triangle, ConvexPolygon, Ellipse, and Square. Note that Ellipse and Square are new shape-types to be implemented in this assignment and are specified below.

      6. Implements the java.lang.Comparable<T> interface. Implement the Comparable interface's compareTo method so that when comparing any two shapes, a.compareTo(b), the method would return as follows:

        1. A negative value when a's class name is less that b's class name or, when the names are equal, when a's area is less than b's area.
        2. Zero when a's class name equals b's class name and a's area is approximately equal to b's area (+/- 0.0001).
        3. A positive value when a's class name is greater than b's class name or, whe the names are equal, when a's area is greater than b's area.  

                        You can use the following code to obtain any object's name:

      String className = yourObjectRef.getClass().getName();

    Notice that the class name returned is of type String.  The String class also implements the Comparable interface and, therefore, has a compareTo method of its own that can be used to determine the order of class names.

      1. If necessary, override the inherited equals() method so that it returns true if the two objects being compared are the same class, not null, and have equal values for all instance variables. See How to Override the equals Method for assistance, as necessary. Remember, you must not use the instanceof operator in your equals method now that we are writing code with deeper inheritance.

     

    1. Modify ConvexPolygon so that it extends the abstract class, Shape, rather than implements the Shape interface.
      1. Has the same single constructor signature as in Program 3 with appropriate logic changes to accommodate the use of inheritance.

      2. Implement only the instance variables unique to the class and inherits the others from the abstract Shape class.
      3. This class must have the same methods and behaviors as its Program 3 equivalent. Due to the use of inheritance you'll need to do the following:

        1. Remove all methods from this class that are now inherited and are correct and appropriate as is.
        2. If necessary, override the inherited equals() method so that it returns true when the objects being compared are both the same type, are not equal to null, and all of their instance variables, defined here and in any super classes, have equal values for all instance variables. See How to Override the equals Method for assistance, as necessary.

    1. Modify Rectangle so that it extends the ConvexPolygon class rather than implements the Shape interface.

      1. Has the same single constructor signature as in Program 3 with appropriate logic changes to accommodate the use of inheritance. Note: When calling the super constructor (in ConvexPolygon) from Rectangle's constructor you will have to constuct an array of points in the super call using array initialization syntax.  That syntax is not obvious, so here is an example for you to use:

            super(new Point[ ] {a, new Point(...), new Point(...), new Point(...)}, et cetera...);

        This example assumes that Point reference called a is the position of the Rectangle and the new Point(...) calls will be completed by you to with parameters you calculate to specify the rectangle's corners (as required by the ConvexPolygon class's constructor). Remember that the caller (you) of the ConvexPolygon's constructor is required, by stated precondition, to pass the points in counter-clockwise order or the area algorithm will not work!
      2. Remove all instance variables from Rectangle - ConvexPolygon has all the necessary instance variables to support a Rectangle. Do not add width and height to ConvexPolygon but, instead, map the constructor parameters to fit the parameters of the ConvexPolygon class.

      3. This class must have the same methods and behaviors (except setVertex as specified below) as its Program 3 equivalent. Due to the use of inheritance you'll need to do the following:

        1. Remove all methods from this class that are now inherited and are correct and appropriate as is.
        2. If necessary, override the inherited equals() method so that it returns true when the objects being compared are both the same type, are not equal to null, and all of their instance variables, defined here and in any super classes, have equal values for all instance variables. See How to Override the equals Method for assistance, as necessary.

        3. Override the setVertex method inherited from ConvexPolygon so that it throws an UnsupportedOperationExceptionwhen it is called. This is to prevent someone from changing a Rectangle object so that it no longer defines a rectilinear shape! While we could solve this problem I feel it is a bit complicated and distracts from the primary focus of the class and this assignment which is Object Oriented Programming, not graphics!

    1. Modify Triangle so that it extends the ConvexPolygon class rather than implements the Shape interface.

      1. Has the same single constructor signature as in Program 3 with appropriate logic changes to accomodate the inheritance. Note: When calling the super constructor of ConvexPolygon in Triangle's constructor you will have to constuct an array of points in the super call using array initialization syntax. That syntax is not obvious, so here is an example for you to use:

                super(new Point[ ] {a, b, c}, et cetera...);

        This example assumes that Point references called a, b, and c have been passed as parameters to the Triangle constructor - change the names as necessary in your implementation.

      2. Remove all instance variables from Triangle - ConvexPolygon has all the necessary instance variables to support a Triangle. 
      3. This class must have the same methods and behaviors as its Program 3 equivalent. Due to the use of inheritance you'll need to do the following:

        1. Remove all methods from this class that are now inherited and are correct and appropriate as is.

        2. If necessary, override the inherited equals() method so that it returns true when the objects being compared are both the same type, are not equal to null, and all of their instance variables, defined here and in any super classes, have equal values for all instance variables. See How to Override the equals Method for assistance, as necessary.

        3. The getArea method is interesting in this class - recall that the ConvexPolygon class requires the points to be passed in counter-clockwise order but the Triangle class does not. Your solution must return the correct area for Triangle objects regardless of the order of the points it was constructed with. There are ways to accomodate with or without overriding the getArea method in Triangle - you may solve it either way!

       

      1. Implement a class called Square so that it extends the Rectangle as follows:
        1. Has the following single constructor: Square(int sideLength, java.awt.Point position, java.awt.Color color, boolean filled){...} Note that, like a Rectangle, the position is considered to be the lower left corner of the shape. Remember that a square has a length and width that are equal.

        2. Since a square is, geometrically speaking, a type of rectangle, the Square class needs no explicit instance variables of its own and will, instead, make use of its super-class's instance variables.

        3. Implements a method called getSize that has no parameters and a return type of int. The method returns the side length of the square (the value it was constructed with).

        4. Implements a method called setSize that has a single parameter of type int that specifies the new side length for the sqaure object and a return type of void. The method changes the side length of the square to the specified size.

        5. Overrides the setHeight and setWidthmethods inherited from Rectangle to make sure a square remains a square if someone changes its height or width. Whenever one of these methods is called on a Square object you must set both height and width to the new value without changing the position of the shape.

        6. If necessary, override the inherited equals() method so that it returns true when the objects being compared are both the same type, are not equal to null, and all of their instance variables, defined here and in any super classes, have equal values for all instance variables. See How to Override the equals Method for assistance, as necessary.
      2. Implement a class called Ellipse so that it extends the abstract class Shape.

        1. Has the following single constructor: Ellipse(double semiMajorAxis, double semiMinorAxis, java.awt.Point position, java.awt.Color color, boolean filled){...}. Note that the semi-major axis of an ellipse is the distance from the center to its furthest edge and the semi-minor axis is the distance to the closest edge. A circle has the same value for both (also known as the radius for a circle).

        2. Implement only the instance variables unique to the class and inherits the others from the abstract Shape class.

        3. Implement a method called getSemiMajorAxis that has no parameters and a return type of double. The method returns the major axis of the ellipse.
        4. Implement a method called setSemiMajorAxis that has a single parameter of type double. The method sets the ellipses major axis to the specified value. If the new value is less than the current minor axis then the new value becomes the minor axis value and the old (larger) minor axis value becomes the major axis value.

        5. Implement a method called getSemiMinorAxis that has no parameters and a return type of double. The method returns the minor axis of the ellipse.

        6. Implement a method called setSemiMinorAxisthat has a single parameter of type double. The method sets the ellipses minor axis to the specified value. If the new value is more than the current major axis then the new value becomes the major axis value and the old (smaller) major axis value becomes the minor axis value.
        7. If necessary, override the inherited equals() method so that it returns true when the objects being compared are both the same type, are not equal to null, and all of their instance variables, defined here and in any super classes, have equal values for all instance variables. See How to Override the equals Method for assistance, as necessary.

      3. Modify Circle so that it extends Ellipse rather than implements the Shape interface.

        1. Remove all instance variables from Circle - Ellipse has all the necessary instance variables to support a Circle. Remeber  that a circle's radius is both the major and minor axis value for an Ellipse.

        2. Has the same single constructor signature as in Program 3 with appropriate logic changes to accomodate the inheritance. 
        3. Implement only the methods that must be implemented in the Circle class and inherits the others from Shape. 

        4. Overrides, as necessary, setSemiMajorAxis so that a Circle object remains a circle shape, i.e., minor axis needs to change too.

        5. Overrides, as necessary, setSemiMinorAxis so that a Circle object remains a circle shape, i.e., minor axis needs to change too.
        6. This class must have the same methods and behaviors as its Program 3 equivalent. Due to the use of inheritance you'll need to do the following:

          1. Remove all methods from this class that are now inherited and are correct and appropriate as is.

          2. If necessary, override the inherited equals() method so that it returns true when the objects being compared are both the same type, are not equal to null, and all of their instance variables, defined here and in any super classes, have equal values for all instance variables. See How to Override the equals Method for assistance, as necessary.
          3. The inherited setSemiMajorAxis and setSemiMinorAxis methods are interesting in this class - A circle's major and minor axis are always the same (a.k.a radius). You'll need to override these methods so that whenever either axis is changed the other is also set to the same value so that a circle remains a circle!

          1. The Canvas methods must have identical behavior to their Program 3 behavior.  Note, as a simplification to you, that no additional methods are to be written in this class for the new shapes, Ellipse and Square. However, due to inheritance and depending on how you wrote them, you may need to modify some of the existing methods so that they work correctly. For example, the method getRectangles is supposed to return all Rectangle objects. Now, however, Square objects exist and they have an isA relationship with the Rectangle class. You must now filter out Squares in that method. Other methods have similar issues. You can use the instanceof operator with some additional logic or you can call getClass() to find out exactly what something is. There is a convienent syntax for obtaining the Class class for any class, it is ClassName.class. You can compare that with the value returned by getClass() to see if the reference points to the specific class type you are curious about.


          Program courtesy of Kurt Mammen.