10.3 Some Examples

Some examples

As I mentioned before, this book is not meant to be a pattern catalog. The level of detail I reveal below isn't quite as high as in a pattern catalog, and the examples aren't as well organized. I also give a couple of Visual FoxPro implementation examples that you wouldn't find in a catalog. These should help you to see how other patterns found in catalogs can be used in Visual FoxPro.

Most examples I use here can be found in Erich Gamma's book and other catalogs in similar form.

Adapter pattern

An adapter is used to convert an object's interface into another interface that is expected by other objects. This is useful when you want to reuse an incompatible object in your system.

Consider the movie theater example. We used a screen class with methods that we needed to maintain a screen. Later in our example, we took over another theater that had a well-organized software system, and we wanted to incorporate the screen manager classes of that system. As expected, those screen manager classes weren't compatible with ours. However, we could still use those classes if we wrote an adapter. Figure 1 shows the general idea.

Figure 1. The basic idea of an adapter.

Note that an adapter doesn't house a lot of functionality. It simply translates the messages and some of the parameters that travel along. The following code shows an example of our screen class, a foreign screen class and an adapter.

DEFINE CLASS OurScreen AS Custom

PROTECTED lLightsOn

lLightsOn = .F.

FUNCTION ToggleLights

* Code to toggle the lights

ENDFUNC

FUNCTION StartMovie( lcMovieTitle )

* Code that turns on the projector

* and starts the movie specified in the parameter

ENDFUNC

FUNCTION StopMovie

* Code that turns off the projector

ENDFUNC

ENDDEFINE

DEFINE CLASS OtherScreen AS Custom

PROTECTED cCurrentMovie

cCurrentMovie = ""

FUNCTION TurnOnLights

* Code that turns on the lights

ENDFUNC

FUNCTION TurnOffLights

* Code that turns off the lights

ENDFUNC

FUNCTION SpecifyMovie( lcMovieTitle )

* Specifies the current movie in cCurrentMovie

ENDFUNC

FUNCTION StartProjector

* Starts the current movie as specified in cCurrentMovie

ENDFUNC

FUNCTION StopProjector

* Stops the projector (and the movie)

ENDFUNC

ENDDEFINE

DEFINE CLASS ScreenAdapter AS OurScreen

PROTECTED oForeignObject

FUNCTION Init

THIS.oForeignObject = CreateObject("OtherScreen")

ENDFUNC

FUNCTION ToggleLights

IF THIS.lLightsOn

THIS.lLightsOn = .F.

RETURN THIS.oForeignObject.TurnOffLights()

ELSE

THIS.lLightsOn = .T.

RETURN THIS.oForeignObject.TurnOnLights()

ENDIF

ENDFUNC

FUNCTION StartMovie( lcMovieTitle )

IF THIS.oForeignObject.SpecifyMovie( lcMovieTitle )

RETURN THIS.oForeignObject.StartProjector()
ELSE
RETURN .F.
ENDIF

ENDFUNC

FUNCTION StopMovie

RETURN THIS.oForeignObject.StopProjector()

ENDFUNC

ENDDEFINE

As you can see, the real classes (OurScreen and OtherScreen) lack some detail to keep the example simple. The adapter class (ScreenAdapter) is fully implemented and functional.

The adapter class is a subclass of my internal class, but this may or may not be the case in all situations. I like to do it this way because it ensures the interface is compatible and always up to date. However, you could also create a separate class. In many scenarios, the adapter wouldn't be a direct subclass of the real class, but those two classes would share a common abstract parent class. Figure 2 shows examples of different hierarchies an adapter might use.

As you can see, the adapter automatically creates an instance of the class that is supposed to be used. It keeps a reference to this class internally (oForeignObject in my example). This is important, because it ensures that I can instantiate the adapter in the same way as other objects, which is essential if I want to use the adapted class as if it were an internal one.

Figure 2. Possible class hierarchies.

Some of the calls are straightforward. The StopMovie() method, for instance, simply transfers the call to StopProjector(). The incompatibility was minimal in this case, caused by a different method name. Other things might be a little harder, such as the ToggleLights() method. Our class uses the approach of toggling lights on and off every time we use the method, while the foreign screen class has specific methods to turn the lights on and off. For this reason, the current status must be evaluated before the call is made to the two different methods. We also need to keep track of the current light status.

The StartMovie() message needs to be translated into multiple messages because we simply pass the movie name as a parameter in our class, while in the foreign class one method is used to set the movie and another one to start it. Of course, the adapter also needs to check whether the calls were successful.

Note that the adapter also takes care of the return value. Often you'll be able to simply return the value sent by the adapted class, but sometimes you'll have to translate this value or take care of multiple values at a time, as in the StartMovie() method that translates to two separate methods in the foreign class.

Like many patterns, this pattern is also known by other names (such as "wrapper" and "translator"). This somewhat defeats the purpose, but it isn't as big a deal as it might seem.

Observer pattern

An observer is an object that observes some objects and notifies others if changes occur. Observer objects are typically used to keep objects synchronous.

Awhile back, when I created the PowerBrowser (a Class Browser add-in), one of the things I did was to replace the Class Browser's Redefine button. The original button was enabled and disabled by the browser depending on the selected class. Obviously, the browser couldn't have known about my new button, so the new button always stayed enabled. This caused serious trouble if the user clicked on the button when the wrong class was selected. The solution to the problem was to use an object that observed the original button (which was still there but invisible) and if the enabled status changed did the same for my button. I implemented this observer as a timer that checks the status twice a second.

There are two different kinds of observers: active observers (see Figure 3) and passive observers (see Figure 4).

Figure 3. Active observer diagram.

Active observers (or "pop observers") actively monitor the status of other objects. In Visual FoxPro, they are usually implemented as timers. The major advantage of active observers is that other objects don't have to know they are observed (for this reason, active observers are sometimes referred to as "voyeurs"). My Class Browser example is a typical use of active observers. Typically they are used to enhance a system that wasn't designed to be easily enhanced, and to overcome design shortcomings.

The disadvantage of active observers is that they need resources and can be costly in terms of performance. Fortunately, observing tasks is usually very simple, so this disadvantage doesn't hurt too much.

Figure 4. Passive observer diagram.

Passive observers have to be notified by other objects about changes. These kinds of observers are usually used in large scenarios where a lot of objects must be kept synchronous. In this case, it is hard for every object to know about every other object that has to be updated. Let's assume we have 10 such objects. If every object only needs to know about one method of each other object, that's a grand total of 90 messages that can be sent. If we add one more object to this scenario, every other object has to be notified of the change and modified so it can handle this new object. When we add an 11th object, 10 messages must be added to the existing objects. The larger the scenario gets, the more objects have to be changed and the larger (and more confusing) the number of messages.

The alternative is to use an observer object. Every time an object changes its status, it notifies the observer. The observer knows about all the other objects. If we add another object, we only have to add one method call to the observer, rather than 10 as in the previous scenario.

A passive observer is a specialized version of a mediator (yet another pattern). Mediators are used to handle messaging between a large number of objects. This is essentially very similar to the behavior of a push observer. The difference is that mediators can handle all kinds of messages, while passive observers are specifically used to handle an object's state.

Strategy pattern

The strategy pattern defines a family of algorithms, encapsulates each one of them in a separate object, and makes them interchangeable. Other objects that talk to this construction would not be able to see the difference in the behavioral object that is invoked.

Earlier in this chapter I used this pattern as an example that calculates taxes in different parts of the country. In fact, I used the strategy pattern in a lot of examples throughout the book without mentioning it.

Figure 5 documents the general idea behind the strategy pattern.

Figure 5. Strategy pattern overview.

In short, there is an object that needs some kind of behavior (such as an invoice object that needs the ability to calculate taxes), some algorithm that decides what kind of behavioral object is invoked, and finally, a set of behavioral objects, one of which will be utilized.

Implementations of the strategy pattern vary greatly. In small scenarios, the object that requires some behavior actually makes the decision of what object to invoke (see Figure 6). However, this architecture takes away many advantages of this pattern, mainly because this object must be changed in order to enhance the number of invoked algorithms. I generally do not recommend this approach (even though I sometimes use it myself in very small scenarios).

Figure 6. The caller decides what strategy will be implemented.

Another version of the strategy pattern is to use three separate objects (see Figure 7). In this case, the caller talks to a decision object that decides what behavioral object to invoke. The decision object typically returns an object reference. All further messaging does not involve the decision object.

Figure 7. A separate object decides what strategy to use and returns a reference
to the strategy to the client class.

I prefer this architecture because it offers the most advantages. Additional behavioral objects can be added easily because changes have to be made only in the decision object. This is much easier than the earlier approach in which we also needed to change only one object, but this object was much larger and therefore more complex. We can also exchange the decision object for another one. This is very important if we want to take our tax-calculation example to an international level. The decision object would be responsible for deciding what calculation object to involve. In other words, taxes we have to charge a U.S. customer are very different than taxes we have to charge if we ship to Germany. However, this also depends on our current location. If we ship from Austria to Germany, the situation might be very different from shipping from the U.S. to Germany. For this reason, we need to be able to swap out the decision object as well (possibly at compile time).

The architecture documented in Figure 7 can also be seen as a "double-strategy" pattern (even though this is not an official term).

A third possible version is to make all the behavioral objects members of the decision object. In this case the use of the strategy pattern is not obvious, because to the outside it looks like one object is used. Figure 8 demonstrates this concept.

Figure 8. The decision object is a container that owns all possible strategies. To the client object, the construction looks like a single behavioral object.

This version is harder to maintain than the three-object architecture, but it's easier to use from the client's point of view because all messaging goes through the decision object. This means that the caller doesn't retrieve a reference to the actual behavioral object and therefore needs no knowledge about it. You can use this approach quite easily if you want to add strategies to an existing component.

The strategy pattern is also known as the "policy" pattern.

 

Template method pattern

A template method pattern is a very small-scale pattern. I include it here so you can see that patterns occur on all scales, beginning at the method level and going all the way up to the framework.

The template method pattern describes a way of organizing a method to overcome problems that occur due to the static nature of inheritance. The main idea is to specify a skeleton of an algorithm that is split into several steps (methods) rather than keeping the code in only one method. Consider the following code:

DEFINE CLASS Class1 AS Custom

FUNCTION Execute

MessageBox("Step 1")

MessageBox("Step 2")

MessageBox("Step 3")

ENDFUNC

ENDDEFINE

DEFINE CLASS Class2a AS Class1

FUNCTION Execute

MessageBox("Before Step 1")

DoDefault()

ENDFUNC

ENDDEFINE

DEFINE CLASS Class2b AS Class1

FUNCTION Execute

DoDefault()

MessageBox("After Step 1")

ENDFUNC

ENDDEFINE

In this example, a simple method (Execute) displays three message boxes. In the two subclasses, we display a message box before invoking the original method (class2a) and after the default code is fired (class2b). So far so good, but what if we want to add another message box between the first and the second one? We couldn't do that unless we overwrite the original method. This might not seem like a big deal in this example, but keep in mind that in a real-life scenario we wouldn't have three message boxes we'd have a large number of lines of code.

The following code behaves exactly the same, but it gives us more flexibility in the subclasses:

DEFINE CLASS Class1 AS Custom

FUNCTION Execute

THIS.Step1

THIS.Step2

THIS.Step3

ENDFUNC

FUNCTION Step1

MessageBox("Step 1")

ENDFUNC

FUNCTION Step2

MessageBox("Step 2")

ENDFUNC

FUNCTION Step3

MessageBox("Step 3")

ENDFUNC

ENDDEFINE

DEFINE CLASS Class2 AS Class1

FUNCTION Step1

DoDefault()

MessageBox("After Step 1")

ENDFUNC

ENDDEFINE

As you can see, we can now make more detailed subclass adjustments because we can add code in the middle of a process.

In this example, the number of lines of code grew a lot, which gives us an incorrect perspective because every single method would have much more code. So all we really did was add the three lines that call the steps.

Another advantage of using this code is that we can reorganize how the steps are called:

DEFINE CLASS Class1 AS Custom

FUNCTION Execute

THIS.Step1

THIS.Step2

THIS.Step3

ENDFUNC

FUNCTION Step1

MessageBox("Step 1")

ENDFUNC

FUNCTION Step2

MessageBox("Step 2")

ENDFUNC

FUNCTION Step3

MessageBox("Step 3")

ENDFUNC

ENDDEFINE

DEFINE CLASS Class2 AS Class1

FUNCTION Execute

THIS.Step1

THIS.Step3 && We call this one first...

THIS.Step2

ENDFUNC

ENDDEFINE

In this example, we had to overwrite (rewrite) the Execute method in the subclass. However, this was not a big deal because template methods don't grow very large (no more than 10 or 15 lines depending on the number of steps). Also, we didn't rewrite any real behavior.



Advanced Object Oriented Programming with Visual FoxPro 6. 0
Advanced Object Oriented Programming with Visual FoxPro 6.0
ISBN: 0965509389
EAN: 2147483647
Year: 1998
Pages: 113
Authors: Markus Egger

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net