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

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!

Exercises

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

  1. import

  2. implements

  3. extends

  4. super

Solution

Answer: 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.

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

  1. To call methods defined in Employee

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

  3. To make variables private

  4. To loop over arrays

Solution

Answer: B

We use super(...) to call the parent class constructor so that Person’s fields (name, dateOfBirth) are initialized correctly. This avoids duplicating setup code in Employee.

  1. 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

Solution

Answer: B

Private fields 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.

  1. 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.

Solution

Answer: B

Even though Employee inherits Person’s fields, private visibility prevents direct access. To use name, Employee must call getName() or pass it via the constructor.

e. 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;

Solution

Answer: 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.

  1. 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.

Solution

Answer: C

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

  1. 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.

Solution

Answer: C

Person’s toString() shows only name and birth date, but Employee needs more detail. Overriding allows Employee’s toString() to include id and dateOfHire.