Advanced Exercise

The object creation for each state change can make this particular player rather slow. Using the Singleton design pattern can make this application run faster. The difference in performance is small for this application, but can be huge for other applications. We note that the Singleton design pattern assures that only a single instance of a given class exists, guaranteeing a single centralized object which can have a number of consequences. First, it can prevent problems from duplicate use of precious resources. Second, it can improve performance and reduce memory consumption.

The general idea behind the Singleton design pattern is to assure that only a single instance of a given class ever exists. This is done by avoiding the use of an explicit constructor. Instead, a static method is provided by the class that will return the one-and-only instance of the object. Because of the technical differences in Java and Python, we'll cover each implementation in some detail.

The deliverables for this exercise are revisions to the Player1326State class hierarchy to implement the Singleton design pattern. This will not change any of the existing unit tests, demonstrations or application programs.

Singleton Design in Java

To implement the Singleton design pattern for the entire state class hierarchy, we must add a static variable, theInstance, initially null, to each individual subclass of Player1326State. Additionally, we have to add a getInstance method to each subclass. Finally, we can make the constructor protected in the superclass to prevent any other class from using it via the new operator.

Note that we can't inherit a single superclass definition of theInstance variable or getInstance method. Being static, these would be shared by all objects of all four subclasses. We would only have a single Player1326State object, and it would be the initially constructed object. Consequently, we need to have a distinct instance variable and method in each subclass, so that an instance of each distinct subclass gets created.

This aspect of this class hierarchy has to be included by a tedious cut-and-paste process. It might be nice to specify that this piece of programming is copied out of a template into each class, assuring consistent implementation of the Singleton design pattern. There are a few automated approaches to this, including tools for aspect-oriented programming and literate programming. Both of these are areas for further investigation, well outside the scope of this book.

The Player1326 constructor would be changed to use the getInstance method of Player1326NoWins to create the initial state. We have to remove the creation a new instance via new Player1326NoWins() and replace this with Player1326NoWins.getInstance().

Each state's nextWon and nextLost methods would have to use the getInstance method to get the next state instance. By removing all of the individual new operations, we assure that objects are only made available through getInstance, controlling the number of instances actually created.

We note that the compile-time binding in Java forces us to repeat these two static elements in each class. There is some administrative overhead in assuring that this aspect of the Singleton pattern is repeated in each subclass. In Python, however, the late binding makes this slightly simpler.

Singleton Design in Python

This class can be sped up by using the Singleton design pattern for the entire state class hierarchy. In Python, we have two choices for dealing with the various singleton instances. The most common solution is to create module-level variables at import time; Python assures that each module is only important once. A second solution is to implement a Java-like singleton design in the classes.

Because of the deferred binding in Python, we can easily have each class contain references module-level variables that won't be created until after the class is defined. The variables don't exist when the class is defined, but they will exist when the object methods are actually executed. Each class can then expect a module-level variable with a name like theNoWinsState. These four variables are created by the module after the definitions of the classes.

The Java-style declaration means that we have a theInstance variable which contains the one-and-only instance for this class. Note that we have two choices for handling the object creation. We can override the __new__ method, or we can provide a getInstance method. We'll focus on the getInstance method because it's most like Java.

For programmers new to Python, there are two small syntax changes that make a variable or method part of the class and not part of each individual instance. First, instance variables must be created by __init__ and qualified with the instance name (usually self). If, instead, we simply place a variable in the class definition, this variable belongs to the class, and therefore it belongs to every instance of the class. Second, if we call a method using an object name, the method is called with that given instance as the value for self. If, instead, we call a method using the class name, the method is called for the class as a whole.