Chapter 21. The Final Mile

Congratulations! You’ve made it to the final lesson in this book. You’ve come a long way. You should now have the foundation that you need to be successful as you continue your studies of OOP.

Today we will clean up some loose ends and then send you on your way!

Today you will learn about

  • Refactoring the Blackjack design for reuse in other systems

  • The benefits that OOP brought to the Blackjack game

  • Realities in the industry that may prevent total OO solutions

Tying Up the Loose Ends

You’ve covered a lot of ground during the past three weeks. You started by considering basic OO theory and worked your way through an entirely OOP based project. You should now have a good idea what OOP truly means.

Before completing the book, however, there are three issues left to cover:

  • Refactoring the Blackjack design for reuse in other systems

  • A survey of the benefits that OOP brought to the Blackjack system

  • A word about industry realities and OOP

Refactoring the Blackjack Design for Reuse in Other Systems

There is a small issue related to the Blackjack game design that we need to examine. This issue does not impact the Blackjack game, but it could negatively impact another OO system if it reuses the design incorrectly.

On Day 15 you saw two alternative solutions. In one solution the dealer loops over its players telling each to play after the previous player completes. You then saw a more object based approach to the game loop.

Instead of the dealer going through the players one by one and telling them what to do, the OO dealer started each player and waited for the player to tell him when it was done playing.

This offered a cleaner solution because you left it up to the player to tell the dealer when it was done—only then did the dealer continue. It also turns out that this design was absolutely required for the GUI to work; otherwise, a human player would instantly return, and if the dealer were looping, the dealer would tell the next player to go. The human player would never get a turn in the procedural approach!

The Design Problem

There is a small problem with the approach as outlined in the lessons. Let’s trace through the method calls in a game where there is one player and a dealer. Both players stand during their turn.

Listing 21.1 represents a trace of all method calls in the game. The stack terminates when a method returns.

Example 21.1. A Method Stack Track

BlackjackSim.main
 BlackjackDealer.newGame
  Player.play
   BlackjackDealer$DealerCollectingBets.execute
    Player.play
     BettingPlayer$Betting.execute
      BlackjackDealer.doneBetting
       Player.play
        BlackjackDealer$DealerCollectingBets.execute
         BlackjackDealer$DealerDealing.execute
          BlackjackDealer$DealerWaiting.execute
           Player.play
            Player$Playing.execute
             Player$Standing.execute
              BlackjackDealer.standing
               Player.play
                BlackjackDealer$DealerWaiting.execute
                 Player$Playing.execute
                  BlackjackDealer$DealerStanding

The problem is subtle. None of the methods return until the dealer finishes his turn! The methods recursively call one another. So for example, the notifyChanged method in Listing 21.2 will not execute until the current game ends.

Example 21.2. A Method That Will Not Get Called Until After the Current Game Ends

public void execute( Dealer dealer ) {
    if( hit( dealer ) ) {
        dealer.hit( Player.this );
    } else {
        setCurrentState( getStandingState() );
        notifyStanding();
    }
    current_state.execute( dealer );
    // transition

    // will not get called until stack unwinds!!!!
    notifyChanged();
}

In the Blackjack game, this is not really a problem because there is a limit of seven players and the method call stack unwinds after every game. You can also code carefully around problems like the one demonstrated in Listing 21.2.

However, imagine a simulator with hundreds or thousands of objects that follow the Blackjack game design. If these objects call one another recursively, even if the stack does eventually unwind, you could end up running out of memory. Either way, each method call will allocate more memory. If you follow this design unaltered, your system either will not run or will require a whole lot more memory than absolutely necessary.

Sewing a Solution with Threads

Luckily there is a solution: threads. While a full discussion of threading is well beyond the scope of this book, you’ll see how you can use threading to solve the method call problem quickly.

Note

Almost every non-trivial system will share two characteristics:

  • They will be threaded

  • They will have state logic

The Blackjack system shares both of these characteristics.

A thread is simply a path of execution through your program. So far, the Blackjack system has one thread of execution. Figure 21.1 helps visualize that single thread.

The single threaded Blackjack system.

Figure 21.1. The single threaded Blackjack system.

Because the Blackjack game is single threaded (it only has one thread), that single thread does everything.

Threading allows you to create multiple threads of execution through your program. By creating multiple threads, your program can do many different things at the same time. Figure 21.2 helps visualize two threads running through the Blackjack system.

The multithreaded Blackjack system.

Figure 21.2. The multithreaded Blackjack system.

Threading the Blackjack system can allow a method to return right away. Let’s look at a simple thread example from Java. Listing 21.3 presents a simple thread that prints out "Hello World!".

Example 21.3. A Threaded "Hello World!"

public class HelloWorld {

    public void sayHello() {
        System.out.println( "Hello World!" );
    }

    public static void main( String [] args ) {
        final HelloWorld hw = new HelloWorld();

        Runnable runnable = new Runnable() {
            public void run() {
                hw.sayHello();
            }
        };

        Thread thread = new Thread( runnable );
        thread.start();

        System.out.println( "All Done!" );
    }
}

HelloWorld itself is a simple class that has one method: sayHello. sayHello prints out a message to the command line.

The main is where it gets interesting. First, the main instantiates HelloWorld. It then creates an anonymous Runnable class. Runnables have one method: run. run tells the thread what to do when it is started. In this case it will tell the HelloWorld instance to print its message.

After creating the Runnable, the main instantiates a Java Thread. When you create a Thread you need to pass it a Runnable. The Runnable’s run method tells the Thread what to do when you tell the Thread to start. After starting the Thread the main prints out a message of its own.

You may be surprised when you see the main run. Figure 21.3 presents the output of HelloWorld.

The output of HelloWorld.

Figure 21.3. The output of HelloWorld.

When running HelloWorld, you’ll find that “All Done” gets printed before “Hello World!” The call to Thread.start does not block like other method calls. Because start starts a new thread of execution it automatically returns. After you call start you have two threads of execution in the HelloWorld program. It just so happens that the main prints its message before the new thread gets a chance to call sayHello.

You can use the fact that start does not block to fix the design shortcoming in the Blackjack game. Listing 21.4 presents a new Waiting state for the BlackjackDealer that starts each player on its own thread.

Example 21.4. A Threaded DealerWaiting

private class DealerWaiting implements PlayerState {
    public void handChanged() {
        // not possible in waiting state
    }
    public void handPlayable() {
        // not possible in waiting state
    }
    public void handBlackjack() {
        // not possible in waiting state
    }
    public void handBusted() {
        // not possible in waiting state
    }
    public void execute( final Dealer dealer ) {
        if( !waiting_players.isEmpty() ) {
            final Player player = (Player) waiting_players.get( 0 );
            waiting_players.remove( player );
            Runnable runnable = new Runnable() {
                public void run() {
                    player.play( dealer );
                }
            };
            Thread thread = new Thread( runnable );
            thread.start();
        } else {
            setCurrentState( getPlayingState() );
            exposeHand();
            getCurrentState().execute( dealer );
            // transition and execute
        }
    }
}

By starting each player on its own thread, the BlackjackDealer’s state execute method can return right away, thus unrolling the stack. This does interject some difficulties if you loop calls to newGame because newGame will now return before the game is actually finished. If you loop, you’ll start another game before the last has finished and then you’ll run into all kinds of nasty problems. You can solve this problem by telling the BlackjackDealer how many times to loop. At the end of each game, it can check to see if it needs to play again.

Note

Threading is just one way to solve the problem with recursion. I presented a threaded solution here to give you some exposure to threads.

The loop/thread problem raises some concerns. You could also create a GameTable object that would start and stop the threads. The Dealer could then listen to the table’s state and deal, hit, fulfill, and so on based on the state. However, such an approach is a bit more involved than simply threading the players as they start.

You could also get rid of the recursion through iteration over the players.

The good news is that if you don’t loop, such as in the GUI, you can easily thread by simply changing the BlackjackDealer’s DealerWaiting state! The downloadable source contains threaded versions of the GUI.

Caution

It is easy to thread the Blackjack game because only one player thread runs at any given time. You don’t have many different player threads running concurrently.

Threading becomes tricky when many threads run concurrently and share the same data!

Identifying the Benefits the OOP Brought to the Blackjack System

The first week pointed out some of the goals and benefits of OOP. To recap, OOP attempts to produce software that is

  • Natural

  • Reliable

  • Reusable

  • Maintainable

  • Extendable

  • Timely

OOP brought each of these benefits to the Blackjack system. The Blackjack system fulfills each of the goals of OOP:

  1. Natural: The Blackjack system naturally models a game of Blackjack.

    The Blackjack system exists in the terms of an actual Blackjack game. The Blackjack system is made up of Players, a BlackjackDealer, Cards, Decks, and a DeckPile. As you see, the Blackjack game is a living simulation of the Blackjack domain.

  2. Reliable: The Blackjack system is reliable.

    Through a combination of careful testing and encapsulation you’ve created a reliable Blackjack system. Because you have isolated knowledge and responsibility and placed them where they belong, you can make enhancements to the system without worrying about negatively impacting unrelated parts of the system.

  3. Reusable: The Blackjack system is reusable.

    Because this was the first card game that you have written, there wasn’t a lot of emphasis placed on writing an abstract card game framework. Instead, you wrote a Blackjack game. As a result the game is not completely reusable; however, classes such as Card, Deck, and Deckpile can be reused across almost any card game.

    Furthermore, many of the design ideas are reusable across many problems. As you write more card games, you will be able to abstract further and create a fully reusable framework.

  4. Maintainable: The Blackjack system is maintainable.

    By encapsulating knowledge and responsibility where they belong, it is simple to make changes to one part of the system without negatively impacting other unrelated parts of the system.

    Such divisions make it possible to make improvements to the system at any time. You’ve also seen first hand how inheritance and polymorphism make it possible to add new players to the system at any time.

  5. Extendable: The Blackjack system is extendable.

    You saw firsthand how you can add new players to the system. Furthermore, through careful inheritance you can introduce new types of cards (such as visual cards) and hands. The iterative process proved just how extendable an OOP system can be.

  6. Timely: The Blackjack system is timely.

    You were able to produce a full Blackjack game in four iterations—a week’s worth of time. Now that’s timely!

Industry Realities and OOP

The lessons of this book have assumed that you’re starting your OOP projects from scratch. When you start from scratch you don’t have to integrate into legacy, non-OO, backend systems. You don’t have to reuse procedural libraries. You can start fresh and everything that you use can be OO.

You’ll find that a standalone OOP project is rare. Most times you will need to interact with non-OO components. Take the case of relational databases. Relational databases are not particularly object-oriented, and object-oriented databases are still rarely used outside of some niche industries.

Java itself is not even fully object oriented. The reliance on non-OO primitives make you perform some non-OO coding from time to time.

When faced with these realities, it is best to bite the bullet and wrap these non-OO aspects in an object-oriented wrapper. For example, when dealing with relational databases, it helps to write an object persistence layer. Instead of going directly to a database and reconstituting your objects through a number of SQL queries, the persistence layer can do that work for you.

It’s really not possible to cover every type of non-OO system that you will encounter here. But it would have been negligent not to point out these realities before sending you out to apply OOP.

It will be a long time before every legacy system is converted to an object-based architecture (if it ever happens). You must be prepared for this eventuality and ready to deal with it as elegantly as possible.

Summary

You’re done! In three short weeks, this book has given you a solid foundation in OOP. The rest is up to you. You now have enough knowledge to begin applying OOP to your daily projects. Good luck!

Q&A

Q

Why did you wait until now to tell us about threading?

A

The design issue does not really affect the Blackjack game. Bringing up the possible issues sooner would have confused the issue.

It is important that you do realize the shortcomings of the design as well as a possible solution.

Threading is also an advanced topic. Threading the Blackjack game was rather easy. But threading other applications may not prove so simple.

Q

What can make threading difficult?

A

If you have multiple threads sharing data, one thread could change the data and break another thread. Such concurrency issues are extremely difficult to design, implement, and debug.

Workshop

The quiz questions and answers are provided for your further understanding. See Appendix A, “Answers,” for the answers.

Quiz

1.

How does threading take care of the recursive method call problem?

Exercises

1.

Download the source code for today’s iteration. The code is broken into four separate directories: threaded_hello_world, threaded_mvc_gui, threaded_pac_gui, and threaded_simulator. Study the code and be sure to understand how it works.

2.

Your study of OOP should not end with this book. Generate a list of topics that you would like to learn more about. Rank those topics by importance search the web for materials, and start studying!

 

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

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