Building from Scratch

One of the places where patterns are most often applied is in developing a new system. In designing a system architecture from the ground up, I take the following approach:

  • Understand your requirements

    Know what you don't know

    Know what's likely to change

  • Create hinge points for the unknowns and entities that we suspect are likely to change

  • Utilize supporting patterns to ensure no loose ends

  • Make a sanity check

  • Implement a little

  • Restart the whole process at a lower level, as necessary

Now, let's create a fairly simple project to illustrate this approach:

Understanding Your Requirements

I know the following is obvious, but I am going to restate it here because it is so easily overlooked: Before you design a system or write any code or anything else, make sure you understand where you are going. Most problems are not coding or architectural problems but rather are requirements issues. Only by insuring that we understand not only the current but future requirements and direction, can we hope to design a successful product.

Blackjack is a simple card game in which the player's goal is to get a better hand than the dealer without “busting” or going over 21 points. Aces count as 1 (in a soft hand) or 11; all face cards count as 10. The dealer must hit (draw another card) on all hands containing fewer than 17 points.

We must create a text-based (console) computer simulation to allow us to try out different strategies prior to going to a real casino and losing real money. The purpose of this project is simply to simulate game play so betting support is not required. We must be able to automate a player strategy fully or play manually when developing this strategy. The code will be written in C++.


Know What You Don't Know

Look at those parts of the requirements that seem highly subject to change or are just not decided on yet. Those things that are decided on later in the requirements portion (usually the TBD items) are the most likely to change as you roll your product out to the customers. Each unknown can become a hinge point on which to allow the system to change. The first part of this activity is to abstract the unknown in such a way that we can define an interface that allows us to represent any of the possible solutions.

The following things are unknown at this phase so we will apply the first pattern discussed in this book, Pattern Name: High Road Development, in Chapter 1. Let's assume simple implementations for now, but in the next step we can create the ability to add more advanced capabilities later.

Blackjack has several variations; the standard approach requires the dealer to “stand” on soft 17. Because of a house advantage, several casinos have added a “hit” on the soft 17 option for the dealer. In addition, some casinos have also introduced a “double exposure” option, in which both cards are dealt to the dealer faceup (in exchange for reduced blackjack payouts, and the elimination of “pushes” or ties).

In addition, the strategy the player must use is unknown at this point, but we must be able to create new automated players by adding the new playing strategy and a single maintenance point.


Know What's Likely to Change

Several parts of your requirements may screen future release issues or are things that you are going to need to redo in the future. My way of handling these items is to recognize that if I choose a certain approach, it will probably be wrong. Instead, what I will do in this case is to abstract these areas of change again in such a way that we can define an interface that allows us to represent any of the possible solutions.

We know that the dealer's and player's strategies are likely to change because they are unknown. In addition, I almost always introduce IO as an unknown because the user interface is one of the most likely aspects in any system to change.


Create Hinge Points for the Unknowns and Entities that We Suspect Are Likely to Change

Every pattern provides some level of system variance. I believe this is the highest value, second to vocabulary, introduced in utilizing patterns. Once we have identified the places in which we have unknowns or areas likely to change and have some idea of what a proper abstraction or concept necessary to represent this variance is, we can simply utilize patterns to express them. These areas now become hinge points, allowing us to connect new or different functionality.

In his book, Multi-Paradigm Design for C++ [Cop, 99], Jim Coplien does a fantastic job of discussing commonality and variance and how they might be applied in developing a system. By applying analysis techniques (of which patterns play a part), we can more easily analyze a system or a domain, and we can more easily identify these hinge points.

For the core framework (see Figure 7.1) we will first introduce the concepts of Deck and Cards. We will support multiple decks as well as single decks.

Figure 7.1. Core framework



A Dealer class will be created to handle the implementation of the basic rules of the system. The interaction diagram is shown in Figure 7.2.

Figure 7.2. Sequence diagram for play



We will introduce a strategy pattern for the variation in the different types of player hands called BlackJackHand: Standard (PlayerHand, DealerHand, and SimulatedPlayerHand. A Strategy pattern [Gam, 95] allows us to vary the implementation of the specific algorithms used. Because much of the behavior in our strategies is the same right now, we will introduce a middle class called StandardBlackJackHand. As mentioned earlier, it is usually a good idea to introduce a common middle class to allow code to be reused. These middle classes can usually be refactored (or reorganized) into an existing design as needed, because there should never be any reference to them other than through their children. (See Figure 7.3 for the hand hierarchy.)

Figure 7.3. The hand hierarchy



We will also decouple ourselves from the display rules by creating a Displayer interface (see Figure 7.4) that handles all display rules. For now we will implement only a Standard Displayer that handles the console text-based rules.

Figure 7.4. Displayer hierarchy



Utilize Supporting Patterns to Ensure No Loose Ends

Once we have the patterns in place to provide us this system variance, we need to connect it to the overall architecture. Usually this involves adding structural or creational patterns to the fledgling architecture. It is essential to ensure that when new or different functionality is to be added to the system, only one point in the existing system is ever changed. This is the point at which we decide to use the new functionality; usually this involves making sure that any creational patterns to decouple the implementation classes are in place. This is not to imply that patterns can simply be “plugged in”; instead the problem (and the context) must be well understood to ensure the proper application.

Let's look at the high level of our system at this point (see Figure 7.5). I might consider adding a creation pattern such as a Factory Method pattern [Gam, 95] to isolate the creation of the different types of blackjack hands. However, an examination of the architecture at this point shows only a single point that references the concrete classes, and I can add the creational support later without any advantage. Because there is no advantage to adding it now and we can always add it later, let's leave it as is for now.

Figure 7.5. A high-level diagram of our system



Make a Sanity Check

Now, examine the architecture and ensure that we have sufficient flexibility. While the architecture may not be easy, especially for the more ambitious designer, one must also make sure it is not overly complex for the designated implementers. It is extremely painful to design a beautiful and elegant framework only to have to go back and revisit it because it cannot be implemented by the coders. This occurs when we fail to consider the audience of the patterns and we fail to understand design complexity vs. code complexity. A simpler design often leads to more (and often more complex) code—but not always. However, many developers can understand complex code more than they can think in terms of abstraction, so training is essential to get over this curve. This is especially true with novice developers who have little experience in enterprise systems or who are used to working in an environment that is more code-feedback-intensive, such as Web design.

The architecture, as it stands now, is fairly straightforward, and C++, the language in which we implement this, easily supports all of the design mechanisms we are using. Therefore we can freely continue into implementation.


Implement a Little

Now comes the easy part—coding. The choice of programming languages is an important one, but it is more often a question of existing skill levels than of the specific language itself. Unfortunately, language choices (and operating systems) are often more of a religious debate than a technical one. The key, in my opinion, is always to look at a programming language as a tool, and it becomes simply a question of what is the “right” tool for the job. Realize that any programming language carries with it certain things (forces) that it does well and certain things (forces) it does not do well. If your architecture relies on multiple-inheritance techniques and the language choice is Visual Basic (which supports only a single-inheritance model), then we must change either the architecture or the language used. Sometimes we have to do both.

Implement a small part of your system to prove that the underlying architecture and approach are sound.

Some important things to consider in implementing this architecture is that we first should define the core interfaces for BlackJackHand and Displayer. From that point forward we can place multiple developers on the subclasses and on other parts of the system. This gives us amazing freedom in allowing parallel development of this system. As you can see in Figure 7.6, the code itself is actually fairly simple, or at least as simple as a trivial single module approach would be. In addition, it should be obvious that the testing of each unit by the developer can be done quickly, and this system may evolve at a rapid pace.

Figure 7.6. Sample program execution



See Appendix C: Blackjack Code for detail code.

Restart the Whole Process at a Lower Level, as Necessary

Now we should have a small portion of the system built forming a core of our system. In all but the most trivial systems, we must now begin the entire process again. The difference is that this cycle is now constrained by the existing application. As we go through each iteration, the system becomes more constrained, much like an empty house we are trying to furnish. As you continue to furnish it, the amount of room left, as well as the choices available to match the remaining pieces, becomes less and less. Luckily, I am far better at software development than interior decoration.

The one major change I might introduce to this project at this point is to abstract the dealer another layer. This would allow the exact rules to vary without a code change. I asked myself if there was any advantage to doing this now as opposed to later, and I decided to postpone it until necessary.

A useful exercise is to add a logging mechanism to record all game play for review. This can be done by adding a Decorator (see the previous Burger example for more information on the decorator), as follows:



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

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