Refactoring class relationships – Extract Superclass seven

Refactoring class relationships-Extract Superclass refines superclass seven

1. Refining superclass

1.1. Usage scenarios

“Both classes have similar properties.
Create a superclass for these two classes and move the same features to the superclass.

Duplicated code is one of the worst things in a system. If you do the same thing in different places, once you need to modify those actions, you have to make more modifications for no reason.

Some form of duplication of code is when two classes do similar things in the same way, or do similar things in different ways. Objects provide a mechanism to simplify this situation, and that is inheritance. However, you often cannot discover such commonality before establishing these common classes, so you often start to build the inheritance structure between them after the common classes appear.

Another option is Extract Class (149). The choice between these two options is really a choice between inheritance and delegation. Inheritance is simpler if two classes can share behavior and also share an interface. If you make a wrong choice, there is always Replace Inheritance with Delegation (352) for regret.

1.2. How to do it

  • Creates a blank abstract superclass for the original class.
  • Use Pull Up Field (320), Pull Up Method (322) and Pull Up Constructor Body (325) to move up the common elements of the subclass to the superclass one by one.
  • It is usually easier to move the field first.
  • If the corresponding subclass functions have different signatures but have the same purpose, you can first use Rename Method (273) to change their signatures to be the same, and then use Pull Up Method (322).
  • If corresponding subclass functions have the same signature but different function bodies, you can declare their common signature as an abstract function in the superclass.
  • If corresponding subclass functions have different function bodies but serve the same purpose, try using Substitute Algorithm (139) to copy the function body of one function into another. If it works, you can use the Pull Up Method (322).
  • After each move up, compile and test.
  • Check the functions left in the subclass to see if they still have elements in common. If there is, you can use Extract Method (110) to extract the common part, and then use Pull Up Method (322) to move the extracted function up to the super class. If the overall flow of a function in each subclass is similar, you might be able to use Form Template Method (345).
  • After moving all common elements up to the superclass, check all users of the subclass. If they only use a common interface, you can change the type of object they request to the superclass.

1.3. Example

In the following example, I use Employee to represent “employee” and Department to represent the department

 class Employee...
   public Employee (String name, String id, int annualCost) {<!-- -->
       _name = name;
       _id = id;
       _annualCost = annualCost;
   }
   public int getAnnualCost() {<!-- -->
       return _annualCost;
   }
   public String getId(){<!-- -->
       return_id;
   }
   public String getName() {<!-- -->
       return_name;
   }
   private String _name;
   private int _annualCost;
   private String _id;
 public class Department...
   public Department (String name) {<!-- -->
       _name = name;
   }
   public int getTotalAnnualCost(){<!-- -->
       Enumeration e = getStaff();
       int result = 0;
       while (e.hasMoreElements()) {<!-- -->
           Employee each = (Employee) e. nextElement();
           result += each. getAnnualCost();
       }
       return result;
   }
   public int getHeadCount() {<!-- -->
        return _staff. size();
   }
   public Enumeration getStaff() {<!-- -->
       return _staff. elements();
   }
   public void addStaff(Employee arg) {<!-- -->
       _staff. addElement(arg);
   }
   public String getName() {<!-- -->
       return_name;
   }
   private String _name;
   private Vector _staff = new Vector();

There are two things in common here. First, employees and departments have names; second, they both have annual costs, but are calculated in slightly different ways. I want to extract a super class to accommodate these common characteristics. The first step is to create this superclass and define the two existing classes as its subclasses

 abstract class Party {<!-- -->}
 class Employee extends Party...
 class Department extends Party...

Then I started moving the attributes up to the superclass. It is usually simpler to implement the Pull up Field (320) first:

 class Party...
   protected String _name;

Then, I can use the Pull Up Method (322) to move the value function of this field up to the superclass:

 class Party {<!-- -->
   public String getName() {<!-- -->
       return_name;
   }

I usually declare this field as private. However, before that, I need to use Pull Up Constructor Body (325) so that _name can be correctly assigned:

 class Party...
   protected Party (String name) {<!-- -->
       _name = name;
   }
   private String _name;
 class Employee...
   public Employee (String name, String id, int annualCost) {<!-- -->
       super (name);
       _id = id;
       _annualCost = annualCost;
   }
 class Department...
   public Department (String name) {<!-- -->
       super (name);
   }

Department.getTotalAnnualCost() and Employee.getAnnualCost() serve the same purpose, so they should have the same name. I first use the Rename Method (273) to change their names to the same:

 class Department extends Party {<!-- -->
   public int getAnnualCost(){<!-- -->
       Enumeration e = getStaff();
       int result = 0;
       while (e.hasMoreElements()) {<!-- -->
           Employee each = (Employee) e. nextElement();
           result += each. getAnnualCost();
       }
       return result;
   }

Their function bodies are still different, so I can’t use the Pull Up Method (322) yet. But I can declare an abstract function in the superclass:

 abstract public int getAnnualCost()

After this modification, I need to observe the users of the two subclasses to see if I can change them to use the new superclass. One of the users is the Department itself, which holds a collection of Employee objects. Department.getAnnualCost() only calls the getAnnualCost() function of the elements (objects) in the collection, which is currently declared in Party:

 class Department...
   public int getAnnualCost(){<!-- -->
       Enumeration e = getStaff();
       int result = 0;
       while (e.hasMoreElements()) {<!-- -->
           Party each = (Party) e. nextElement();
           result += each. getAnnualCost();
       }
       return result;
   }

This behavior suggests a new possibility: I can use the Composite pattern [Gang of Four] to treat Department and Employee, so that one Department object can contain another Department object. This is a new feature, so this modification is not strictly a refactoring. If the user happens to need Composite mode, I can modify the _staff field name to better represent this mode. This modification will also bring other corresponding modifications: modify the name of the addStaff() function, and change the parameter type of the function to Party. Finally, we need to turn the headCount() function into a recursive call. My approach is to create a headCount() function in Employee, let it return 1; then use Substitute Algorithm (139) to modify Department’s headCount() function, let it sum up the headCount() call results of each department.