JAL Computing

C++COMProgramming .NET Mac Palm CPP/CLI Hobbies

 

Home
Up

Chapter 40 "Subclassing a Singleton"

In Chapter 4, we discussed the Singleton Pattern which limits the instantiation of a class to a single instance. To be fair, the singleton pattern is sometimes criticized as encouraging the use of global variables and methods, sort of an anti-pattern. The Singleton Pattern provides functionality and generates IL code similar to the use of Static methods and fields. For instance, both Static methods/fields and Singletons have been used to create counters. Prefer the simpler static approach, but recognize the advantages of the Singleton Pattern over using static methods and fields which include:

bulletThe ability to use a class with an inheritance/interface hierarchy
bulletThe ability to limit access to the single instance using a parameterized GetInstance method
bulletAdding a level of indirection that allows the creation of more than one instance of the "singleton" in the future

Note: It is an error to declare a static method abstract or virtual.

Declaring a static method abstract prohibits instantiation of the enclosing class. Since static methods are part of the class itself they can be invoked without creating an instance of the class, so there is no logic in prohibiting instantiation of the enclosing class. Declaring a static method virtual is an error since it would result in runtime ambiguity. If the static virtual method was overridden in more than one subclass, the runtime would not be able to determine the "proper" implementation to invoke.

In general, trying to subclass a singleton is complex as discussed in this article at MSDN. Note the difference between sub classing a singleton and applying the singleton pattern to a class that has an inheritance/interface hierarchy.

"The GoF Singleton pattern details the issues with trying to subclass a singleton and it is generally not a trivial matter. In most cases, it is very easy to develop a singleton without a parent class, and adding sub-classing functionality adds a new level of complexity that is generally not needed anyway."

However complicated, one can try to subclass a Singleton. The GoF (Gang of Four, "Design Patterns, Elements of Reusable Object-Oriented Software," Gamma, Helm, Johnson and Vlissides) suggest one approach which is to embed a class factory in the singleton base class. Another approach is to use a "registry." These patterns do not strictly prohibit the instantiation of more than one instance of the base class, but they do provide a global point of access to a single instance of the base class.

Note: As Brian G. states at microsoft.public.dotnet.framework neither of these approaches represents a strong singleton guarantee since a poorly designed subclass could allow the creation of more than one instance of the base class.

Even with a properly designed set of subclasses, concurrency conflicts could result in the creation of more than one instance of the base class. However, these patterns do insure that only one instance of the base class will be successfully registered.

Although it is not possible to provide a strong singleton guarantee when sub classing a singleton, the "global point of access" will return a single instance of the base class, a valid subclass, at runtime, or throw.

Embedded Class Factory

In the first example, the GoF used an embedded class factory in the singleton base class. So given a singleton base class OnlyMaleAnimal that inherits from Animal and two concrete nested subclasses, NestedMaleTiger and NestedMaleGiraffe, we can do:

       public enum MaleAnimal { Tiger, Giraffe };
        // GOF Approach
        public static OnlyMaleAnimal CreateSingletonMale(MaleAnimal e)
        {
            lock (padlock2)
            {
                if (uniqueMA == null)
                {
                    switch (e)
                    {
                        case MaleAnimals.Giraffe:
                            uniqueMA = new NestedMaleGiraffe();
                            break;
                        case MaleAnimals.Tiger:
                            uniqueMA = new NestedMaleTiger();
                            break;
                        default:
                            break;
                    }
                }
            }
            return uniqueMA;
        }

As the GoF note, this approach is not very extensible. Also note the use of nested classes to prohibit construction of an instance of the subclass from outside the class.

Registry Approach

The GoF also discuss the use of a "registry" to limit creation of a single instance of a class. This alternative approach is more extensible, but is more complicated. In the following code, the base class only allows one child class to "register" with the base class. Here is an example in Java that also demonstrates "registering" a sub class in the singleton base class.

In the following C# version, a client that wishes to use the singleton base class must first call TryCreateSingletonMale on a valid subclass. The first runtime call will "register" the single instance with the base class. Any further calls to TryCreateSingletonMale from any subclass will fail. Note that OnlyMaleAnimal.Instance may throw an exception if the singleton has not been initialized by a call to TryCreateSingletonMale. A call to the protected get property UniqueMA does not thrown an exception on null. Also note the use of simple thread safety as described by Jon Skeet.

Here is the "registry" code in the base class OnlyMaleAnimal:

        private static OnlyMaleAnimal uniqueMA= null;
        // can only return one instance of a MaleAnimal
        // could return null if UniqueMA not initialized with valid reference
        // one could look at this as the "registry" discussed by the GoF
        protected static OnlyMaleAnimal UniqueMA
        {
            get { return uniqueMA; }
            set {
                lock (padlock1)
                {
                    if (uniqueMA == null)
                    {
                        uniqueMA = value; // only one instance may "register" with this base class
                    }
                    //else do nothing if two threads read null "simultaneously", only one will write.   
                    //or you could throw here on concurrency conflict
                    //theoretically could create more than one instance on concurrency conflict
                    //but only one instance would be "registered"
                }
            }
        }

Only one call to the setter will succeed so that only a single instance will be registered. The caller can then access the single instance using the "global point of access" the public getter function OnlyMaleAnimal.Instance:

        // ASSERT: uniqueMA must be initialized by a valid concrete subclass
        public static OnlyMaleAnimal Instance
        {
         get 
         {
            if (uniqueMA == null)
            {
                throw new Exception("Failure to initialize singleton OnlyMaleAnimal.");
            }
            return uniqueMA;
         }
        }

Any subclass of OnlyMaleAnimal should provide a method TryCreateSingletonMale. This use of the try/out idiom is used to make it clear that registration could fail. For instance, the call MaleTiger.TryCreateSingletonMale might return false on failure and the out parameter of type OnlyMaleAnimal could refer to an object of a different subclass such as MaleGiraffe!

        static readonly object padlock = new object();
        public static bool TryCreateSingletonMale(out OnlyMaleAnimal oma)
        {
            lock (padlock) // use lock to avoid concurrency conflict
            {
                bool success;
                if (UniqueMA == null)
                {
                    // UniqueMA may NOT be updated on concurrency conflict
                    UniqueMA = new MaleTiger(); // this is in turn a thread safe call however
                    // on conflict only one assignment will succeed
                    success = true;
                }
                else {success = false;}
                oma= UniqueMA;
                return success; // thread safe return value
            }
        }
Source Code

Here is the source code. Enjoy.

using System;

namespace SubclassSingleton
{
    // demonstrates subclassing a singleton AND
    // creating a single instance of a class with an inheritance hierarchy
    // in this example there can only be one instance of any subclass of female animal
    // there can only be one instance of ANY male animal
    class NoahsArkWithOneMale
    {
        private Animal[] aa = new Animal[6];
        static void Main(string[] args)
        {
            NoahsArkWithOneMale na = new NoahsArkWithOneMale();
            na.aa[0] = FemaleGiraffe.Instance;
            na.aa[1] = FemaleSnake.Instance;
            na.aa[2] = FemaleTiger.Instance;
            OnlyMaleAnimal oma;
            //na.aa[3]= OnlyMaleAnimal.Instance; // throws
            //na.aa[3] = OnlyMaleAnimal.CreateSingletonMale(MaleAnimals.Tiger); // GoF approach
            //new OnlyMaleAnimal.NestedMaleTiger(); //invalid call, nested
            System.Console.WriteLine(MaleGiraffe.TryCreateSingletonMale(out oma)); // succeeds
            na.aa[3] = oma;
            System.Console.WriteLine(MaleTiger.TryCreateSingletonMale(out oma));  // fails
            na.aa[4] = oma;
            na.aa[5] = OnlyMaleAnimal.Instance;
            foreach (Animal a in na.aa)
            {
                System.Console.WriteLine(a.Name+"("+a.Sex+")");
            }
            System.Console.ReadLine();
            // output true false G(F) S(F) T(F) G(M) G(M) G(M)
        }
    }
    public abstract class Animal
    {
        public abstract string Sex {get;}
        public virtual string Name { get { return "Animal"; } }
    }
    public enum MaleAnimals { Tiger, Giraffe }; // for GoF sample code
    // singleton class to be subclassed
    public abstract class OnlyMaleAnimal : Animal
    {
        protected OnlyMaleAnimal() { ;}
        static readonly object padlock1 = new object();
        static readonly object padlock2 = new object();
        
        private static OnlyMaleAnimal uniqueMA= null;
        // can only return one instance of a MaleAnimal
        // could return null if UniqueMA not initialized with valid reference
        // one could look at this as the "registry" discussed by the GoF
        protected static OnlyMaleAnimal UniqueMA
        {
            get { return uniqueMA; }
            set {
                lock (padlock1)
                {
                    if (uniqueMA == null)
                    {
                        uniqueMA = value; // only on instance may "register" with this base class
                    }
                    //else do nothing if two threads read null "simultaneously", only one will write.   
                    //or you could throw here on concurrency conflict
                    //theoretically could create more than one instance on concurrency conflict
                    //but only one instance would be "registered"
                }
            }
        }

        
        // GOF factory method Approach
        public static OnlyMaleAnimal CreateSingletonMale(MaleAnimals e)
        {
            lock (padlock2)
            {
                if (uniqueMA == null)
                {
                    switch (e)
                    {
                        case MaleAnimals.Giraffe:
                            uniqueMA = new NestedMaleGiraffe();
                            break;
                        case MaleAnimals.Tiger:
                            uniqueMA = new NestedMaleTiger();
                            break;
                        default:
                            throw new ArgumentException("Internal logic error.");
                            //break;
                    }
                }
            }
            return uniqueMA;
        }
        // stub
        class NestedMaleGiraffe : OnlyMaleAnimal
        {

        }
        // stub
        class NestedMaleTiger : OnlyMaleAnimal
        {

        }
        
        // ASSERT: iniqueMA must be initialized by a valid concrete subclass
        public static OnlyMaleAnimal Instance
        {
         get 
         {
            if (uniqueMA == null)
            {
                throw new Exception("Failure to initialize singleton OnlyMaleAnimal.");
            }
            return uniqueMA;
         }
        }
        public override string Sex { get {return "M";} }
    }
    
    public sealed class MaleTiger : OnlyMaleAnimal
    {
        static readonly object padlock = new object();
        public static bool TryCreateSingletonMale(out OnlyMaleAnimal oma)
        {
            lock (padlock) // use lock to avoid concurrency conflict
            {
                bool success;
                if (UniqueMA == null)
                {
                    // UniqueMA may NOT be updated on concurrency conflict
                    UniqueMA = new MaleTiger(); // this is in turn a thread safe call however
                    // on conflict only one assignment will succeed
                    success = true;
                }
                else {success = false;}
                oma= UniqueMA;
                return success; // thread safe return value
            }
        }
        private MaleTiger() { ;}
        public override string Name { get { return "Tiger"; } }
    }
    public sealed class MaleGiraffe : OnlyMaleAnimal
    {
        static readonly object padlock = new object();
        public static bool TryCreateSingletonMale(out OnlyMaleAnimal oma)
        {
            lock (padlock)
            {
                bool success;
                if (UniqueMA == null)
                {
                    UniqueMA = new MaleGiraffe();
                    // on conflict only one assignment will succeed
                    success = true;
                }
                else { success = false; }
                oma = UniqueMA;
                return success; // thread safe return value
            }
        }
        private MaleGiraffe() { ;}
        public override string Name { get { return "Giraffe"; } }
    }

    public abstract class FemaleAnimal : Animal
    {
        public override string Sex { get { return "F"; } }
    }

    // here we create singletons of a class that is part of an inheritance hierarchy
    // as opposed to subclassing a singleton above
    public sealed class FemaleGiraffe : FemaleAnimal 
    {
        private static readonly FemaleGiraffe uniqueFG= new FemaleGiraffe();
        static FemaleGiraffe() { ;}
        public static FemaleGiraffe Instance
        {
            get 
            {
                return uniqueFG;
            }
        }
        private FemaleGiraffe() {;}
        public override string Name { get { return "Giraffe"; } }
    }

    public sealed class FemaleSnake : FemaleAnimal
    {
        private static readonly FemaleSnake uniqueFS = new FemaleSnake();
        static FemaleSnake() { ;}
        public static FemaleAnimal Instance
        {
            get
            {
                return uniqueFS;
            }
        }
        private FemaleSnake() {;}
        public override string Name { get { return "Snake"; } }
    }

    public sealed class FemaleTiger : FemaleAnimal
    {
        private static readonly FemaleTiger uniqueFG = new FemaleTiger();
        static FemaleTiger() { ;}
        public static FemaleAnimal Instance
        {
            get
            {
                return uniqueFG;
            }
        }
        private FemaleTiger() {;}
        public override string Name { get { return "Tiger"; } }
    }
}
 

Regards,
Jeff

Send mail to [email protected] with questions or comments about this web site. Copyright © 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 © 
Last modified: 08/04/09
Hosted by www.Geocities.ws

1