CS 2113 Software Engineering - Fall 2024 | CS 2113 Software Engineering.

View all the videos from this unit a single playlist on youtube

Review from last class: Java basics #

Let's take 30 minutes to go through questions 1-7 on the J2 worksheet.

Extending Objects via Inheritance and Subtyping #

Public Interface of a Class #

In the last class, we reviewed how Java works and spent some time defining a Point class and a Line class, built using Points. A key idea we covered was encapsulation that the data and methods of the class should exist within the same class for ease of understanding and organization.

Another way to view this construction is via information hiding and creating a public interface. That is, we encapsulated data and methods such that the programmer only need to know the public interface to the class in order to use it, the rest is hidden via private fields and methods.

This is an extremely important idea because often in Java, you’re only provided with the compiled .class file for a program, and not the source code. You may not, nor should you, know the gritty details of how a method functions, but rather just the interface. For example, you don’t need to know how the dist() method calculates distance, just that it will return the distance between one object and another.

Extending a Class #

You might be worried now, what if I want to modify or repurpose classes that were provided to me? If I don’t have the source code, how do I do that? Let’s work through a small example.

Points and Triangles #

Object oriented programming, fortunately, provides a straight forward mechanism for doing that through inheritance and sub-typing. To see how this works, let’s continue with our example of Point classes, but this time, let’s assume that these are all ready written and final.

public class Point {
   private double x, y; //the x,y fields
   public Point(double x, double y); //construct a point from an x,y
   public Point(Point other); //construct a point from another point
   public double getX(); //return the x component
   public double getY(); //return the y component
   public double dist(Point other); //distance to another point
   public String toString(); //return the string representation
   public static Point nextPoint(Scanner sc); //given a scanner, read two doubles,
                                              //and return a new point
}

Let’s also assume we have a Triangle class with some handy static methods for calculating the area of a triangle and perimeter of the triangle defined by using the Point. Again, we know the public interface, but not how it’s implemented.

public Triangle {
    //return perimeter of the triangle defined by points p1,p2,p3
    public static double perimeter(Point p1, Point p2, Point p3);
    //return the area of the triangle
    public static double area(Point p1, Point p2, Point p3); 
}

Now you can imagine how these might have been implemented, but it doesn’t matter, as long as it works and the public interface makes sense. If you’re really curious, you can display/hide the source code by clicking below:

Using these public interfaces, we can write a reasonably complex and interesting program that given four Points, finds the triangle with the largest area.

import java.util.Scanner;

public class LargestArea {
    public static void main(String args[]) {
        Scanner sc = new Scanner(System.in);

        System.out.println("Enter four points:");
        //declare four points in an array
        Point points[] = new Point[4];
        for(int i = 0; i < points.length; i++){
            points[i] = Point.nextPoint(sc);
        }

        //create all four possible triangles by group three points together
        Point triangles[][] = { {points[0],points[1],points[2]},
                                {points[0],points[1],points[3]},
                                {points[0],points[2],points[3]},
                                {points[1],points[2],points[3]}};

        //find the triangle with largest area!
        int maxI = 0;//assume it's the first, first
        double maxArea = Triangle.area(triangles[maxI][0],
                                       triangles[maxI][1],
                                       triangles[maxI][2]);
        for(int i = 1; i < triangles.length; i++){
            double curArea = Triangle.area(triangles[i][0],
                                           triangles[i][1],
                                           triangles[i][2]);
            if( curArea > maxArea){
                maxI = i;
                maxArea = curArea;
            }
        }

        //print out the max triangle
        System.out.println("The largest triangle is:");
        System.out.printf("%s %s %s\n",triangles[maxI][0],
                                       triangles[maxI][1],
                                       triangles[maxI][2]);
        System.out.printf("With area %.2f\n",maxArea);

    }
}

Here an example of the output:

Enter four points:
1 1
2 3
4 5
2 2
The largest triangle is:
(2.0,3.0) (4.0,5.0) (2.0,2.0)
With area 1.00

Inheritance: an “is-a” relationship rather than a “has-a” #

Let’s suppose we want to update our program so that instead of having it print the coordinates of each point, we can apply a label to each point that will print. For example:

Enter four points:
P 1 1
Q 2 3
R 4 5
S 2 2
The largest triangle is:
Q:(2.0,3.0) R:(4.0,5.0) S:(2.0,2.0)
With area 1.002

What we would really like to do to solve our problem is to go add a “label” field to the class Point, so that when we had chosen the three points that defined the triangle of largest area, we could simply write out their labels through a getter method. However, we can’t modify the class Point — we technically don’t have the source code to Point — and even if we could, we may want to have Point objects that don’t have labels.

We already have a mechanism for using existing classes to build new classes. You use it whenever you create a class that has, for example, a String field (data member). We haven’t given it a special name, but if you want to know, we call this composition of classes. If class Foo has two String fields, for instance, we would say that Foo is composed of two Strings. Composition is a has-a relationship. Foo “has-a” String field.

If we try to combine a point and a String label by composition, we get a new class, maybe LabelPoint class like below.

public class LabelPoint {
   Point p;
   String label;
   //...
}

And it’s memory diagram would look like the following for an instance lp of LabelPoint:

  STACK           HEAP
                                                   
  .--------.      .-----------.        .-------.
  | lp | .-+----->|   p   | .-+------->| x | 4 | Point
  '----'---'      |-----------|        |-------|
                  | label | .-+--.     | y | 5 |
                  '-----------'  |     '-------'
                    LabelPoint     |     .-----.
                                 '---->| "R" | String
                                       '-----'

This is really ugly for us. Why? Well this is no longer a point, which means that we can’t just pass three LabelPoints to the Triangle constructor anymore so that we can calculate the area, but we constantly have to pull out the “point-part” of the LabelPoint to pass around. In fact, this class has no methods yet at all, and we’d have to recreate all the methods we already have for Point in the new LabelPoint class. Either that or, we’d need to also write a new Triangle constructor — which we can’t because we don’t have the source code for that! So instead, we’d also need to write a new LabTriangle class with new methods to handle LabelPoints … it’s a mess! And this is because a LabelPoint defined this way has-a Point, rather than is-a Point.

Deriving a new class with extends — adding fields #

Instead we can use inheritance, as in this new LabelPoint is a child of Point, or put another way, we define LabelPoint as derived from the class Point. In the Java parlance, LabelPoint extends Point — which means that a LabelPoint object is-a Point, but it is a Point with some extra features. Thus, all the code that worked for Point, all the methods of class Point and methods that take Points as arguments, all still works. But we can add more! We are extending the class Point.

The existing class Point is called the “super-class” (or parent class) and the new class LabelPoint is called the “sub-class” (or child class). Sometimes, we’ll use “super-type” and “sub-type” as well, but it means the same thing.

To derive a new class LabelPoint from class Point, and add a String label, we would write

public class LabelPoint extends Point { //extend the Point
   private String label;
}

and a memory diagram for an an instance lp of LabelPoint would look like

  STACK           HEAP
                           Point
  .--------.      .----------.
  | lp | .-+----->|   x  | 4 | 
  '----'---'      |----------|
                  |   y  | 5 |
                  |..........| 
                  |..........|    .-----.
       extended   |label | .-+--->| "R" |  String
                  '----------'    '-----'
                  LabelPoint

Since lp is a Point and and a LabelPoint then we can call all the methods of Point on lp, including dist() with other Points or LabelPoints, as well as toString(). That is, LabelPoint inherits all the methods and data of LabelPoint.

Deriving a new class with extends — constructors #

One problem with our new LabelPoint is that it also inherits the Point constructor, but the Point constructor is unaware of the new field label we added.

The constructor for a LabelPoint would naturally have three values - x, y, and label lab. However, the constructor has to split duties and say that we want the Point part of the LabelPoint to be initialized with x and y, and the label part with lab. But, once we do that, we find there is another problem.

Consider the following constructor (which does not compile):


//THIS DOES NOT COMPILE !!

public class LabelPoint extends Point {
    private String label;
    public LabelPoint(double x, double x, String lab){
        this.x = x; //x and y inheritted from Point, but declared private
        this.y = y; //can't read/write them directly
        this.label = lab;
    }
        
}

When we try and use this.x we would find we get a compiler error because we’re access a private field outside the class. While, yes, LabelPoint is a Point, direct access to private fields are not inherited! The fields are, however, inhereted (i.e. they can store values), but they’re just inaccessible.

We could have declared the fields of Point as protected instead of private, which gives all the protections of private outside the class, but allows sub-classes access to the fields/methods (discussed more later). But, we cannot edit Point, so how do we solve this problem? We need to set the fields in the super-class, or put another way, we need to call the constructor of the super class.

Java provides a super which allows us to refer to the super-class and, in this context, allows us to explicitly call the constructor for Point, initializing the Point part of the new object. After, we can initialize the LabelPoint part.


public class LabelPoint extends Point {
    private String label;
    public LabelPoint(double x, double y, String lab) {
        super(x, y); //initilize the Point part, sets x and y
        this.label = lab; //initialize LabelPoint part
    }
}

At this point, we have a full LabelPoint constructor that has inherited all the fields and methods from Point, which is great! But, we still need to make some modifications in order to leverage the new field label.

Deriving a new class with extends — adding methods #

In our new LabelPoint class, we need to add a new getter method for the label as it’s private and inaccessible otherwise. This is not a problem for LabelPoint class, but would not have made sense for Point as it has no label.


public String getLabel() { 
    return this.label;
}

At this point, you may notice that LabelPoint is more expressive than Point. For starters, it has an additional data field label but now it also has an additional method getLabel(). And this is typically the case with sub-classes, they should trend to higher expressive and more specificity for the item being described. Otherwise, the super-class would have sufficed.

Deriving a new class with extends — overriding methods #

We are not quite done with LabelPoint’s definition yet because it has inherited two methods (that would still function) but are not expressive enough, yet.

First the readPoint() static method which scans in a Point doesn’t take into account that for a LabelPoint we need the label as well. So we can override that method with a new version, just like we wrote our new constructor. In this case, we can take advantage of the static method Point.nextPoint(), so we don’t have to complete write it from scratch, but rather add just the needed functionality.

public static LabelPoint nextPoint(Scanner sc) {
        if(! sc.hasNext()) 
            return null;
        String lab = sc.next(); //read label
        Point p = Point.nextPoint(sc); //read point
        if(p == null) 
            return null; //error check
        return new LabelPoint(p.getX(), p.getY(), lab); //return new LabelPoint
    }

We also need to do the same for toString(). The inherited version from Point would work fine, but doesn’t account for the label.

    public String toString() {
        return this.label + ":(" + getX() + "," + getY() + ")";
    }

While the above toString() works fine, it is unsatisfying because we are re-implementing functionality for converting the x and y component of the Point to a string. Just like with the constructor, we can use the super keyword to access the super-classes version of toString to save us some time and maintain good code reuse.

    public String toString() {
        return this.label + ":" + super.toString();
    }

There is even an @Override annotation (similar to the @Test annotation in Junit tests) that you can use in child classes to let the compiler know you intend to override the method from the parent class; if you modify its signature by mistake, the compiler will tell you!

Putting it all together #

Now that our LabelPoint class is ready to go, we can modify our main method from before in only a few spots, using LabelPoint instead of Point to get the functionality we desired.

        //...
        LabelPoint points[] = new LabelPoint[4];
        for(int i = 0; i < points.length; i++){
            points[i] = LabelPoint.nextPoint(sc);
        }

        //create all four possible triangles by group three points together
        LabelPoint triangles[][] = { {points[0],points[1],points[2]},
                                   {points[0],points[1],points[3]},
                                   {points[0],points[2],points[3]},
                                   {points[1],points[2],points[3]}};
        //...

The rest of the code stays the same, and in that lies the power of inheritance and extending. Click below to see the completed versions of the code.

Let's take 15 minutes to go through questions 8-11 on the J2 worksheet.

Private classes #

Now that we have a better understanding of inheritance, lets look at another example. Consider that we’ve been provided with a Queue class for strings with the following fields/methods.

public class Queue {
    private Node head; //head of the queue
    private Node tail; //tail of the queue
    
    //private Node class for the linked list 
    //implementation of a queue
    private class Node {
        public String data;
        public Node next;
        public Node prev;
        public Node(String d);
    }
    
    public boolean empty();//returns true if empty, false otherwise
    public void enqueue(String s);//enques the s on the queue
    public String dequeue();//returns head of queue, or null if empty

}

Again, we can imagine how this was implemented, and for now, we don’t need to know. The public interface is sufficient for us as the programmer to use this class in a program and/or add extensions to it. If you really want to see how the queue is implemented, you can click below, but the beauty of inheritance in OOP is that you don’t really, really need to know.

You’ll note that Queue makes use of a private class Node. A private class, functions much like any other class in Java, except its scope is within the containing class. That means as the programmer of the class, you can use the private class as you would any other class, when operating within the class. But this class should not be accessible out of the containing class context.

Implementing a Linked List (or other lined structures) is a canonical example of using private classes. It’s a situation where you need additional class structure to implement the containing class, but that additional structure is not relevant to the user of the class. The Node class used to implement the list is not relevant to the user of the LinkedList or Queue as long as they function properly and there is a reasonable interface.

As a private class within a containing class, the containing class Queue, it is often not necessary to declare fields private, as they are already constrained. Moreover, you generally want the containing class full access for convenience.

Extending Queue for size() #

The Queue defined above is minimal. It’s missing some key functionality, such as the current size of the queue, or how many items are currently enqueued. With extends this is relatively easy to add, following the same procedures we outlined above with Point and LabelPoint.

public class CountingQueue extends Queue {
    private int count;
    
    public CountingQueue {
        super();
        count = 0;
    }    
    public void enqueue(String s) {
         count++;
         return  super.enqueue(s);
    }
    public String dequeue(String s) {
       if(count > 0) {
         count--; //don't let it get less than 0
       }
       return super.dequeue(s); //will return null when empty
    }

    public int getSize() { 
        return count; 
    }
}

Extending Queue for peek() #

Another common functionality of a queue is to be able to peek at the first element enqueued by returning it but not actually removing it from the queue. We could write this routine (incorrectly) in the same way we extended Queue for CountingQueue to produce PeekaCountingQueue (keeping CountingQueue’s functionality).

public class PeekaCountingQueue extends CountingQueue {

    public String peek() {
        if(empty()) 
            return null;
        String s = dequeue();
        enqueue(s);
        return s;
    }
}

But, that is entirely unsatisfying because we are actually dequeing and enqueing (and so it’s not peeking really because it puts the item at the end of the queue rather than leaving it at the start) when what we really want to do is to write the following version.

public class PeekaCountingQueue extends CountingQueue {

    public String peek() {
        if(empty()) 
            return null;
        return head.data;
    }
}

But we cannot do that because head was declared private in Queue and thus cannot be accessed directly.

This brings up a dilemma as a programmer of classes. On one hand you want to restrict access to internal fields from users of your class, but on the other you want to allow extensions of the class to be functional and efficient.

From the perspective of the implementer of class Queue, you would say that it’s impossible to anticipate everything someone would want to do with a Queue and to provide public methods that make all possible operations implementable. That’s true. A compromise between strict separation of interface from implementation and a wild west all-fields-are-public approach is to use the protected access modifier.

When a field is marked protected, it means that it is accessible inside the class and any derived classes, but not from anywhere else (except the package, which we haven’t talked about yet). If we change head and tail in Queue to be protected, implementing PeekaCountingQueue becomes easy and efficient (as above). The new Queue class definition would simply change to

public class Queue {
    protected Node head; //head of the queue
    protected Node tail; //tail of the queue

    //... rest same as before
}

This is a case where we do want to change the original class and as a programmer and the designing of the API for a class, you need to plan ahead to make these changes.

Let's take 5 minutes to go through questions 12-14 on the J2 worksheet.

UML Diagrams #

A great tool when you begin to plan out the relationships between classes is to visualize how class depend and extend each other. The UML (or Unified Model Language) is a standard way to describe classes, their members, and relationships. As a starter, consider the UML diagram for the Point LabelPoint and Triangle classes used in the prior example. (Note this was generated by IntelliJ via yworks)

intellij UML of LabelPoint

Classes #

Each box represents a class. The name of the class as indicated by (c) is at the top followed by the fields (f). The methods (m) come below that. If a field or method is private, then a (red) lock 🔒 is used, if it is protected a (silver) key is used 🔑, and if it is public an unlock 🔓 is used. A static method/field uses the diamond ◈.

Note that there are other styles of UML. See this wikipedia article for more details. Here’s the same diagram generated using the Violet UML Editor.

violet UML of LabelPoint

You’ll note the symbols

It is highly recommend to use Violet UML for developing your UML structure.

Relationship between classes #

There are two types of lines in this image, but UML diagrams can represent many different relationships

UML relationship lines

Most important for the diagrams you will draw are the following:

In IntelliJ the color of the arrows also indicate if the classes define extend (blue) or implement relationships (green). We’ll discuss implement later.

Using UML diagrams to (plan) your program #

The relationship between UML diagrams and class structures are so tight, that in many IDE’s, you can actually lay out your entire class structure in UML, and the IDE will create all the classes for you. In Project 2 you will be required to develop a UML diagram of your objects before you program.

As an example, let’s consider the following program requirements. Suppose that we want to design an object model for an HR system at a company:

Take a moment to try and draw out this diagram. When you’re finished, click below to reveal the diagram below (with an explanation).

Let's take 10 minutes to go through the last question on the J2 worksheet.


Material on this page adopted with permission from USNA courses ic211, taught by Nate Chambers, Gavin Taylor, Chris Brown, and many others. Thank you.