Class-Level Refactorings

Extract Subclass

Problem

A class has features that are only used by some instances.

Solution

Create a new subclass. Iteratively push down methods and features.

Method

Anti Patterns

Examples

Our Aircraft class contains a hover method only certain types of aircraft don't perform:

class Aircraft {
   private double speed, altitude;
   private boolean canHover = false;
   public double getSpeed() { return speed; }
   private void setSpeed(double s) { speed = s; }
   public double getAltitude() { return altitude; }
   private void setAltitude(double a) { altitude = a; }
   public void hover() {
      altitude = 100;
      speed = 0;
   }

   public void fly() {
      // etc.
      if (canHover) hover();
   }
}

Introduce a new subclass:

class Helicopter extends Aircraft { }

Push helicopter specific methods down into the subclass:

class Helicopter extends Aircraft {
   public void hover() {
      setAltitude(100);
      setSpeed(0);
   }
   public void fly() {
      super.fly();
      hover();
   }
}

Remove methods and fields from super class. It may be necessary to change the scope of some members:

public class Aircraft {
   private double speed, altitude;
   //protected boolean canHover = false;
   public double getSpeed() { return speed; }
   protected void setSpeed(double s) { speed = s; }
   public double getAltitude() { return altitude; }
   protected void setAltitude(double a) { altitude = a; }
   public void fly() {
      //if (canHover) hover();
   }
}

Extract Superclass

Problem

Two classes have similar features. This is a form of code duplication.

Solution

Introduce an abstract superclass. Iteratively use Pull Up Field, Pull Up Method, and Pull Up Constructor Body. Extract Method can be used to extract and pull up common tasks within methods.

Method

Anti Patterns

Examples

class Helicopter {
   private double speed, altitude;
   public void takeoff() {  }
   public void fly() { }
   public void land() {  }
   public void hover() { }
}

class Airplane {
   private double _speed, _altitude;
   public void takeoff() {  }
   public void fly() { }
   public void land() {  }
   public void bank() { }
}

Step 1

Introduce a new abstract superclass:

abstract class Airplane {  }

Step 2

Use Pull Up Field to move common fields to the new superclass. Be sure the fields are used in the same way. This might also require renaming some of the fields:

abstract class Aircraft {
   protected double speed, altitude;
}

class Helicopter extends Aircraft {
   public void takeoff() {  }
   public void fly() { }
   public void land() {  }
   public void hover() { }
}

class Airplane extends Aircraft {
   public void takeoff() {  }
   public void fly() { }
   public void land() {  }
   public void bank() { }
}

Step 3

Pull up each method. This may require changing the signature of some methods:

abstract class Aircraft {
   protected double speed, altitude;
   public void takeoff() {  }
   public void fly() { }
   public void land() {  }
}

class Helicopter extends Aircraft {
   public void hover() { }
}

class Airplane extends Aircraft {
   public void bank() { }
}

Step 4

Common tasks performed inside bank() and hover() can be extracted into methods that get pulled up to the superclass.

Replace Type Tags

Problem

Behavior is determined by a type tag. This can lead to complex and ubiquitous multi-way conditionals that perform type tag dispatches.

Solution

For each value of the type tag introduce a subclass that overrides any methods containing dispatches.

Method

Anti Patterns

Examples

Collapse Hierarchy (Introduce Type Tags)

Problem

A superclass and a subclass are not very different.

Solution

Choose which class to eliminate. Iteratively apply Pull Up Method/Field or Push Down Method/Field.

Method

Anti Patterns

Examples

Replace Inheritance by Delegation

Problem

A subclass only uses part of what it inherits from a superclass. Alternatively, objects need to dynamically alter the implementation of their superclass.

Solution

Add a reference to the superclass. Qualify all calls to inherited methods by this reference. Remove superclass declaration.

Method

Anti Patterns

Examples

Replace Delegation by Inheritance

Problem

Too many delegation operations must be performed.

Solution

Make the delegate class the superclass. Remove qualifiers from calls to delegate methods. Remove delegate reference.

Method

Anti Patterns

Examples

Replace Bi-directional Association by Unidirectional Association

Problem

Bidirectional associations add to the complexity of a program.

Zombies: Objects that should be dead but are still around because of an uncleared reference.

Solution

If a back reference is no longer needed, then eliminate it.

Method

Anti Patterns

Examples

Replace Unidirectional Association by Bi-directional Association

Problem

A back link is required.

Solution

Decide which class will control the association.

Method

Anti Patterns

Examples

Replace Values by References

Problem

The identity of a value object is determined by its fields. Two value objects are the same if and only if their fields are the same. Value objects should be immutable. Dates, numbers, and quantities are examples of value objects. It doesn't matter if a program contains multiple value objects that are the same.

The identity of a reference object is determined by what it represents. Two reference objects are the same if they both represent the same thing. Entities, events, and types are examples of reference objects. Having multiple objects in a program that represent the same thing can lead to synchronization problems and confusion.

Solution

Replace Part constructor with a static factory method that only creates new instances when needed.

Method

Anti Patterns

Examples

class Customer {
   private String name;
   public String getName() { return name; }
   public Customer(String name) { this.name = name; }
   public boolean equals(Object other) {
      if (other == null) return false;
      if (other.getClass() != this.getClass()) return false;
      Customer cust = (Customer)other;
      return name.equals(cust.getName());
   }
   // etc.
}

public class Order {
   private Customer customer;
   public Customer getCustomer() { return customer; }
   public Order(String customerName) {
      customer = new Customer(customerName);
   }
   // etc.
}

if (customer.equals(order.getCustomer())) { ... }



Step 1: Replace the constructor of the value class by a factory method:

class Customer {
   private String name;
   public String getName() { return name; }
   public static Customer makeCustomer(String name) {
      return new Customer(name);
   }
   private
Customer(String name) { this.name = name; }
   public boolean equals(Object other) {
      if (other == null) return false;
      if (other.getClass() != this.getClass()) return false;
      Customer cust = (Customer)other;
      return name.equals(cust.getName());
   }
   // etc.
}

public class Order {
   private Customer customer;
   public Customer getCustomer() { return customer; }
   public Order(String customerName) {
      customer = Customer.makeCustomer(customerName);
   }
   // etc.
}

Step 2: Alter the factory method to return a reference:

class Customer {
   private String name;
   public String getName() { return name; }
   private static Map registry = new Hashtable();
   public static Customer makeCustomer(String name) {
      Customer cust = (Customer) registry.get(name);
      if (cust == null) {
         cust = new Customer(name);
         registry.put(name, cust);
      }
      return cust;
   }
   private Customer(String name) {
      this.name = name;
   }
   // etc.
}

We may want to add a new Order constructor:

   public Order(Customer customer) {
      this.customer = customer;
   }

Replace References by Values

Problem

Immutable objects don't really need to be references.

Solution

Add equals and hash methods to value class that respect logically equality:

Method

Anti Patterns

Examples

The center of a circle is a point:

class Circle {
   Point center;
   double radius;
   // etc.
}

A point is completely determined by its x and y coordinates, which can't be changed:

class Point {
   double xc, yc;
   public String toString() {
      return "(" + xc + ", " + yc + ")";
   }
   // etc.
}

We override the inherited equality, which simply tests for literal equality, with a version that tests for logical equality. To make hash tables work properly, we must also replace the inherited hash code function with one that guarantees logically distinct points will have different hash codes:

class Point {
   private double xc, yc;
   public String toString() {
      return "(" + xc + ", " + yc + ")";
   }
   public boolean equal(Object other) {
      if (other == null) return false;
      if (other.getClass() != getClass()) return false;
      Point p = (Point)other;
      return p.xc == xc && p.yc == yc;
   }
   public int hashCode() {
      return toString().hashCode();
   }
   // etc.
}

Inline Class

Problem

Solution

Method

Anti Patterns

Examples

Extract Class

Problem

Solution

Method

Anti Patterns

Examples

Hide Delegation

Problem

Solution

Method

Anti Patterns

Examples

 

Eliminating the Middleman

Problem

Solution

Method

Anti Patterns

Examples