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.
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.
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.