13 Lecture 13: Abstracting over behavior
Defining function objects to abstract over behavior
Related files:
BostonMarathon.java
Introduction: Young at Heart — The Boston Marathon
The most popular Boston sports event takes place every year on a Monday in April, on the Patriot’s Day holiday. It is not a baseball game, or a football game. There are over 10000 athletes who take part in the event, with over 100000 spectators, bringing the whole city to a standstill for most of the day.
The Boston Marathon has been held for over 100 years, with some of the
runners becoming legends. The best known among them is Johnny Kelly,
who died in 2004 after having run the marathon over than fifty
times—
Our challenge today is to help the organizers to keep track of all the runners. For each runner we need to record the name, the age, the bib number, the runner’s time (in minutes), and whether the runner is male or female. We’ll also record the starting position of each runner. The marathon itself can be represented by a list of runners.
+-----------+ |
| ILoRunner |<-------------------+ |
+-----------+ | |
+-----------+ | |
/ \ | |
--- | |
| | |
---------------------- | |
| | | |
+------------+ +----------------+ | |
| MtLoRunner | | ConsLoR | | |
+------------+ +----------------+ | |
+------------+ +-| Runner first | | |
| | ILoRunner rest |----+ |
| +----------------+ |
| |
v |
+----------------+ |
| Runner | |
+----------------+ |
| String name | |
| int age | |
| int bib | |
| boolean isMale | |
| int pos | |
| int time | |
+----------------+ |
For today, we are simply going to look at various groups of runners. We’d like to find out all the runners who are male, and all the runners who are female. We’d like to find all the runners who start in the pack of the first 50 runners. We’d like to find all runners who finish the race in under four hours. We’d like to find all runners younger than age 40. Later we’ll ask more complicated questions, too.
13.1 Warmup: answering the first few questions
We start with examples of runners and lists of runners:
// In Examples class Runner johnny = new Runner("Kelly", 97, 999, true, 360); Runner frank = new Runner("Shorter", 32, 888, true, 130); Runner bill = new Runner("Rogers", 36, 777, true, 129); Runner joan = new Runner("Benoit", 29, 444, false, 155); ILoRunner mtlist = new MTLoRunner(); ILoRunner list1 = new ConsLoRunner(johnny, new ConsLoRunner(joan, mtlist)); ILoRunner list2 = new ConsLoRunner(frank, new ConsLoRunner(bill, list1));
Do Now!
What method (or methods) will we need to define to achieve this? What classes or interfaces should we modify to do so?
// In ILoRunner ILoRunner findAllMaleRunners(); ILoRunner findAllFemaleRunners();
// In MtLoRunner public ILoRunner findAllMaleRunners() { return this; } public ILoRunner findAllFemaleRunners() { return this; }
// In ConsLoRunner public ILoRunner findAllMaleRunners() { if (this.first.isMale) { return new ConsLoRunner(this.first, this.rest.findAllMaleRunners()); } else { return this.rest.findAllMaleRunners(); } } public ILoRunner findAllFemaleRunners() { if (!this.first.isMale) { return new ConsLoRunner(this.first, this.rest.findAllFemaleRunners()); } else { return this.rest.findAllFemaleRunners(); } }
Do Now!
How?
// In Runner public boolean isMaleRunner() { return this.isMale; }
// In ConsLoRunner public ILoRunner findAllMaleRunners() { if (this.first.isMaleRunner()) { return new ConsLoRunner(this.first, this.rest.findAllMaleRunners()); } else { return this.rest.findAllMaleRunners(); } } public ILoRunner findAllFemaleRunners() { if (!this.first.isMaleRunner()) { return new ConsLoRunner(this.first, this.rest.findAllFemaleRunners()); } else { return this.rest.findAllFemaleRunners(); } }
// In Examples class boolean testFindMethods(Tester t) { return t.checkExpect(this.list2.findAllFemaleRunners(), new ConsLoRunner(this.joan, new MtLoRunner())) && t.checkExpect(this.list2.findAllMaleRunners(), new ConsLoRunner(this.frank, new ConsLoRunner(this.bill, new ConsLoRunner(this.johnny, new MtLoRunner())))); }
Do Now!
Following the pattern above, design this method. What helpers are needed?
// In MtLoRunner public ILoRunner findRunnersInFirst50() { return this; }
// In ConsLoRunner public ILoRunner findRunnersInFirst50() { if (this.first.posUnder50()) { return new ConsLoRunner(this.first, this.rest.findRunnersInFirst50()); } else { return this.rest.findRunnersInFirst50(); } }
// In Runner boolean posUnder50() { return this.pos <= 50; }
13.2 Abstracting over behavior: Function objects
// In MtLoRunner public ILoRunner find...() { return this; }
// In ConsLoRunner public ILoRunner find...() { if (this.first...) { return new ConsLoRunner(this.first, this.rest.find...()); } else { return this.rest.find...(); } }
Do Now!
Do you see a way around this problem? Think back to Assignment 2...
class RunnerIsMale { boolean isMaleRunner(Runner r) { return r.isMale; } } class RunnerIsFemale { boolean isFemaleRunner(Runner r) { return !r.isMale; } } class RunnerIsInFirst50 { boolean isInFirst50(Runner r) { return r.pos <= 50; } }
interface IRunnerPredicate { boolean apply(Runner r); } class RunnerIsMale implements IRunnerPredicate { public boolean apply(Runner r) { return r.isMale; } } class RunnerIsFemale implements IRunnerPredicate { public boolean apply(Runner r) { return !r.isMale; } } class RunnerIsInFirst50 implements IRunnerPredicate { public boolean apply(Runner r) { return r.pos <= 50; } }
// In ILoRunner ILoRunner find(IRunnerPredicate pred);
// In MtLoRunner public ILoRunner find(IRunnerPredicate pred) { return this; }
// In ConsLoRunner public ILoRunner find(IRunnerPredicate pred) { if (pred.apply(this.first)) { return new ConsLoRunner(this.first, this.rest.find(pred)); } else { return this.rest.find(pred); } }
This is certainly less convenient than lambda, which let us define new, anonymous functions whenever and wherever they were needed. The latest versions of Java are now, finally, adding more convenient syntax to make defining lambdas easier...
// In Examples class boolean testFindMethods(Tester t) { return t.checkExpect(this.list2.find(new RunnerIsFemale()), new ConsLoRunner(this.joan, new MtLoRunner())) && t.checkExpect(this.list2.find(new RunnerIsMale()), new ConsLoRunner(this.frank, new ConsLoRunner(this.bill, new ConsLoRunner(this.johnny, new MtLoRunner())))); }
Do Now!
Design whatever helper methods or classes you need to solve the problem, “Find all runners who finish in under 4 hours.”
class FinishIn4Hours implements IRunnerPredicate { public boolean apply(Runner r) { return r.time < 240; } }
// In Examples class boolean testFindUnder4Hours(Tester t) { return t.checkExpect(this.list2.find(new FinishIn4Hours()), new ConsLoRunner(this.frank, new ConsLoRunner(this.bill, new ConsLoRunner(this.joan, new MtLoRunner())))); }
13.3 Compound questions
How might we find the list of all male runners who finish in under 4 hours? How might we find the list of all female runners younger than 40 who started in the first 50 starting positions? We could continue to define new IRunnerPredicate classes for each of these...but notice that we’ve already answered each of the component questions here. It would be a shame not to be able to reuse their implementations.
Do Now!
What logical operator is being used in the combined questions above?
// Represents a predicate that is true whenever both of its component predicates are true class AndPredicate implements IRunnerPredicate { IRunnerPredicate left, right; AndPredicate(IRunnerPredicate left, IRunnerPredicate right) { this.left = left; this.right = right; } public boolean apply(Runner r) { return this.left.apply(r) && this.right.apply(r); } }
Do Now!
Use this new class to answer the questions above.
// In Examples class boolean testCombinedQuestions(Tester t) { return t.checkExpect(this.list2.find( new AndPredicate(new RunnerIsMale(), new FinishIn4Hours())), new ConsLoRunner(this.frank, new ConsLoRunner(this.bill, new ConsLoRunner(this.joan, new MtLoRunner())))) && t.checkExpect(this.list2.find( new AndPredicate(new RunnerIsFemale(), new AndPredicate(new RunnerIsYounger40(), new RunnerIsInFirst50()))), new ConsLoRunner(this.joan, new MtLoRunner())); }
Do Now!
Design an answer to the problem, “Find all runners who are female or who finish in less than 4 hours.”