A class has features that are only used by some instances.
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();
}
}
Step 1: Introduce a new subclass:
class Helicopter extends Aircraft { }
Step 2: 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();
}
}
Step 3: 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();
}
}
Two classes have similar features. This is a form of code duplication.
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 Aircraft { }
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.
A class has too many responsibilities.
A class has too few responsibilities.
Behavior is determined by a type tag. This can lead to complex and ubiquitous multi-way conditionals that perform type tag dispatches.
class Aircraft {
public static final void AIRPLANE = 0;
public static final void HELICOPTER =
1;
public static final void BLIMP = 2;
private int type;
public void fly() {
switch(type) {
case AIRPLANE: flyAirplane();
break;
case HELICOPTER: flyHelicopter();
break;
case BLIMP: flyBlimp(); break;
default: defaultFly();
}
}
// etc.
}
Step 1: For each value of the type tag introduce a subclass that overrides any methods containing dispatches.
class Airplane extends Aircraft {
public void fly() { flyAirplane(); }
// etc.
}
Step 2: Replace body of original methods by default code or else make them abstract:
class Aircraft {
public static final void AIRPLANE = 0;
public static final void HELICOPTER =
1;
public static final void BLIMP = 2;
private int type;
public void fly() { defaultFly();
}
// etc.
}
Step 3: Remove type tags:
class Aircraft {
public void fly() { defaultFly(); }
// etc.
}
A superclass and a subclass are not very different.
Step 1: Choose which class to eliminate.
Step 2: Iteratively apply Pull Up Method/Field or Push Down Method/Field.
A subclass only uses part of what it inherits from a superclass. Alternatively, objects need to dynamically alter the implementation of their superclass.
class Fleet extends Vector {
public void add(Aircraft a) { if
(good(a)) super.add(a); }
// etc.
}
Step 1: Add a field that references the superclass.
class Fleet extends Vector {
private Vector members = new
Vector();
// etc.
}
Step 2: Qualify all internal calls to inherited methods by this reference.
class Fleet extends Vector {
private Vector members = new Vector();
public void add(Aircraft a) { if
(good(a)) members.add(a); }
// etc.
}
Step 3: Add methods to replace external calls to inherited methods. For example, assume Fleet doesn't override the inherited isEmpty method, but the inherited isEmpty method is called through Fleet objects by external clients:
class Fleet extends Vector {
private Vector members = new Vector();
public void add(Aircraft a) { if
(good(a)) members.add(a); }
public boolean isEmpty() {
return members.isEmpty(); }
// etc.
}
Step 4: Remove superclass declaration.
class Fleet {
private Vector members = new Vector();
public void add(Aircraft a) { if
(good(a)) members.add(a); }
public boolean isEmpty() { return members.isEmpty();
}
// etc.
}
Too many delegation operations must be performed.
Step 1: Make the delegate class the superclass.
Step 2: Remove qualifiers from calls to delegate methods.
Step 3: Remove delegate reference.
Bidirectional associations add to the complexity of a program.
Removing the Customer pointer field from Account is similar to moving a field from a source class to a target class, only in this case the field is being eliminated altogether.
A back link is required.
// Account.h
class Account { ... };
// Customer.h
#include "Account.h"
class Customer {
Account* account;
public:
void setAccount(Account* acct) {
delete account;
account = acct;
}
// etc.
};
Step 1: Add a field for the back pointer:
class Account {
Customer* customer;
// etc.
};
Step 2: Decide which class will control the association. In some cases this choice is arbitrary. In other cases the choice is dictated by application logic. Collections and assemblies are usually controllers. In our example we choose Customer as the controller.
Step 3: Add a helper function to the non-controller class. The helper function sets the back pointer:
class Account {
Customer* customer;
public:
void helper(Customer* cust) {
customer = cust;
}
// etc.
};
Step 4: Modify the setter on the controller side so that it calls the helper:
class Customer {
Account* account;
public:
void setAccount(Account* acct) {
acct.helper(this);
delete account;
account = acct;
}
// etc.
};
In C++ we have added a new dependency from Account.h to Customer.h. We can't resolve this dependency with an include directive. We can add a forward reference to Account.h:
// Account.h
class Customer;
class Account {
Customer* customer;
public:
void helper(Customer* cust) {
customer = cust;
}
// etc.
};
This trick works as long as Account.h makes no assumptions about the Customer class. For example, if the helper function needed to call a customer method:
void helper(Customer*
cust) {
if (cust->important()) customer =
cust;
}
then this implementation must be moved to Account.cpp, which must now include Customer.h:
// Account.cpp
#include "Customer.h"
void Account::helper(Customer* cust) {
if (cust->important()) customer =
cust;
}
By choosing a suitably obscure name for the helper function, we may not need to worry much about this problem. There are some techniques to guarantee that users won't call the helper function.
In C++ we can always make the helper function private, but grant access to the Customer class using a friend declaration:
class Account {
friend class Customer;
Customer* customer;
void helper(Customer* cust) {
customer = cust;
}
// etc.
};
In Java, we can declare the helper function to have package scope, while the setter in the controller has public scope.
We can go one step further in Java by declaring Account to be a public interface:
public interface Account {
void deposit(double amt);
void withdraw(double amt);
Customer getCustomer();
// etc.
}
The Account interface is implemented by a private inner class of the Customer. A factory method creates the accounts:
public class Customer {
private Set accounts = new Hashset();
private class AccountImpl implements
Account {
Customer getCustomer() { return
Customer.this; }
// etc.
}
public Account makeAccount() {
Account acct = this.new
AccountImpl();
accounts.add(acct);
return acct;
}
// etc.
}
If errors are possible, then the helper function should throw an exception:
void helper(Customer*
cust) {
if (!valid()) throw
InvalidAccountError();
customer = cust;
}
Recall that the helper function was called before the customer's account pointer was set:
void setAccount(Account*
acct) {
acct.helper(this);
delete account;
account = acct;
}
Thus, if the helper throws an exception, setAccount implicitly throws an exception before any customer fields have been modified.
In practice, a customer may have many accounts. In this case the customer is equipped with a collection of some sort:
class Customer {
set<Account*> accounts;
public:
void add(Account* acct) {
acct.helper(this);
accounts.insert(acct);
}
void remove(Account* acct) {
accounts.erase(acct);
delete acct;
}
// etc.
};
In C++ the delegator is called the handle and the delegate is called the body. Following the Canonical Form pattern, it is common practice for the handle to manage the body. This means redefining the handle's assignment operator and the copy constructor so that copying the handle also copies the body. It also means providing the handle with a destructor so that deleting the handle also deletes the body. Thus, the C++ implementation of Customer might look like this:
class Customer {
Account* account;
public:
Customer(Customer& cust) {
account = new Account(cust.account);
}
~Customer() { delete account; }
Customer& operator=(Customer&
cust) {
delete account;
account = new Account(cust.account);
return *this;
}
// etc.
};
We are probably adding a back pointer because there is a way to reach an account object other than through a customer object, otherwise we would always know the customer and therefore wouldn't need a back pointer. This means there will be other pointers to an account. If deleting a customer deletes the associated account, then these pointers could become dangling references. In this situation we might want to apply the counted pointer idiom.
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.
We want to convert a composite into an aggregation. In other words, we want to change a value object container into a reference container.
Customer objects are value objects in the sense that two customer objects represent the same real world customer if and only if their name fields match:
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.
}
An order contains a customer by value:
public class Order {
private Customer customer;
public Customer getCustomer() { return
customer; }
public Order(String customerName) {
customer = new
Customer(customerName);
}
// etc.
}
Determining if a particular customer placed a particular order is awkward and inefficient:
if (customer.equals(order.getCustomer())) { ... }
The basic idea is to keep all customers in a table indexed by customer names. Each place where a customer is constructed is replaced by a factory method that first searches the table to determine if the customer object already exists.
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;
}
The situation is simpler in C++ is conceptually simpler, where we replace value objects by pointers:
Before:
class Order {
Customer customer;
public:
Customer getCustomer() { return
customer; }
// etc.
};
After:
class Order {
Customer* customer;
public:
Customer* getCustomer() { return
customer; }
// etc.
};
Syntactically this can be a headache because we have changed the type of the field.
Immutable objects don't really need to be references.
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 equals(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.
}