6: Function objects
In Advanced C++:Programming Styles And
Idioms (Addison-Wesley, 1992), Jim Coplien coins the term functor
which is an object whose sole purpose is to encapsulate a function (since
“functor” has a meaning in mathematics, in this book I shall use the
more explicit term function object). The point is to decouple the choice
of function to be called from the site where that function is called.
This term is mentioned but not used in
Design Patterns. However, the theme of the function object is repeated in
a number of patterns in that
book.
Command: choosing the operation at run-time
This is the function object in its purest
sense: a method that’s an
object[10]. By
wrapping a method in an object, you can pass it to other methods or objects as a
parameter, to tell them to perform this particular operation in the process of
fulfilling your request.
//: c06:CommandPattern.java
import java.util.*;
import com.bruceeckel.test.*;
interface Command {
void execute();
}
class Hello implements Command {
public void execute() {
System.out.print("Hello ");
}
}
class World implements Command {
public void execute() {
System.out.print("World! ");
}
}
class IAm implements Command {
public void execute() {
System.out.print("I'm the command pattern!");
}
}
// An object that holds commands:
class Macro {
private List commands = new ArrayList();
public void add(Command c) { commands.add(c); }
public void run() {
Iterator it = commands.iterator();
while(it.hasNext())
((Command)it.next()).execute();
}
}
public class CommandPattern extends UnitTest {
Macro macro = new Macro();
public void test() {
macro.add(new Hello());
macro.add(new World());
macro.add(new IAm());
macro.run();
}
public static void main(String args[]) {
new CommandPattern().test();
}
} ///:~
The primary point of
Command is to allow you to hand a desired action to a method or object.
In the above example, this provides a way to queue a set of actions to be
performed collectively. In this case, it allows you to dynamically create new
behavior, something you can normally only do by writing new code but in the
above example could be done by interpreting a script (see the Interpreter
pattern if what you need to do gets very complex).
Another example of Command is
c12:DirList.java. The DirFilter class is the command object which
contains its action in the method accept( ) that is passed to the
list( ) method. The list( ) method determines what to
include in its result by calling accept( ).
Design Patterns says that
“Commands are an object-oriented replacement for
callbacks[11].”
However, I think that the word “back” is an essential part of the
concept of callbacks. That is, I think a callback actually reaches back to the
creator of the callback. On the other hand, with a Command object you
typically just create it and hand it to some method or object, and are not
otherwise connected over time to the Command object. That’s my take
on it, anyway. Later in this book, I combine a group of design patterns under
the heading of
“callbacks.”
Strategy: choosing the algorithm at run-time
Strategy appears to be a family of
Command classes, all inherited from the same base. But if you look at
Command, you’ll see that it has the same structure: a hierarchy of
function objects. The difference is in the way this hierarchy is used. As seen
in c12:DirList.java, you use Command to solve a particular
problem—in that case, selecting files from a list. The “thing that
stays the same” is the body of the method that’s being called, and
the part that varies is isolated in the function object. I would hazard to say
that Command provides flexibility while you’re writing the program,
whereas Strategy’s flexibility is at run time. Nonetheless, it
seems a rather fragile distinction.
Strategy also adds a
“Context” which can be a surrogate class that controls the selection
and use of the particular strategy object—just like State!
Here’s what it looks like:
//: c06:strategy:StrategyPattern.java
package c06.strategy;
import com.bruceeckel.util.*; // Arrays2.print()
import com.bruceeckel.test.*;
// The strategy interface:
interface FindMinima {
// Line is a sequence of points:
double[] algorithm(double[] line);
}
// The various strategies:
class LeastSquares implements FindMinima {
public double[] algorithm(double[] line) {
return new double[] { 1.1, 2.2 }; // Dummy
}
}
class NewtonsMethod implements FindMinima {
public double[] algorithm(double[] line) {
return new double[] { 3.3, 4.4 }; // Dummy
}
}
class Bisection implements FindMinima {
public double[] algorithm(double[] line) {
return new double[] { 5.5, 6.6 }; // Dummy
}
}
class ConjugateGradient implements FindMinima {
public double[] algorithm(double[] line) {
return new double[] { 3.3, 4.4 }; // Dummy
}
}
// The "Context" controls the strategy:
class MinimaSolver {
private FindMinima strategy;
public MinimaSolver(FindMinima strat) {
strategy = strat;
}
double[] minima(double[] line) {
return strategy.algorithm(line);
}
void changeAlgorithm(FindMinima newAlgorithm) {
strategy = newAlgorithm;
}
}
public class StrategyPattern extends UnitTest {
MinimaSolver solver =
new MinimaSolver(new LeastSquares());
double[] line = {
1.0, 2.0, 1.0, 2.0, -1.0,
3.0, 4.0, 5.0, 4.0 };
public void test() {
Arrays2.print(solver.minima(line));
solver.changeAlgorithm(new Bisection());
Arrays2.print(solver.minima(line));
}
public static void main(String args[]) {
new StrategyPattern().test();
}
} ///:~
Note similarity with
template method – TM claims distinction that it has more than one method
to call, does things piecewise. However, it’s not unlikely that strategy
object would have more than one method call; consider Shalloway’s order
fulfullment system with country information in each strategy.
Strategy example from JDK: comparator
objects.
Chain of responsibility
Chain of Responsibility might be
thought of as a dynamic generalization of recursion using Strategy
objects. You make a call, and each Strategy in a linked sequence
tries to satisfy the call. The process ends when one of the strategies is
successful or the chain ends. In recursion, one method calls itself over and
over until a termination condition is reached; with Chain of
Responsibility, a method calls itself, which (by moving down the chain of
Strategies) calls a different implementation of the method, etc.,
until a termination condition is reached. The termination condition is either
the bottom of the chain is reached (in which case a default object is returned;
you may or may not be able to provide a default result so you must be able to
determine the success or failure of the chain) or one of the Strategies
is successful.
Instead of calling a single method to
satisfy a request, multiple methods in the chain have a chance to satisfy the
request, so it has the flavor of an expert system. Since the chain is
effectively a linked list, it can be dynamically created, so you could also
think of it as a more general, dynamically-built switch
statement.
In the GoF, there’s a fair amount
of discussion of the implementation details of the
chain of responsibility as a linked list. However, when you look at the pattern
it really shouldn’t matter how the chain is maintained; that’s an
implementation detail. Since GoF was written before the Standard Template
Library (STL) was incorporated into most C++ compilers, the reason for this is
most likely (1) there was no list and thus they had to create one and (2) data
structures are often taught as a fundamental skill in academia, and the idea
that data structures should be standard tools available with the programming
language may not have occurred to the GoF authors. I maintain that the
implementation of Chain of Responsibility as a chain (specifically, a
linked list) adds nothing to the solution and can just as easily be implemented
using a standard Java List, as shown below. Furthermore, you’ll see
that I’ve gone to some effort to separate the chain-management parts of
the implementation from the various Strategies, so that the code can be
more easily reused.
In StrategyPattern.java, above,
what you probably want is to automatically find a solution. Chain of
Responsibility provides a way to do this by chaining the Strategy
objects together and providing a mechanism for them to automatically recurse
through each one in the chain:
//: c06:ChainOfResponsibility.java
import com.bruceeckel.util.*; // Arrays2.print()
import com.bruceeckel.test.*;
import java.util.*;
// Carry the information into the strategy:
interface Messenger {}
// The Result object carries the result data and
// whether the strategy was successful:
class Result {
private boolean succeeded;
public boolean isSuccessful() {
return succeeded;
}
public void setSuccessful(boolean b) {
succeeded = b;
}
}
abstract class Strategy {
abstract public Result strategy(Messenger m);
}
// Manage the movement through the chain and
// find a successful result:
class ChainLink {
private List chain;
private Strategy strat;
public ChainLink(List chain, Strategy s) {
strat = s;
this.chain = chain;
chain.add(this);
}
public ChainLink next() {
// Where this link is in the chain:
int location = chain.indexOf(this);
if (!end())
return (ChainLink)chain.get(location + 1);
// Indicates a programming error (thus
// doesn't need to be a checked exception):
throw new RuntimeException(
"Tried to move past end of chain");
}
public boolean end() {
int location = chain.indexOf(this);
return location + 1 >= chain.size();
}
public Result strategy(Messenger m) {
Result r = strat.strategy(m);
if(r.isSuccessful() || end()) return r;
return next().strategy(m);
}
}
// For this example, the Messenger
// and Result can be the same type:
class LineData
extends Result implements Messenger {
public double[] data;
public LineData(double[] data) {
this.data = data;
}
}
class LeastSquares extends Strategy {
public Result strategy(Messenger m) {
System.out.println(
"Trying LeastSquares algorithm");
LineData ld = (LineData)m;
// [ Actual test/calculation here ]
Result r = new LineData(
new double[] { 1.1, 2.2 }); // Dummy data
r.setSuccessful(false);
return r;
}
}
class NewtonsMethod extends Strategy {
public Result strategy(Messenger m) {
System.out.println(
"Trying NewtonsMethod algorithm");
LineData ld = (LineData)m;
// [ Actual test/calculation here ]
Result r = new LineData(
new double[] { 3.3, 4.4 }); // Dummy data
r.setSuccessful(false);
return r;
}
}
class Bisection extends Strategy {
public Result strategy(Messenger m) {
System.out.println(
"Trying Bisection algorithm");
LineData ld = (LineData)m;
// [ Actual test/calculation here ]
Result r = new LineData(
new double[] { 5.5, 6.6 }); // Dummy data
r.setSuccessful(true);
return r;
}
}
class ConjugateGradient extends Strategy {
public Result strategy(Messenger m) {
System.out.println(
"Trying ConjugateGradient algorithm");
LineData ld = (LineData)m;
// [ Actual test/calculation here ]
Result r = new LineData(
new double[] { 5.5, 6.6 }); // Dummy data
r.setSuccessful(true);
return r;
}
}
public
class ChainOfResponsibility extends UnitTest {
List chain = new ArrayList();
ChainLink[] solutions = {
new ChainLink(chain, new LeastSquares()),
new ChainLink(chain, new NewtonsMethod()),
new ChainLink(chain, new Bisection()),
new ChainLink(chain, new ConjugateGradient()),
};
LineData line = new LineData(new double[]{
1.0, 2.0, 1.0, 2.0, -1.0,
3.0, 4.0, 5.0, 4.0
});
public void test() {
Arrays2.print(
((LineData)solutions[0]
.strategy(line)).data);
}
public static void main(String args[]) {
new ChainOfResponsibility().test();
}
} ///:~
Exercises
- Use Command in
Chapter 3, Exercise
1.
- Implement
Chain of Responsibility to create an "expert system" that solves problems
by successively trying one solution after another until one matches. You should
be able to dynamically add solutions to the expert system. The test for solution
should just be a string match, but when a solution fits, the expert system
should return the appropriate type of ProblemSolver object. What other
pattern/patterns show up
[10]
In the Python language, all functions are already
objects and so the Command pattern is often redundant.
here?