Chapter 11. Strategy Pattern

Enact strategy broadly, correctly and openly. Then you will come to think of things in a wide sense and, taking the void as the Way, you will see the Way as void.

Miyamoto Musashi (16th-17th Century Japanese developer of Sword Method Design Pattern [Kenjutsu] and guy who reminds us that in ActionScript 3.0 the void statement is all lowercase.)

All men can see these tactics whereby I conquer, but what none can see is the strategy out of which victory is evolved.

Sun Tzu

However beautiful the strategy, you should occasionally look at the results.

Winston Churchill (Remark made after finishing up a “Hello world” application using the Strategy pattern.)

What is the Strategy Pattern?

The Strategy design pattern is sometimes used to illustrate good OOP practices rather than offered as a design pattern that has focused applications. In one article [Agerbo and Cornils, 98], the authors set up a number of guidelines for judging design patterns. One guideline posits that a design pattern should not be an inherent object-oriented way of thinking. Then they proceed to reject the Strategy design pattern because it seems to be nothing but a collection of fundamental OOP principles!

Whether the Strategy pattern is a true design pattern or just a template for how to go about OOP programming, it has lots to offer. Because the class model looks identical to the State design pattern, we need to spend some time distinguishing the Strategy from the State pattern to show not only how each is unique, but to suggest focal applications each may have.

Key Features

When looking at the key features of the Strategy pattern, you’ll benefit from comparing them with the key features of the State pattern in the previous chapter (Chapter 10). The comparison will not only help you understand the difference between the State and Strategy patterns, it’ll also help you better understand each pattern in its own right.

The following key features are used in concert to create the Strategy design pattern, but you will find the same elements when looking at good practices for just about any object-oriented programming:

  • Define and encapsulate a family of algorithms.

  • Make encapsulated algorithms interchangeable.

  • Allow algorithms to change independently from the clients that use it.

Before going back to Chapter 10 and looking at the list of features in the State pattern, take a look at the next section, “The Strategy Model.” Then make the comparison. Right off the bat you’ll see that the key features of the State and Strategy differ, but by comparing the models as well, you’ll see the differences more clearly.

Because the features of the Strategy pattern are so fundamental to good OOP, we’re going to save a more detailed discussion of them for the section, “Key OOP Concepts used with the Strategy Pattern.” As you’ll see, all the features are discussed as applicable to the more general principles of programming with OO languages such as ActionScript 3.0.

The Strategy Model

When you look at the class diagram for the Strategy design pattern, you may be struck by a sense of déjà vu. In Figure 11-3 in Chapter 10, the labels look different, but the State design looks very similar. Fear not. They’re very different in intent and implementation. So in looking at Figure 11-1, keep the Strategy design pattern key features in mind.

Strategy design pattern class diagram
Figure 11-1. Strategy design pattern class diagram

Looking at the Context class in the Strategy class diagram, you can see that the Strategy interface is an aggregate of the Context class. Because of this aggregation, we can say that the Context class (aggregator) owns the Strategy interface (aggregate), just as in the State design pattern. (See Figure 10-3 in Chapter 10.) As you will see in the examples, the Context class aggregates the Strategy operations. Because aggregation is so fundamental to many of the design patterns and an integral part of OOP, look for its actual use in the examples. The Strategy design pattern is one of the best sources of clear examples showing aggregation at work.

Key OOP Concepts Used with the Strategy Pattern

David Geary (JavaWorld.com, 04/26/02) points out that the Strategy pattern embodies several of the OOP principles put forth by Gamma, Helm, Johnson and Vlissides. One of those principles is

Encapsulate the concept that varies

Eric and Elisabeth Freeman (Head First Design Patterns, O’Reilly 2004) make the same point in discussing the Strategy pattern, and we’re not ones to break tradition and miss an important connection between a design pattern and an OOP concept.

Encapsulating Variation

In Chapter 10, you saw that with the State design pattern, the states varied in the video player, and so the State object encapsulated the state-dependent behavior, such as playing and stopping the video. With the Strategy pattern, the Strategy object encapsulates algorithms. What both these design patterns have in common is that the elements that change are encapsulated. (As a hint of what is encapsulated, look at the name of the class. In both the State and Strategy classes, the encapsulating object is the name of the class—check the class diagrams.)

More important is why GoF suggests encapsulating those concepts that vary. Essentially, if you encapsulate those parts that vary, then, when you change the application, you’ll have fewer surprises. Encapsulated concepts allow the objects in object-oriented programming to act like objects. Rather than being sticky-glued to and dependent on other elements in a single program, encapsulated objects can act in concert with any new element added to the application, because they can vary independent of other objects.

To better see what it means to encapsulate what varies, consider a script from an example in this chapter with and without encapsulation. This involves clowns and what they do in clown venues.

Unencapsulated version (pseudocode)

class Clown {
    function juggle() {
    //juggle code
    }
    function balloonAnimals() {
    //balloon animal code
    }
}

class Bojo extends Clown {
    //Bojo unique
}

class Koka extends Clown {
    //Koka unique
}

All the algorithms for juggling and making balloon animals are placed in the Clown class. They’re passed on to the specific subclasses via inheritance. That works fine as long as all the clowns can perform all the tricks. But what happens if one of the clowns doesn’t know how to make balloon animals or juggle? Or what about a new act being added? What will happen then?

So now, let’s encapsulate the algorithms in a Strategy object:

Encapsulated algorithms (pseudocode)

interface ClownTricks {
    function someTrick(): void;
}
class Juggle implements ClownTricks {
    function someTrick(): void {
     //Juggling algorithm
    }
class BalloonAnimals implements ClownTricks {
    function someTrick(): void {
     //Making balloon animal algorithm
    }

Now, using the Clown class as an aggregator, the program can delegate requests for the different algorithms to the concrete Clown subclasses. If new tricks are introduced, just implement them from the ClownTricks interface and use them as delegates to the Clown subclasses that want to use them. (The actual application using these concepts begins with Example 11-6.)

The encapsulated algorithms aren’t going to be very useful unless some object can employ them. This is where delegation comes in, as you’ll see in the next section.

Using Delegation and Delegates

The Strategy design pattern is a perfect example of delegation. In delegation, two objects are involved in handling a single request. One of the objects receives the request and then delegates operations to a delegate. This is exactly what transpires between the context classes and the strategy classes. The context classes are made up of class and its subclasses. The context classes delegate actions to the strategy classes, made up of strategy interfaces and implementations of those interfaces. So the context classes delegate to the strategy classes.

The delegation process begins in the main context class. It includes reference variables to the strategy interfaces. Methods in the context class do the actual delegation. The concrete context classes inherit the instance variables from the context class, and use those variables to specify which behaviors they want from the concrete strategies. Figure 11-2 shows a visual depiction of delegation.

Delegation
Figure 11-2. Delegation

If an instance of a class, which has a reference to a delegate that is called, is called, the delegate invokes the called method containing the encapsulated algorithm. So instead of inheriting the operation, the object can be said to have a method that handles a call.

Minimalist Abstract State Pattern

This first example cuts to the bare bones of a Strategy design pattern to reveal the structure of the design pattern. Each part is analyzed in terms of what it does for the overall pattern. This example should help make the structure clearer.

You need to see the Strategy design pattern in a context where several different objects delegate behaviors to different delegates. (The second example provides this richer applicability.) In this minimalist example, you need to look at the points of delegation and how it all works. In Figure 11-3, following the listing, you will be able to see what looks like a web of intrigue. Not to worry; the illustration just shows the delegation work going on between the objects in the application.

Using Delegation with the Context

The Context class shown in Example 11-1 is an aggregator —it owns the Strategy object. So in every Context class where Strategy patterns are found, you will find a reference to the strategy class—in this example a variable taking the lowercase name of the Strategy interface. (Example 11-1 to Example 11-5 make up the first application.)

Example 11-1. Context.as
package
{
    class Context
    {
        protected var strategy:Strategy;

        public function doStrategy():void
        {
            strategy.think();
        }
    }
}

The Context class is pretty simple. One reason is that all the methods it needs are delegates implemented from the Strategy interface. Let’s now look at such an interface.

Adding a Strategy

In a Strategy design pattern, you’re likely to see more than one interface, depending on the different types of methods required. Example 11-2 shows a single method in the interface.

Example 11-2. Strategy.as
package
{
    interface Strategy
    {
        function think():void;
    }
}

This represents the first step in encapsulating a behavior. The subsequent concrete Strategy implementations add detail.

Details of the Strategy

Next, in Example 11-3, a concrete strategy adds a method, think(), which does something. In this case, it just sends out a trace message.

Example 11-3. ConcreteStrategy.as
package
{
    class ConcreteStrategy implements Strategy
    {
        public function think():void
        {
            trace("Great thoughts now...");
        }
    }
}

More Delegation in a Concrete Context

In a concrete context class as in Example 11-4, you can see that the work is delegated to a concrete strategy class.

Example 11-4. ConcreteContext.as
package
{
    class ConcreteContext extends Context
    {
        public function ConcreteContext()
        {
            strategy = new ConcreteStrategy();
        }
    }
}

Pulling All the Parts Together

Finally, to test the Strategy design pattern, Example 11-5’s script creates an instance of the concrete context, but types the instance as a Context data type—an example of programming to the interface and not the implementation.

Example 11-5. TestStrategy.as
package
{
    import flash.display.Sprite;

    public class TestStrategy extends Sprite
    {
        public function TestStrategy()
        {
            var thinker:Context= new ConcreteContext();
            thinker.doStrategy();
        }
    }
}

Now that you have an abstract Strategy design pattern where you can see the arrangement of the different parts, an illustration where you can see all of the connections, delegation and interactions in a single view will give you an overview of the process. Figure 11-3 shows the connections between the different parts, all in one view.

Delegation connections
Figure 11-3. Delegation connections

In Figure 11-3, note how the call,

thinker.doStrategy();

is delegated. The thinker instance in the TestStrategy class is typed as a Context type and instantiated as a ConcreteContext object. The doStrategy() method is created in the Context class, and so that would be the first place to look, as the arrow from doStrategy() in the TestStrategy class to the doStrategy() in the Context class shows.

However, in the Context class the doStrategy() method has delegated the details to the think() method, so we need to look elsewhere. In the ConcreteStrategy class, you can see the details of the think() method. Because the ConcreteContext strategy is instantiated in the thinker instance, it actually did the work, but since ConcreteContext is a subclass of Context, the delegation is structured in the Context class.

Finally, we can trace the delegation framework back to the Strategy interface. Because the origin of the think() method is in the Strategy class, we can see how it is a delegate of the Context class.

Adding More Concrete Strategies and Concrete Contexts

The purpose of the minimalist example is to expose a design pattern’s structure. However, with the Strategy pattern, a more robust example may actually do a better job of showing the pattern’s features, because it places the pattern in a more practical context in the bare bones example.

In this next example, we envision a Strategy pattern used with a clown employment agency. The agency has a number of clowns it represents. Clients call for different clown venues—circuses, birthday parties, political conventions. They want either a particular clown or particular clown acts (skits) or tricks they can perform. To create a flexible program for the clown agency, each of the skits and tricks has been created in separate algorithms. The algorithms are encapsulated, and the clown characters delegate their performance skills to the encapsulated algorithms—strategies.

The Clowns

The first step is to create a context class and concrete contexts that will use the different tricks and skits. Example 11-6 is the main context class establishing the references to the strategy methods. The trick and skit operations are delegated to the strategy classes. Then Example 11-7 and Example 11-8 create two concrete clowns. Each is assigned a different trick and skit that are delegated to concrete strategy instances. To get started, open three ActionScript files and enter the code in Example 11-6 to Example 11-8, saving each with the caption name provided. (Example 11-6 through Example 11-16 make up the entire application.)

Example 11-6. Clown.as
package
{

    class Clown
    {
        protected var tricks:Tricks;
        protected var skits:Skits;

        public function doTrick():void
        {
            tricks.trick();
        }

        public function doSkit():void
        {
            skits.skit();
        }
    }
}
Example 11-7. Koka.as
package
{
    class Koka extends Clown
    {
        public function Koka()
        {
            tricks = new Disappear();
            skits = new Chase();
        }
    }
}
Example 11-8. Bojo.as
package
{
    class Bojo extends Clown
    {
        public function Bojo()
        {
            tricks = new BaloonAnimals();
            skits = new FallDown();
        }
    }
}

Notice that each clown can perform only a single trick and a single skit. We’ll have to reconsider how to set up our strategies, or see if we can find a way to dynamically add features later. For now, though, the clowns can choose only a single trick or skit.

The Trick Interface and Implementations

All the algorithms for doing tricks are encapsulated in the concrete strategy subclasses. The Tricks interface (Example 11-9) provides the abstract method, and the subclasses add detail to the operations. A total of three trick algorithms (Example 11-10, Example 11-11, and Example 11-12) are implemented.

Example 11-9. Tricks.as
package
{
    interface Tricks
    {
        function trick():void;
    }
}
Example 11-10. BalloonAnimals.as
package
{
    //Make Balloon Animals
    public class BalloonAnimals implements Tricks
    {
        public function trick():void
        {
            trace("See! It's a doggy! No, it's not an elephant!!
")
        }
    }
}
Example 11-11. Disappear.as
package
{
    //Make Something Disappear
    public class Disappear implements Tricks
    {
        public function trick():void
        {
            trace("Now you see it! Presto! It's gone!
")
        }
    }
}
Example 11-12. Juggle.as
package
{
    //Juggle Balls
    public class Juggle implements Tricks
    {

        public function trick():void
        {
            trace("Look at me juggle! Whoops!
")
        }
    }
}

By using a general concept like “tricks,” we’re limiting the range of what the clowns can do. The way the Strategy pattern’s set up, each clown can have only a single trick. We could implement an “All Tricks” class, but that’s too broad at the opposite extreme in that it would require a clown to do everything, even if he could only do two of the tricks.

The Skits Interface and Implementations

The Skits interface (Example 11-13) and its implemented classes (Example 11-14 and Example 11-15) are set up exactly the same as the Tricks class and its implementations. It also has the same limitations.

Example 11-13. Skits.as
package
{
    //Skits Interface
    interface Skits
    {
        function skit():void;
    }
}
Example 11-14. FallDown.as
package
{
    //Falls down a ladder
    public class FallDown implements Skits
    {
        public function skit():void
        {
            trace("I'm climbing up this ladder!")
            trace("Where's my banana peel?")
            trace("Whoaaaa! I'm falling!!")
            trace("Thanks for finding my banana peel!
")
        }
    }
}
Example 11-15. Chase.as
package
{
    //Clowns chase each other
    public class Chase implements Skits
    {
        public function skit():void
        {
            trace("Here I come! I'm going to get you!")
            trace("Nah! Nah! Can't catch me!
")
        }
    }
}

Even though we’ve discussed some possible shortcomings in this design, it still shows how the strategies are built in the implementations of the Tricks and Skits classes. The final step is to see how to launch the program.

Here Come the Clowns!

For this example, the ClownCollege class (Example 11-16) instantiates two different clowns. Both clowns are typed as Clown types following the dictum of programming to the interface instead of the implementations; however, each instantiates through a Clown subclass. Each is given a trick and skit, and it’s ready to run. Save Example 11-16 using the caption name as the filename.

Example 11-16. ClownCollege.as
package
{
    import flash.display.Sprite;

    public class ClownCollege extends Sprite
    {
        public function ClownCollege()
        {
            var joker:Clown=new Koka();
            joker.doTrick();
            joker.doSkit();

            var gagGrrrl:Clown=new Bojo();
            gagGrrrl.doTrick();
            gagGrrrl.doSkit();
        }
    }
}

Once you’re finished saving Example 11-6 Example 11-16, open a new Flash document and save it as ClownCollege.fla. In the Document class window, type in ClownCollege and test the application. You should see the following in the Output window:

* =>Koka<= *
Now you see it! Presto! It's gone!

Here I come! I'm going to get you!
Nah! Nah! Can't catch me!

* =>Bojo<= *
See! It's a doggy! No, it's not that!!

I'm climbing up this ladder!
Where's my banana peel?
Whoaaaa! I'm falling!!
Thanks for finding my banana peel!

The output represents the algorithms set up in the strategy classes. All are delegated from the Clown context class and implemented in the concrete clown classes.

Additional Clown Functionality

As noted in the initial clown application using the Strategy design pattern, the structure allowed very little flexibility and no way to dynamically change a concrete context to accept another strategy. So what happens if we add another clown who can do more than one trick but not all the tricks?

Fortunately, we have a simple solution. By adding setters to the context class, Clown, it will be possible to dynamically add strategies to the concrete context classes—the clowns. Example 11-17 revises the original Clown class by adding the setters (shown in bold).

Example 11-17. Clown.as
package
{
    class Clown
    {
        protected var tricks:Tricks;
        protected var skits:Skits;

        public function doTrick():void
        {
            tricks.trick();
        }

        public function doSkit():void
        {
            skits.skit();
        }

        public function setTrick(addTrick:Tricks):void
        {
            tricks=addTrick;
        }

        public function setSkit(addSkit:Tricks):void
        {
            tricks=addSkit;
        }
    }
}

Adding a new clown

The application is already structured to easily accept a new concrete context class. So all we have to do is to add a subclass, and include the concrete strategies to be delegated. Example 11-18 does just that. Save the file using the caption as the filename.

Example 11-18. Bubbles.as
package
{
    class Bubbles extends Clown
    {
        trace("* =>Bubbles<= *");
        public function Bubbles()
        {
            tricks=new Juggle();
            skits = new FallDown();
        }
    }
}

Adding a new trick

Just as adding a new concrete context class in a Strategy pattern is easy, so too is adding a new concrete strategy. Save Example 11-19 using the caption name as the filename. The new script is indistinguishable from any of the other concrete strategies except for the name of the class and the content in the trick method.

Example 11-19. BubblePants.as
package
{
    class BubblePants implements Tricks
    {
        public function trick():void
        {
            trace("Woo woo woo! Bubbles are coming out of my pants!
");
        }
    }
}

Revising clown college

To see both how easy it is to add new elements to the application and to dynamically add new strategies to a concrete context class, the ClownCollege class is revised (bold text). First, the new concrete clown class, Bubbles, is instantiated and the two existing delegation methods launch the operations to display the skit and tricks. Then, the clown instance uses the new setTrick() method to add the new trick to its repertoire. Finally, the doTrick() method is launched a second time to display the new trick. Save Example 11-20 using the caption name for the filename.

Example 11-20. ClownCollege.as
package
{
    import flash.display.Sprite;

    public class ClownCollege extends Sprite
    {
        public function ClownCollege()
        {
            var joker:Clown=new Koka();
            joker.doTrick();
            joker.doSkit();

            var gagGrrrl:Clown=new Bojo();
            gagGrrrl.doTrick();
            gagGrrrl.doSkit();

            var gurgle:Clown=new Bubbles();
            gurgle.doSkit();
            gurgle.doTrick();
            gurgle.setTrick(new BubblePants());
            gurgle.doTrick();
        }
    }
}

Nothing will change for the first two clowns. However, the new clown instance should do something different, but what? In looking at the code, the initial skit and trick involve falling off the ladder and juggling. That is unchanged, and so the new clown’s initial displays should be no different than anything you’ve seen so far.

However, after the doTrick() method is invoked, the instance, gurgle, is given a new trick. Then, when the doTrick() launches a second time for the gurgle instance, a different trick display is shown. The bold output in the following output shows the results of the added materials:

* =>Koka<= *
Now you see it! Presto! It's gone!

Here I come! I'm going to get you!
Nah! Nah! Can't catch me!

* =>Bojo<= *
See! It's a doggy! No, it's not that!!

I'm climbing up this ladder!
Where's my banana peel?
Whoaaaa! I'm falling!!
Thanks for finding my banana peel!

* =>Bubbles<= *
I'm climbing up this ladder!
Where's my banana peel?
Whoaaaa! I'm falling!!
Thanks for finding my banana peel!

Look at me juggle! Whoops!

Woo woo woo! Bubbles are coming out of my pants!

Simply by adding setters to the context class, the application has been given a dynamic way to change which strategy the concrete context class delegates.

Tricks and Skits Reorganization: Clown Planning

Before going on to the next example, we need to consider how the strategy subclasses were organized for the task at hand. Keep in mind that no matter how sophisticated or cool your application is, good planning will make it better.

The more components in your system, the more granularity your application is said to have. Granularity affords more flexibility, and that’s a good thing. However, greater granularity also leads to a greater number of classes, often adding housekeeping headaches.

To optimize your application, especially one using the Strategy design pattern where you can have a large number of both concrete contexts and strategies, you need to plan. Taking a closer look at the clown example, we can see where our planning was incomplete.

Starting with the trick strategies, the structure allows for only a single trick per clown. So, we might want to reorganize the strategies from what we have to something like that shown in Table 11-1:

Table 11-1. Strategies

Juggle Tricks

Balloon Tricks

Magic Tricks

Balls

Animal shapes

Card tricks

Rings

Human shapes

Escape tricks

Flaming Torches

Politicians

Disappearing children

Any objects

Any shapes

Any magic

Now, instead of a single trick interface, it has three. Additionally, a general algorithm covering any tricks in the category is covered as well (any object, any shapes and any magic). To cover contingencies, the context class retains its ability to dynamically add a strategy to an instance of a concrete context object. The skit strategies can be reorganized as well, and new strategies can be added as the application grows and changes.

Working with String Strategies

The best aspect of the Strategy design pattern is its ability to allow change and adaptation. The design pattern is a virtual poster child for composition, where change and flexibility are handled with aplomb. Also, it’s a great tool for working with algorithms large and small.

With any application, you’re likely to encounter recurring algorithms. For this application, we decided to include some little algorithms that we use with different applications that have to be rebuilt every time we create an application that uses them. However, we don’t want to make them into classes where the main objective is to inherit the functionality. Rather, we want to use them in composition so that we have more flexibility, and use their functionality only where we need them.

For our example, we’ve taken two simple types of string and sorting issues that seem to come up in a lot of different applications. First, we built some simple strategies for checking strings. The first strategy is designed to see if the @ symbol is placed in an e-mail address, and if it’s not in the first or last position in a string. The second strategy is a password-checking algorithm that knocks all entries to lowercase, and then sees if they match the correct password.

Our second strategy interface is for demonstrating different sorting possibilities using the Array class and sort() method. By changing the sort parameters, you will be able to see which constants to use with different types of sorts and see the outcomes. Figure 11-4 shows all of the connections between the classes.

Applying the Strategy pattern
Figure 11-4. Applying the Strategy pattern

The code in Example 11-21 through11-33 makes up the ActionScript files that constitute this next application. The application also requires a single Flash document file with the Document class name TestStringStrategy.

Contexts for String Strategies

You can think of the context classes as the clients for the strategies. They use the different strategies. Often, you will hear that a client has-a behavior. Whenever a client class delegates to a delegate class, such as the strategy classes in Figure 11-5, it has-a behavior generated by the strategy class. In this application, the context classes represent the client classes for the strategy classes.

Like all context classes, the StringChecker class in Example 11-21 sets a reference to the two strategy interfaces, StringWork and SortWork. In addition, it has functions, workStrings() and workSorts() that constitute the methods used to delegate work to the strategies.

Example 11-21. StringChecker.as
package
{
    //Context class
    class StringChecker
    {
        protected var stringWork:StringWork;
        protected var sortWork:SortWork;
        public function StringChecker():void
        {
        }

        public function workStrings(s:String):String
        {
            return stringWork.stringer(s);
        }
        public function workSorts(a:Array):Array
        {
            return sortWork.sorter(a);
        }
        public function setString(sw:StringWork):void
        {
            stringWork=sw;
        }
        public function setSort(sow:SortWork):void
        {
            sortWork=sow;
        }
    }
}

In addition to having reference properties and methods, the context class includes setter operations, setString() and setSort(), to dynamically set strategies to instances of the context classes.

Next, in Example 11-22 and Example 11-23, two concrete context classes extend the primary context class. Note that they inherit the references to the strategies, stringWork() and sortWork(). These references are then used to specify exactly which of the concrete strategies each will be using.

Example 11-22. Checker.as
package
{
    //Concrete Context
    class Checker extends StringChecker
    {
        public function Checker()
        {
            stringWork = new EmailCheck();
            sortWork = new SimpleSort();
        }
    }
}
Example 11-23. Passwork.as
package
{
    //Concrete Context
    class Passwork extends StringChecker
    {
        public function Passwork()
        {
            stringWork = new PasswordVerify();
            sortWork = new SortAll();
        }
    }
}

Keep in mind that both of these classes inherit the methods of the main context class, StringChecker, and when you test instances of these classes, you will be using the workStrings() and workSorts() methods.

String Strategies

The classes associated with the strategies are quite simple by comparison to the client classes in the Strategy design pattern. The interfaces specify the methods, and then each of the concrete strategy classes implements them, supplying the details in the form of algorithms. However, in this example (Example 11-24 and Example 11-25), both methods in the interfaces require parameters and expect returns.

Example 11-24. StringWork.as
package
{
    //Strategy
    interface StringWork
    {
        function stringer(s:String):String;
    }
}
Example 11-25. SortWork.as
package
{
    //Strategy
    interface SortWork
    {
        function sorter(a:Array):Array;
    }
}

Even with the added parameter and the return datatype, the interfaces are very simple. They get more interesting in their implementation.

Checking strategies

The first two concrete strategies (Example 11-26 and Example 11-27) create algorithms using String methods. Each adds an algorithm requiring work with strings. Neither is especially sophisticated, but both are handy for certain applications that require checking strings.

Example 11-26. EmailCheck.as
package
{
    //Concrete Strategy
    class EmailCheck implements StringWork
    {
        public function stringer(s:String):String
        {
            var atPlace:int=s.indexOf("@");
            if (atPlace != -1 && atPlace !=0 && atPlace != (s.length-1))
            {
                return "Email address verified";
            } else
            {
                return "Email does not verify.
Missing or
                    misplaced @ sign";
            }
        }
    }
}
Example 11-27. PasswordVerify.as
package
{
    //Concrete Strategy
    class PasswordVerify implements StringWork
    {
        public function stringer(s:String):String
        {
            var pwv:String=s;
            pwv=pwv.toLocaleLowerCase();
            if (pwv == "sandlight")
            {
                return "Welcome to Sandlight";
            } else
            {
                return "Your password is incorrect.
                     Please enter again.";
            }
        }
    }
}

Instead of using simple string comparisons, you could use regular expressions. Far more sophisticated substring searches and comparisons are possible using RegExp data types and algorithms.

Sort strategies

The algorithms used for the sorting strategies (Example 11-28, Example 11-29, and Example 11-30) are much simpler than the simple string algorithms used in the two StringWork implantations. All three of the algorithms use a single Array method—sort(). However, by changing the parameters, you actually have three different kinds of sorts.

Example 11-28. SimpleSort.as
package
{
    //Concrete Strategy
    class SimpleSort implements SortWork
    {
        public function sorter(a:Array):Array
        {
            a.sort();
            return a;


        }
    }
}
Example 11-29. SortAll.as
package
{
    //Concrete Strategy
    class SortAll implements SortWork
    {
        public function sorter(a:Array):Array
        {
            a.sort(Array.CASEINSENSITIVE);
            return a;


        }
    }
}
Example 11-30. SortBack.as
package
{
    //Concrete Strategy
    class SortBack implements SortWork
    {
        public function sorter(a:Array):Array
        {
            a.sort(Array.CASEINSENSITIVE | Array.DESCENDING);
            return a;

        }
    }
}

As you can see, you can do quite a bit with a single method. To add more options to sorting, add some implementations using the Array.sortOn() method and multi-dimension arrays.

Support Classes

Two support classes help keep the main test application clear, and make good use of the whole idea of a class structural arrangement. The ShowText class (Example 11-31) organizes a dynamic text field, and TextShow (Example 11-32) provides the format.

Example 11-31. ShowText.as
package
{
    import flash.text.TextField;
    import flash.text.TextFieldType;

    class ShowText extends TextField
    {
        public function ShowText():void
        {
            this.type=TextFieldType.DYNAMIC;
            this.multiline=true;
            this.wordWrap=true;
            this.border=true;
            this.borderColor=0xcccccc;
        }
    }
}
Example 11-32. TextShow.as
package
{
    import flash.text.TextFormat;

    class TextShow extends TextFormat
    {
        function TextShow():void
        {
            this.font="Verdana";
            this.color=0xcc0000;
            this.size=11;
            this.leading=3;
            this.leftMargin=11;
            this.rightMargin=6;
        }
    }
}

Feel free to change any of the support classes’ features to meet your own tastes.

String Strategy Test

The test of the applied Strategy design pattern uses two objects (Example 11-33). The first object instantiates the concrete context class, Checker, and the second uses Passwork.

Example 11-33. TestStringStrategy.as
package
{
    import flash.display.Sprite;

    public class TestStringStrategy extends Sprite
    {
        private var showText:ShowText;
        private var showText2:ShowText;
        private var textShow:TextShow;

        public function TestStringStrategy():void
        {
            doText();

            //Stringer -- Uses Checker concrete context
            var stringer:StringChecker= new Checker();
            showText.text="Stringer

"+stringer.workStrings
                ("[email protected]");
            var friends:Array=new Array("John","Carl","aYo","Delia");
            showText.appendText("
"+stringer.workSorts(friends));
            stringer.setString(new PasswordVerify());
            showText.appendText("
"+stringer.workStrings("Sandlight"));

            //Passer -- Uses Passwork concrete context
            var passer:StringChecker= new Passwork();
            showText2.text="Passer

"+passer.workStrings("Rumple");
            showText2.appendText("
"+passer.workSorts(friends));
            passer.setSort(new SortBack());
            showText2.appendText("
"+passer.workSorts(friends));
            passer.setString(new EmailCheck);
            showText2.appendText
                ("
"+passer.workStrings("@passGuy.com"));
        }
        private function doText():void
        {
            showText=new ShowText();
            showText2=new ShowText();
            textShow=new TextShow();
            showText.defaultTextFormat = textShow;
            showText2.defaultTextFormat = textShow;
            addChild(showText),addChild(showText2);
            showText.x=50, showText2.x=270;
            showText.y=100, showText2.y=100;
            showText.width=200,showText2.width=200;
            showText.height=150,showText2.height=150;
        }
    }
}

Keep in mind that the call to the concrete class methods, workStrings and workSorts, are calls that are delegated to the strategy classes. After opening a new Flash document file and placing TextStringStrate in the Document class window, test the application. Figure 11-5 shows what you can expect to see.

Output from TestStringStrategy
Figure 11-5. Output from TestStringStrategy

Also, note that after the dynamic changes were reset using setString() and setSort(), the output changes. Even though adding the setter is optional for a true Strategy design pattern, setters add a whole other level of flexibility and should not be overlooked.

Summary

After examining some applications using the Strategy design pattern, you can see the scope of possibilities for it as a design pattern. It’s certainly a showcase for good OOP practices. Perhaps most obvious is how it uses composition instead of inheritance to develop and use the algorithms. However, it still uses the basic OOP feature of inheritance in its concrete context classes.

Furthermore, you can clearly see polymorphism in the variety of ways the different strategies are implemented. Each of the interfaces demonstrates abstraction along with the main context class. All the strategies encapsulate an algorithm, and each serves as a delegate to another class. So there you have it, the four basic elements of good OOP:

  • Inheritance

  • Polymorphism

  • Abstraction

  • Encapsulation

As we saw in this chapter in the section on the key OOP practices, the Strategy design pattern demonstrates several other good OOP practices in addition to the basics.

So, while we believe that the Strategy design pattern certainly demonstrates fundamental OOP practices, it’s still a distinct design pattern. Ironically, this fact is most obvious when you compare it to the State design pattern—the pattern it’s often accused of duplicating. The State pattern encapsulates states and state contexts, and the Strategy pattern encapsulates algorithms. Both have different intents but these intentions are masked by the almost identical class diagrams. Only when you understand and use each one do the differences manifest themselves. As these differences become clear, you can see the actual use of the State and Strategy patterns as solutions to different kinds of problems. Since the definition of design patterns is that of design solution to recurring problems, we can see that the Strategy design pattern clearly meets this fundamental criterion.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.67.251