Object-Oriented-Programming Extension mechanisms

Relationships between classes and objects

Classes and objects on their own are not too hard to understand. The challenge starts when classes and objects interact with each other.

First of all, let’s discuss how object objects learn about each other:

Of these four methods the first one stand out, as it creates a very tight coupling: The creator object knows exactly what class the created object is. In all the other cases the only information communicated is the interface of the object.

Extension Mechanisms

There are numerous mechanisms that allow us to extend the functionality provided by a certain class. The main two mechanisms are the following:

Example. Consider a simple Grade class:

class Grade {
    private final String letter;

    public Grade(String letter) {
        throwExceptionIfInvalidLetter();
        this.letter = letter;
    }

    public int getPoints() { ... }
    public boolean countsForCredit() { ... }
    public String getLetter() { return letter; }
}

We now want to create a UnitGrade class. It needs to allow for both a letter grade and also the amount of units that the class is worth (1, 0.5 etc). We have fundamentally two options for the design of this new class:

  1. The inheritance approach says that we should create a new subclass of the Grade class which also contains a new field, for the units:

    class UnitGrade extends Grade {
        private final double units;
    
        public UnitGrade(String letter, double units) {
            super(letter);   // Call Grade constructor
            this.units = units;
        }
    
        public double getUnits() { return units; }
        // countsForCredit is inherited
        // getLetter is inherited
        public int getPoints() {
            return units * super.getPoints();
        }
    }

    We use the term extends Grade to indicate that this is a subclass of Grade, and will therefore inherit everything that Grade has. In this instance we automatically get for free the countsForCredit and getLetter methods. We have to modify the implementation of getPoints because it has to take into account the units. We can use the super keyword whenever we have to refer to the superclass.

  2. The composition approach says that we should create a new class that has a grade field in it and a units field in it.

    class UnitGrade {
        private Grade grade;
        private double units;
    
        public UnitGrade(String letter, double units) {
            this.grade = new Grade(letter);
            this.units = units;
        }
        // other possible constructor, receiving an external grade
        public UnitGrade(Grade grade, double units) {
            this.grade = grade;
            this.units = units;
        }
    
        // Delegations
        public boolean countsForCredit() { return grade.countsForCredit(); }
        public String getLetter() { return grade.getLetter(); }
    
        public getUnits() {
            return units * grade.getUnits();
        }
    }

    In this case a UnitGrade instance contains a Grade instance as a field, and it can refer to it for information. In particular, some of the methods of UnitGrade simply return a corresponding call to grade. This is called delegation.

Let’s discuss advantages and disadvantages of the two approaches:

Essentially, inheritance is a static compile-time source-code dependency between classes, while composition is a dynamic run-time dependency between objects.

In UML notation, these two dependencies are drawn differently: inheritance is a hollow-point arrow, often drawn in a consistent vertical direction, while composition is a filled-point arrow, often drawn in a horizontal direction.

Inheritance vs Composition
Inheritance vs Composition

Example

PRACTICE: Draw a UML diagram containing all these classes and their relationships.