Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions

  




 

 

Strategy

Objects can often have variant algorithms. The usual textbook example is an object that has two choices for an algorithm, one of which is slow, but uses little memory, and the other is fast, but requires a lot of storage for all that speed. In our examples, we can use the Strategy pattern to isolate the details of a betting strategy from the rest of a casino game simulation. This will allow us to freely add new betting strategies without disrupting the simulation.

One strategy in Roulette is to always bet on black. Another strategy is to wait, counting red spins and bet on black after we've seen six or more reds in a row. These are two alternate player strategies. We can separate these betting decision algorithms from other features of player.

We don't want to create an entire subclass of player to reflect this choice of algorithms. The Strategy design pattern helps us break something rather complex, like a Player, into separate pieces. The essential features are in one object, and the algorithm(s) that might change are in separate strategy object(s). The essential features are defined in the core class, the other features are strategies that are used by the core class. We can then create many alternate algorithms as subclasses of the plug-in Strategy class. At run time, we decide which strategy object to plug into the core object.

The Two Approaches. As mentioned in the section called “Design Approaches”, we have two approaches for extending an existing class: wrapping and inheritance. From an overall view of the collection of classes, the Strategy design emphasizes wrapping. Our core class is a kind of wrapper around the plug-in strategy object. The strategy alternatives, however, usually form a proper class hierarchy and are all polymorphic.

Let's look at a contrived, but simple example. We have two variant algorithms for simulating the roll of two dice. One is quick and dirty and the other more flexible, but slower.

First, we create the basic Dice class, leaving out the details of the algorithm. Another object, the strategy object, will hold the algorithm

class Dice( object ):
    def __init__( self, strategy ):
        self.strategy= strategy
        self.lastRoll= None
    def roll( self ):
        self.lastRoll= self.strategy.roll()
        return self.lastRoll
    def total( self ):
        return reduce( lambda a,b:a+b, self.lastRoll, 0 )

The Dice class rolls the dice, and saves the roll in an instance variable, lastRoll, so that a client object can examine the last roll. The total method computes the total rolled on the dice, irrespective of the actual strategy used.

The Strategy Class Hierarchy. When an instance of the Dice class is created, it must be given a strategy object to which we have delegated the detailed algorithm. A strategy object must have the expected interface. The easiest way to be sure it has the proper interface is to make each alternative a subclass of a strategy superclass.

import random
class DiceStrategy( object ):
    def roll( self ):
        raise NotImplementedError

The DiceStrategy class is the superclass for all dice strategies. It shows the basic method function that all subclasses must override. We'll define two subclasses that provide alternate strategies for rolling dice.

The first, DiceStrategy1 is simple.

class DiceStrategy1( DiceStrategy ):
    def roll( self ):
        return ( random.randrange(6)+1, random.randrange(6)+1 )

This DiceStrategy1 class simply uses the random module to create a tuple of two numbers in the proper range and with the proper distribution.

The second alternate strategy, DiceStrategy2, is quite complex.

class DiceStrategy2( DiceStrategy ):
    class Die:
        def __init__( self, sides=6 ):
            self.sides= sides
        def roll( self ):
            return random.randrange(self.sides)+1
    def __init__( self, set=2, faces=6 ):
        self.dice = tuple( DiceStrategy2.Die(faces) for d in range(set) )
    def roll( self ):
        return tuple( x.roll() for x in self.dice )

This DiceStrategy2 class has an internal class definition, Die that simulates a single die with an arbitrary number of faces. An instance variable, sides shows the number of sides for the die; the default number of sides is six. The roll method returns are random number in the correct range.

The DiceStrategy2 class creates a number of instances of Die objects in the instance variable dice. The default is to create two instances of Die objects that have six faces, giving us the standard set of dice for craps. The roll function creates a tuple by applying a roll function to each of the Die objects in self.dice.

Creating Dice with a Plug-In Strategy. We can now create a set of dice with either of these strategies.

dice1= Dice( DiceStrategy1() )
dice2 = Dice( DiceStrategy2() )

The dice1 instance of Dice uses an instance of the DiceStrategy1 class. This strategy object is used to constuct the instance of Dice. The dice2 variable is created in a similar manner, using an instance of the DiceStrategy2 class.

Both dice1 and dice2 are of the same class, Dice, but use different algorithms to achieve their results. This technique gives us tremendous flexibility in designing a program.

Multiple Patterns. Construction of objects using the strategy pattern works well with a Factory Method pattern, touched on in the section called “Factory Method”. We could, for instance, use a Factory Method to decode input parameters or command-line options. This give us something like the following.

class MakeDice( object ):
    def newDice( self, strategyChoice ):
        if strategyChoice == 1: 
            strat= DiceStrategy1()
        else: 
            strat= DiceStrategy2()
        return Dice( strat )

This allows a program to create the Dice with something like the following.

dice = MakeDice().newDice( 
someInputOption
 )

When we add new strategies, we can also subclass the MakeDice class to include those new strategy alternatives.


 
 
  Published under the terms of the Open Publication License Design by Interspire