Prev Next

Composition

Composition is a way to combine simple objects or data types into more complex classes. Compositions are a critical building block of software development.

Composition is about expressing relationships between objects. Think about the chair example. A chair has a seat. A chair has a back. And a chair has a set of legs. The phrase "has a" implies a relationship where the chair owns, or at minimum, uses, another object. It is this "has a" relationship which is the basis for composition.

Composing one object inside another

Now that we have created the Point class we can use it to construct a larger class.

We will define a class for Line. A line is defined by two points and hence we need 2 Point instances for a Line class. In Eclipse, right-click on the src folder and create new class with the name Line.java.

public class Line {
  private Point point1;
  private Point point2;

  // Constructor section

  Line() {  // default constructor

      // First create instances of point1 and point2 - assign default coordinates
      this.point1 = new Point();    
      this.point2 = new Point(1,1);
  }

  Line(Point p1, Point p2) {  // A constructor which takes 2 points as arguments
      this.point1 = p1;
      this.point2 = p2;
  }

  Line(int x1, int y1, int x2, int y2) {  // Another constructor which takes 
                                        // x- and y-coordinates as arguments
      this.point1 = new Point(x1,y1);
      this.point2 = new Point(x2,y2);
  }
}

Exercises: Implement the following features.

Important Instructions before you proceed to implement.

1. Implement each functionality, test it out with the driver code, then the next functionality with its driver, so on and so forth. Your mantra should be: One functionality at a time.

2. Try to reuse the existing code to the best possible extent. You already have class that implements several methods. Use them to the hilt. Don't reinvent the wheel.

Here is a general guideline to add and test each feature. Let's say you implemented a method someFeature() in Line class, implement corresponding method in Driver class, say testSomeFeature(), and invoke it in main() to ensure the feature is working properly. You can implement more than one test case for a feature if it is needed. See below for the model you have to follow.

Line.java LineTest.java
      public class Line {
        private Point point1;
        private Point point2;

        Line() {
          // Your code here
        }

        Line(Point p, Point q) {
          // Your code here
        }

        Line(int x1, int y1, int x2, int y2) {
          // Your code here
        }

        public Point getPoint1() {
          // Your code here
        }

        public Point getPoint2() {
          // Your code here
        }

        public void setPoint1(Point r) {
          // Your code here
        }

        public void setPoint2(Point r) {
          // Your code here
        }

        public double slope() {
          // Your code here
          // Invoke Point's slope()
        }

        public int distance() {
          // Your code here
          // Invoke Point's distance()
        }

        public String equation() {
          // Your code here
          // Invoke Point's equation()
        }

        public boolean isOnTheLine(Point r) {
          // Your code here
        }

        public boolean isLeft(Point r) {
          // Your code here
        }

        public boolean isRight(Point r) {
          // Your code here
        }

      /* Let AB be the line & C be the point.
          Compute sign of determinant(AB,AC). 
          - If = 0, then c on the line AB
          - If > 0, then c on the left
          - If < 0, then c on the right
        which implies determinant() method
        has to be implemented. */

        public double determinant (Point A
                      Point B, Point C) {
          // Use the below formula
          Δ = (B.x - A.x)*(C.y - A.y) 
          - (B.y - A.y)*(C.x - A.x)
        }
      }

      
      public class LineTest {
        public static void main(
                    String[] args) {
          Driver d = new Driver();
          d.testConstructor1();
          d.testConstructor2();
          d.testConstructor3();

          d.testSetPoint1();
          d.testSetPoint2();
          d.testGetPoint1();
          d.testGetPoint2();

          d.testSlope();
          d.testdistance();
          d.testEquation();
          d.testIsOnTheLine();
          d.testIsLeft();
          d.testIsRight();
        }

        void testConstructor1() {
          // Your test code here
        }

        void testConstructor2() {
          // Your test code here
        }

        void testConstructor3() {
          // Your test code here
        }

        void testSetPoint1() {
          // Your test code here
        }

        void testSetPoint2() {
          // Your test code here
        }

        void testGetPoint1() {
          // Your test code here
        }

        void testGetPoint2() {
          // Your test code here
        }

        void testSlope() {
          // Your test code here
        }

        void testEquation() {
          // Your test code here
        }

        void testIsOnTheLine() {
          // Your test code here
        }

        void testIsLeft() {
          // Your test code here
        }

        void testIsRight() {
          // Your test code here
        }

        /* If more test cases are
          needed for a feature, add.
          Goal: Thorough testing => 
          reliable code. */
      }
      

In addition, you can implement equals() and toString() methods. Also, you can think of more features and implement (and test) them.

How should you implement the test methods?

An sample implementation of a test case can be as follows:

  public void testSlope() {
      Line line = new Line(new Point(1,3), new Point(2,8));
      
      // Compare computed output with expected output
      if (line.slope() == 5)
          System.out.println("testSlope passed");
      else
          System.out.println("testSlope failed");
  }

Since LineDriver's main() method calls all the test methods one by one, at the end of the execution, the status of each test method will be printed to the console. This can be used for focusing on flaws in the implementation.

In practical scenarios, one test case for each feature is terribly inadequate. For instance, the slope computation may need to be tested with different set of coordinates. You will need to include more test methods for each feature. It is not uncommon that your driver code is much longer than the implementation. In fact it has to be so if your implementation has to be reliable.

More exercises

1. Define Triangle class and implement the features.

2. Define Quadrilateral class and implement the features.

3. Define Circle class along with its attributes and features.

4. How would you define a Polygon class? It can be any n-gon. Your definition should incorporate this freedom of having any number of sides. (Hint: Pass the size through the constructor and create as many Point instances.).