7.3. Person Example (Refactoring Existing Code)

In our first interactive example, we will work through the code in the person subdirectory of our starter code. We will leverage inheritance to eliminate redundant code in the closely related Employee class.

Here is a UML diagram for the starter code. Notice the redundant methods and variables between Person and Employee.

hide circle
set namespaceSeparator none
skinparam classAttributeIconSize 0

package "cs1302.identity" {
   Driver -up-> Person: uses
   Driver -right-> Employee: uses

   class Person {
        -name: String
        -dateOfBirth: LocalDate
        +<<new>> Person(name: String, dateOfBirth: LocalDate)
        +getName(): String
        +getDateOfBirth(): LocalDate
        +computeAge(): int
        +<<override>> toString(): String
   }

   class Driver {
        {static} +main(args: String[]): void
   }

   class Employee {
        -id: long
        -name: String
        -dateOfBirth: LocalDate
        -dateOfHire: LocalDate

        +<<new>> Employee(id: long, name: String,
        \t\t\t    dateOfBirth: LocalDate, \n\t\t\t    dateOfHire: LocalDate)
        +getId(): long
        +getName(): String
        +getDateOfHire(): LocalDate
        +computeAge(): int
        +<<override>> toString(): String

   }

   hide Driver fields
}

In the video below, Dr. Cotterell refactors the code corresponding to the UML class diagram above.

Note

The example code in this section was written without inheritance, so we need to refactor it to eliminate existing redundancies. However, in a real world setting, you would want to design carefully to avoid writing redundant code in the first place!

To get the most out of this section, you will want to follow along with Dr. Cotterell in the video below. But first, make sure to change into the person subdirectory of cs1302-inheritance.

https://www.youtube.com/watch?v=V5Y85rfMfPw

IMAGE ALT TEXT

Similar to how we use implements in a class declaration to have a class implement an interface, we use the keyword extends in the class declaration of a child class to set up the inheritance relationship. After replicating the steps in the video, an inheritance relationship is established. The UML diagram below illustrates this new relationship (note the different arrow head types and compare the size of the Employee class in this diagram with the diagram above):

hide circle
set namespaceSeparator none
skinparam classAttributeIconSize 0

package "cs1302.identity" {
   Driver -up-> Person: uses
   Driver -right-> Employee: uses
   Person <|-- Employee: extends (is-a)

   class Person {
        -name: String
        -dateOfBirth: LocalDate
        +<<new>> Person(name: String, dateOfBirth: LocalDate)
        +getName(): String
        +getDateOfBirth(): LocalDate
        +computeAge(): int
        +<<override>> toString(): String
   }

   class Driver {
        {static} +main(args: String[]): void
   }

   class Employee {
        -id: long
        -dateOfHire: LocalDate

        +<<new>> Employee(id: long, name: String,
        \t\t\t    dateOfBirth: LocalDate, \n\t\t\t    dateOfHire: LocalDate)
        +getId(): long
        +computeAge(): int
        +<<override>> toString(): String

   }

   hide Driver fields
}

By utilizing the power of inheritance, we were able to remove two redundant instance variable declarations from Employee along with two redundant method declarations. These instance variables and methods are now written only once - in Person.

The benefit may seem limited in this small example. However, it is important to consider the impact of inheritance on a larger software system. For example, in a larger software system, we could extend this hierarchy to include other people. We could add classes for Student, Athlete, Professor, etc. and each of these classes would automatically inherit the instance variables and methods from Person. So, in general, declaring entities (methods, variables) in the parent eliminates redundant code across all child classes!

Rapid Fire Review
  1. In the refactored Employee class, which keyword tells Java that Employee should inherit from Person?

    1. import

    2. implements

    3. extends

    4. super

  2. Why do we use the super keyword in the Employee constructor?

    1. To call a method defined in Employee.

    2. To call the constructor of the parent class (Person)

    3. To make the instance variables private instead of public.

    4. To loop over all of the arrays initialized in this class and set their values to null.

  3. After refactoring, why can’t Employee directly access name and dateOfBirth variables from Person?

    1. They are declared in a different package

    2. They are declared as private in Person

    3. Employee doesn’t have a constructor

    4. Java doesn’t allow inheritance of abstract fields

  4. What happens if you try to access name directly inside Employee after extending Person?

    1. It works fine since Employee inherits it.

    2. It causes a compile-time error because name is private in Person.

    3. It throws a runtime error.

    4. It makes name null.

  5. What must be the very first line inside the Employee constructor if you want to call the Person constructor?

    1. this(id);

    2. Person();

    3. super(…);

    4. extends Person;

  6. In the refactored Employee class, how does getName() work if we removed it from Employee?

    1. Employee can’t access names anymore.

    2. Java automatically generates a new getName method.

    3. Employee inherits getName() from Person.

    4. getName() is private in Person.

  7. Why did Dr. Cotterell keep the toString() method in Employee instead of removing it?

    1. Because Java requires every subclass to override toString().

    2. Because Person does not have a toString() method.

    3. To include Employee-specific fields (id and dateOfHire).

    4. To avoid compile-time errors.

Solutions
  1. C

    The extends keyword establishes inheritance in Java. It tells the compiler that Employee is a child class of Person and should inherit its fields and methods.

  2. B

    We use super(...) to call the parent class constructor so that the instance variables in Person (name, dateOfBirth) are initialized in the class where they are declared. This avoids duplicating initialization code in Employee.

  3. B

    Private instance variables are hidden from child classes. Although Employee inherits them, it cannot access them directly. Instead, it must use the parent’s public getters or constructors.

  4. B

    Even though Employee inherits the instance variables from Person, private visibility prevents direct access. To use name, Employee must call getName() or use the constructor.

  5. C

    The super(...) call invokes the parent constructor and must appear as the first line in the child constructor. This ensures the parent portion of the object is initialized first.

  6. C

    Because Employee extends Person, all public methods from Person, like getName(), are automatically available in Employee. There’s no need to rewrite them.

  7. C

    The toString() method in Person shows only name and birth date, but Employee needs more detail. Overriding allows the toString() method in Employee to include id and dateOfHire.