unit27csce302ch23Strategy

Chapter 23
Strategy
Summary prepared by Kirk Scott
1
2
3
Design Patterns in Java
Chapter 23
Strategy
Summary prepared by Kirk Scott
4
The Introduction Before the
Introduction
• In general, a single strategy might be thought
of as an algorithm or an operation
• In the context of the Strategy design pattern,
the idea is that there are multiple approaches
to doing something, depending on certain
conditions or context
• The Strategy design pattern, then, depends on
picking the approach or picking the strategy
5
• The purpose of the Strategy design pattern is
to separate the implementations of different
strategies from each other
• It also separates the code for picking the
strategy from the strategy implementations
6
• The pattern defines a single interface for all
strategies
• The separate strategies are implemented with
a method of the same name in each of the
classes implementing the interface
7
• Which strategy is used will depend on what
kind of object the method implementing the
strategy is called on
• The intent of the pattern is realized through
an interface and depends on polymorphism
and dynamic binding
8
Book Definition of Pattern
• Book definition:
• The intent of Strategy is to encapsulate
alternative approaches, or strategies, in
separate classes that each implement a
common operation.
9
Modeling Strategies
• Like with the previous chapters, and others,
the book illustrates the Strategy design
pattern in the following way:
• It develops an example with multiple
strategies that doesn’t use the Strategy design
pattern
• It then refactors the example using the
Strategy design pattern
10
Example Scenario
• When a potential customer calls in, interested
in buying fireworks, there is software which
will make a recommendation or suggestion
• There are several different ways a
recommendation can be made
11
• Ways of recommending a purchase in this
scenario:
• Recommend a particular firework that is being
promoted
• Use a piece of software, Rel8
• Use another piece of software, LikeMyStuff
• Use a default recommendation option
12
Promoted Firework
• There is nothing special about this option
• If the company is promoting a firework,
recommend it
13
Rel8
• Rel8 relies on a customer’s already being
registered
• During registration the customer specifies
preferences in entertainment and fireworks
• Rel8 makes a suggestion based on the similarity
of the customer to other customers (presumably
suggesting something that similar customers have
tended to buy)
• If the customer isn’t registered, Rel8 can’t be
used
14
LikeMyStuff
• LikeMyStuff doesn’t rely on pre-registration,
but it does rely on customer information
• The idea is that it will make a
recommendation based on a profile of recent
purchases by the customer
• If not enough data can be obtained to form
the profile, then LikeMyStuff can’t be used
15
The Default Option
• This is the default:
• If none of the previous options applies, then a
firework is suggested at random
16
UML for the Scenario
• The UML diagram on the following overhead
shows the classes involved in the design as
described so far
• Appendix D on UML clarifies the notation:
• “Use a dashed arrow between classes to show
a dependency that does not use an object
reference. For example, the Customer class
relies on a static method from the LikeMyStuff
recommendation engine.”
17
18
The getRecommended() Method
• Viewing the scenario from the top down, what
you have is this:
• The Customer class has a getRecommended()
method in it
• This method consists of if/else code which
chooses one of the strategies, whether to do a
promotion, or to use Rel8, LikeMyStuff, or the
default
19
Doing a Promotion
• If there is a promotion underway, the first part
of the logic of getRecommended() deals with
that case
• The logic for doing a promotion consists of
looking up the contents of a file named
strategy.dat in a directory named config
• If there is such a file, its contents should look
something like this: promote=JSquirrel
20
• The basic idea is that if the data file is not
empty, the firework it contains is returned
• If its contents come up null you go on to the
next option
• Also, if the file read doesn’t work, you don’t
do anything in the catch block, you just
continue on to the other options
21
Using Rel8
• The Rel8 class has a method advise()
• getRecommended() wraps a call to advise() if the
Rel8 strategy is selected
• The call looks like this:
• if(isRegistered())
•
return (Firework) Rel8.advise(this);
• “this” is the customer, and Rel8 relies entirely on
the information contained in the registered
customer object
22
Using LikeMyStuff
• The LikeMyStuff class has a suggest() method
• getRecommended() wraps a call to suggest() if
the LikeMyStuff strategy is selected
• The call looks like this:
• if(spendingSince(cal.getTime()) > 1000)
•
return (Firework) LikeMyStuff.suggest(this);
23
• spendingSince() is called on the implicit
parameter, customer
• cal in the parameter refers to an instance of
Calendar
• getTime() specifies a recent period of time
• “this” is the customer, which is sent as a
parameter to suggest()
24
• suggest() relies on a database of recent
purchases by that customer
• The idea is that if the customer has recently
spent $1,000, those purchases provide the
basis for a recommendation
25
Doing the Default
• The Firework class has a getRandom()
method, so if all else fails, getRecommended()
wraps a call to that method
26
The Code for getRecommended() in
the Customer Class
• The code for getRecommended() is shown on
the following overheads
• It is a collection of if statements.
• It is unfortunate that it is not organized as a
sequence of if/else if’s.
27
The Code for getRecommended()
•
•
•
•
•
•
•
•
•
•
•
•
•
•
public Firework getRecommended()
{
// if promoting a particular firework, return it
try
{
Properties p = new Properties();
p.load(ClassLoader.getSystemResourceAsStream(“config/strategy.dat”));
String promotedName = p.getProperty(“promote”);
if(promotedName != null)
{
Firework f = Firework.lookup(promotedName);
if(f != null)
return f;
}
28
•
•
•
•
•
•
•
•
•
•
catch(Exception ignored)
{
// If resource is missing or it failed to load,
// fall through to the next approach.
}
// if registered, compare to other customers
if(isRegistered())
{
return (Firework) Rel8.advise(this);
}
29
•
•
•
•
•
// check spending over the last year
Calendar cal = Calendar.getInstance();
cal.add(Calendar.YEAR, -1);
if(spendingSince(cal.getTime()) > 1000)
return (Firework) LikeMyStuff.suggest(this);
•
•
•
// oh well!
return Firework.getRandom();
}
30
What’s Wrong with the Initial Design
• The book identifies two basic problems with
the getRecommended() method as given:
• It’s too long
• It combines both selecting a strategy and
executing it
31
• This is actually one of the high points of the
book
• It explains that you know that the method is
too long because you need to put comments
in it
• “Short methods are easy to understand,
seldom need explanation…”
32
Comments Are Bad…
• Finally, what every student always knew:
Comments are bad…
• More accurately, you might facetiously say
that code which requires comments is bad.
• The book doesn’t say that putting a comment
at the beginning for the whole method is bad.
• A useful observation might be that a method
should be short and sweet enough that it
doesn’t need internal commenting.
33
Refactoring to the Strategy Pattern
• Applying the Strategy design pattern involves
three things:
• 1. Creating an interface that defines the
strategic operation
• 2. Writing classes that implement the
interface and embody each of the different
strategies
• 3. Refactoring the code to select and use an
instance of the right strategy class
34
The Interface
• 1. The interface for this example will be
named Advisor
• The interface requires the implementation of
a recommend() method
• The recommend() method will take a
customer as a parameter
• It will return a firework
• A UML diagram of the interface is given on the
next overhead
35
36
The Implementing Classes
• 2. The next step is to write the classes that
implement the interface and embody each of
the different strategies
• These classes will have to implement the
recommend() method
37
• The book does the refactoring in part with
challenges
• As usual, it’s easiest to just look at the solutions
• The UML diagram on the following overhead
shows:
– A new Customer class making use of an Advisor
interface
– 4 classes which implement the interface and embody
the 4 strategies
38
Solution 23.1
39
The Implementing Classes
• The PromotionAdvisor and RandomAdvisor class
names should be self-explanatory
• GroupAdvisor refers to the use of Rel8
• ItemAdvisor refers to the use of LikeMyStuff
• The implementations of the recommend()
method for these classes will wrap a call to the
static methods of Rel8 and LikeMyStuff
• An expanded UML diagram for these two classes
is given on the next overhead
40
41
Making Instances of the Implementing
Classes
• An interface can’t define static methods
• An interface defines what the book calls
“object methods”—methods that are called
on objects
• That means that client code will have to make
instances of GroupAdvisor and ItemAdvisor
• The recommend() method will be called on
these objects
42
• Only one instance each of GroupAdvisor and
ItemAdvisor are needed
• In the refactored design, these instances will
be static objects in the Customer class
• So the advisor objects will be “singleton like”
• There won’t be an instance of each kind of
advisor for each customer
43
• Even though the recommend() method isn’t a
static method, it more or less acts like one
• If there is only one advisor object, then there is
the one recommend() method that can be called
on that object
• The recommend() method does something for
customers
• But it does so by taking the customer as an
explicit parameter rather than being called on the
customer
44
Code for the recommend() Method in
the GroupAdvisor Class
• This is the recommend() method in the
GroupAdvisor class:
• public Firework recommend(Customer c)
• {
•
return (Firework) Rel8.advise(c);
• }
• It wraps a call to the advise() method of Rel8
• In essence, the call is adapted to the
recommend() interface of Advisor
45
Code for the recommend() Method in
the ItemAdvisor Class
• The code for the recommend() method in the
ItemAdvisor class is analogous.
• The book doesn’t give it and it doesn’t even
bother to give it as a challenge.
• It should be straightforward to write that
method.
46
• Challenge 23.2
• “In addition to Strategy, what pattern appears
in the GroupAdvisor and ItemAdvisor
classes?”
• [The answer to this was given away in the last
remark about the recommend() code in
GroupAdvisor.]
47
• Solution 23.2
• “The GroupAdvisor and ItemAdvisor classes
are instances of Adapter, providing the
interface a client expects, using the services of
a class with a different interface.”
48
Code for the recommend() Method in
the PromotionAdvisor Class
• A PromotionAdvisor class is also needed, with
a recommend() method
• On the one hand, promotion should be a
simple case
• On the other hand, the book puts a lot of
detail into the implementation
49
• Most of the logic of the original code is moved
into the constructor for the new class
• If a promotion is on, then the promoted
instance variable of the class is initialized
• In addition to the recommend() method, there
is a hasItem() method which can be called to
see whether a promoted item is available
50
• The book’s implementation makes use of class
loading logic
• This requires try/catch blocks
• The details of this technique will not be
covered since they are extraneous to the
design pattern
• The code is shown on the following overheads
51
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
public class PromotionAdvisor implements Advisor
{
private Firework promoted;
public PromotionAdvisor()
{
try
{
Properties p = new Properties();
p.load(ClassLoader.getSystemResourceAsStream("config/strategy.dat"));
String promotedFireworkName = p.getProperty("promote");
if (promotedFireworkName != null)
promoted = Firework.lookup(promotedFireworkName);
}
catch (Exception ignored)
{
// Resource not found or failed to load
promoted = null;
}
}
52
•
•
•
•
•
•
•
•
• }
public boolean hasItem()
{
return promoted != null;
}
public Firework recommend(Customer c)
{
return promoted;
}
53
Code for the recommend() Method in
the RandomAdvisor Class
• The RandomAdvisor class is simple
• Its code is shown on the following overhead
54
• public class RandomAdvisor implements Advisor
• {
•
public Firework recommend(Customer c)
•
{
•
return Firework.getRandom();
•
}
• }
55
Refactoring the Customer Class to Use
the Interface
56
Creating the Advisor Objects
• A single instance of each kind of advisor is
created in the new Customer2 class
• For any given customer, recommend() may be
called on one of these advisor objects
• The code for the new customer class,
Customer2, begins by creating these objects
• This code is shown on the following overhead
57
• private static PromotionAdvisor
promotionAdvisor =
• new PromotionAdvisor();
• private static GroupAdvisor groupAdvisor =
• new GroupAdvisor();
• private static ItemAdvisor itemAdvisor =
• new ItemAdvisor();
• private static RandomAdvisor randomAdvisor =
• new RandomAdvisor();
58
Implementing the getAdvisor()
Method
• Customer2 contains a method named
getAdvisor() for picking which kind of advisor
to use
• The original design had if/else statements in
which different strategies were called
• In the new design you don’t eliminate if/else
statements, but they appear in a different
place
59
• In the new design the if/else logic is
implemented in the getAdvisor() method
• getAdvisor() returns an advisor object
• Polymorphism and dynamic binding
determine which strategy is used when
recommend() is called on the advisor object
60
How the getAdvisor() Method Works
• Recall that getAdvisor() is in the Customer2
class
• Its implementation includes a call to
hasItem(), mentioned earlier to check for a
promotion
• Its implementation also includes calls to
isRegistered() and isBigSpender() on the
implicit customer parameter to determine if
the advisor should be Rel8 or LikeMyStuff
61
• getAdvisor() does lazy initialization of the
advisor
• The value of the advisor instance variable is
only set at the time the getAdvisor() method is
called
• It’s not lazy construction because one instance
of each kind of advisor has already been
created at the top of Customer2
• The code is shown on the following overhead
62
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
private Advisor getAdvisor()
{
if (advisor == null)
{
if (promotionAdvisor.hasItem())
advisor = promotionAdvisor;
else if (isRegistered())
advisor = groupAdvisor;
else if (isBigSpender())
advisor = itemAdvisor;
else
advisor = randomAdvisor;
}
return advisor;
}
63
The getRecommended() Method in
Customer2
• Challenge 23.3
• “Write the new code for
Customer.getRecommended().”
• Comment mode on:
• It may be helpful to step back and review
where we’re at in the redesign
• The UML for the redesign is repeated on the
following overhead
64
65
• We’ve got the 4 advisor classes
• We’ve got the getAdvisor() method
• The final ingredient of the new design is the
getRecommended() method in the Customer2
class
• This code should rely on the advisor for the
customer, and a call to the recommend()
method on that advisor
• The code is shown on the following overhead
66
Solution 23.3
• Your code should look something like:
• public Firework getRecommended()
• {
•
return getAdvisor().recommend(this);
• }
67
68
The End
69