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
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
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!
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.
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.
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.
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.
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. Runnable
s 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
.
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.
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.
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:
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 Player
s, a BlackjackDealer
, Card
s, Deck
s, and a DeckPile
. As you see, the Blackjack game is a living simulation of the Blackjack domain.
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.
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.
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.
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.
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!
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.
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!
The quiz questions and answers are provided for your further understanding. See Appendix A, “Answers,” for the answers.
18.219.103.183