OT: Suggestions how to derive from singleton class.

OT: Suggestions how to derive from singleton class.

Old forum URL: forums.lhotka.net/forums/t/1877.aspx


SonOfPirate posted on Thursday, November 30, 2006

I have a need to extend a class that is implemented in a base (UI-framework) library as a singleton class.  Without going into too much detail, the class was designed to serve as a base class for application-specific implementation but implemented as a singleton in order to take advantage of that pattern, but to also provide a pseudo-global means of accessing settings and properties needed throughout the application.  It is critical that the same instance of the class is used throughout - thus the singleton implementation.

While I know that everything you read about implementing singleton indicates that the class should be sealed (NotInheritable in VB), this class is intended to be extended, so we seem to have an inherent conflict.  In the "base" class, properties that are used by other classes in the UI-framework library as well as by application code are defined.  But in order to provide a single, consistent way to access the information, we are able to extend the class by deriving a new class from it in our application code addign whatever additional properties and methods as are necessary.

The best example of what we are trying to accomplish can be found in the .NET Framework with the System.Net.WebRequest base class and derived FileWebRequest, FtpWebRequest and HttpWebRequest classes.  The WebRequest.Create() method accepts the request's URI and instantiates the proper class based in the value.  This requires that the derived classes be "registered" with the application in the config file by indicating what URI prefix is associated with each class (e.g. ftp -> FtpWebRequest).

Perhaps I need to be a little more concrete...

Essentially, we are using a UI-framework that supports automation-based application development (a derivative of the MVC pattern).  If we use the infamous and oft-referred MS-Office paradigm, all Office applications share a common set of properties and methods exposed through the Application object.  But, each has it's own spin.  For instance, if we were developing Excel and Word, we would have a base Application class in our common Office library (Office.Application) that would have Caption, CommandBars and DisplayRecentFiles properties as well as Help and Quit methods, etc.  Each application would then implement their own Application class (Excel.Application and Word.Application) derived from Office.Application.  The Excel class would have an ActiveSheet property whereas the Word class would have an ActiveDocument property.

This is essentially the model we are trying to follow.

One last note, it has occurred to me that we could possibly accomplish this by abandoning the singleton pattern and implementing each property and method statically - which would more resemble the current (2003 and before) versions of the Office apps - but, we are using this framework to support ASP.NET apps as well as Windows.  This is an important distinction because static variables behave in a bit of a different way with ASP.NET apps then might be intuitive.  Because of the ASP.NET process model, client request share the same process and thereby share the same static variables.  Anyone who has tried to store a singleton (static) object in Session and noticed how one client's changes magically affect other clients has experienced this problem.  And, since we need a separate instance of our object PER USER (i.e. per session), it would be a logistical nightmare to have to work-around this problem on a per property and method basis whereas addressing this in the singleton method allows us to encapsulate the fix in the base class and remove this from the shoulders of our application developers.

FYI - there's a great work-around for the ASP.NET singleton problem.  See http://dotnet.org.za/eduard/archive/2004/07/07/2609.aspx

Hopefully that explains our situation thoroughly.  I have scoured the web for info on this and have found nothing, so I am hoping that the experienced users of this forum will chime in and help us figure out how to make this happen.

Thanks for your input...

 

Bayu replied on Tuesday, December 05, 2006

Hey SonOfPirate,

Wouldn't it be best to use Generics + Reflection here?

The Singleton factory method would then become a generic method, something like:

public class SingletonBase
    protected sub new()
    end sub

    public shared function GetInstance(Of T As SingletonBase) as T
    end function

end class

(this code snippet was typed from scratch, sorry for any errors ;-) )

You could also put in an additional constraint to the Of T that ensures that it has a parameter-less constructor, I know for sure it is possible but I don't recall the specific syntax at the moment.

Using reflection you can then instantiate a (singleton) instance of T and return it.


However, I guess that if you would implement it as such, the class would not be a 'true' singleton, as you would allow different types of instances to be created. You can put some checks in the factory method to throw exceptions that prevent this from happening, or you can support this by maintaining a private dictionary that is indexed by Type. So then you would actually have something like a 'Multiton', that maintains a Singleton of each type. Using a private dictionary that is indexed by type (or type.tostring()) for this purpose is trivial.


Am I addressing your needs? Or did I misunderstand your question?

Kind regards,
Bayu

SonOfPirate replied on Thursday, December 07, 2006

The reason this won;t work is that we need the same instance of the object to be used by code in ALL of the class libraries that need it.  So, running with the example, when running our Word application, code in both the Word and Office class libraries need to use the same instance of the Application class.  Same with our Excel app.  However, even though both Word and Excel know that they want to create Application.GetInstance<Word.Application>() or Application.GetInstance<Excel.Application>, respectively, the classes in our common Office class library don't know how to instantiate the object this way since they won't know what application is being executed.  All that code knows is Application.GetInstance<Office.Application>.  The end result is separate instances for each class library and not what we are trying to accomplish.

We need a way for code in the base library to instantiate the most sub-class applicable to that application.  In our example, either Word.Application or Excel.application depending on which app is running.  This way, every reference to the Application object in the app will refer to the same instance throughout.

Make more sense?

 

Brian Criswell replied on Thursday, December 07, 2006

Why not just store a dictionary of Application objects with the type as the key?

public Application GetInstance<T>()
{
    if (!_applications.ContainsKey(typeof(T)))
       _applications.Add(typeof(T), new T());

    return _applications[typeof(T)];
}

I realize the syntax is not perfect, but do you get the idea?

SonOfPirate replied on Thursday, December 07, 2006

Same reason - all references need to be the same instance.  That is why I indicated the singleton pattern originally.  And, as I mentioned, the base class library doesn't know anything about the derived type.

So, for instance, if we have code in our base Office class library that makes use of the Application.CommandBars property, such as:

Application.CommandBars.ListChanged += new EventHander(CommandBars_ListChanged);

And, in our actual application library, Word.dll, we have the following code:

Application.CommandBars.Add(new CommandBar("myBar"));

unless both references to "Application" are returning the same instance, the second statement will not trigger the desired event in the base class library.

To go with the generic approach suggested, we would have the following statements:

in Office.dll:
Application<Office.Application>.CommandBars.ListChanged += new EventHander(CommandBars_ListChanged);

and,

in Word.dll:
Application<Word.Application>.CommandBars.Add(new CommandBar("myBar"));

Clearly these are two separate instances whether we are tracking them in a dictionary or not.

What we need is a way for the following to be true from both the base class library (Office.dll) and the application class library (Word.dll):

(Office.Application.Current == Word.Application.Current)

so that any calling code will return the same instance no matter how many layers deep the object model is or how many assemblies are involved.

Does that make things clearer?

 

Brian Criswell replied on Thursday, December 07, 2006

Ah sorry, I thought you wanted one of each type (i.e. Word, Excel, etc.).  This is close to what office did in the COM model as you would call GetObject("Word.Application") to get an open word application or create a new one.

If you just want to have one instance line up and are not worried about more than one type of application being active at once then just set the current office app while having Current know how to keep the office apps separate if it is going to be run in a web context.  I guess I just do not understand why you cannot just set the current app and be done with it?  Current would need to be static, but internally it would sort out which one to use in a web context.  That way all of the office objects would know where to look.

So what am I missing?

SonOfPirate replied on Thursday, December 07, 2006

Nah, you're still not getting it.  Forget the COM model because that didn't use inheritance with a common base class library.

Yes, you can instantiate the "client application" - for lack of a better name - using Word.Application or Excel.Application.  But the common, Office class library doesn't know anything about either of those applications even existing, let alone which is running.  And, for that matter, both could be running - and multiple sessions of each could be running.  It doesn't matter.  When ANY code in ANY class in ANY library within the process of the current application calls for the current Application object, we want it to return the same object.

Maybe this will help.  Here's some quick example code of our three Application classes:

In Office.dll (our common base class library):

public class Application
{
    private CommandBarCollection _commandBars;
    private static Application _currentInstance;
   
    public CommandBarCollection CommandBars
    {
        get
        {
            if (_commandBars == null)
                _commandBars = new CommandBarCollection(this);
            
            return _commandBars;
        }
    }

    public static Application Current
    {
        get
        {
            if (_currentInstance == null)
                ?????????
            
            return _currentInstance;
        }
    }
}

public class MainMenu : Office.Menu
{
    public MainMenu(Office.Application application) : base(application)
    {
        application.CommandBars.ListChanged += new CommandBarChangedEventHandler(CommandBars_ListChanged);
    }


    private CommandBars_ListChanged(object sender, CommandBarChangedEventArgs e)
    {
        if (e.ListChangedType == ItemAdded)
            Items["Toolbars"].Add(new MenuItem(e.CommandBar.Name));
    }
}

In Word.dll (a "client application" referencing the common base class library):

public class Application : Office.Application
{
    private DocumentCollection _documents;
   
    public DocumentCollection Documents
    {
        get
        {
            if (_documents == null)
                _documents = new DocumentCollection(this);
   
            return _documents;
        }
    }
   
    protected void Initialize()
    {
        CommandBars.Add(new FormattingToolbar());
    }
}

In Excel.dll (another "client application" referencing the common base class library):

public class Application : Office.Application
{
    private WorkbookCollection _workbooks;
   
    public WorkbookCollection Workbooks
    {
        get
        {
            if (_workbooks == null)
            {
                _workbooks = new WorkbookCollection(this);
                _workbooks.SelectionChanged += new SelectionChangedEventHandler(Workbooks_SelectionChanged);
            }
            
            return _workbooks;
        }
    }

    private Workbooks_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        Application.Title = e.SelectedItem.Name;
    }
}

This is a pretty simplistic example but does demonstrate the needed functionality with the exception of how to instantiate the Application class.  As you can see, the object used by the MainMenu class in Office.dll needs to be the same as what's used in Word.dll (and Excel.dll) if the event is to respond to changes made to the CommandBars collection by code in that library - such as adding the FormattingToolbar in Word.dll.

Does this shed better light on the situation?

 

Brian Criswell replied on Friday, December 08, 2006

Set the current instance when the application instantiates.


public class Application
{
    private CommandBarCollection _commandBars;
    private static Application _currentInstance;

    protected Application()
    {
       if (_currentInstance != null)
          throw new AppAlreadyExistsException();

       _currentInstance = this;
    }
  
    public CommandBarCollection CommandBars
    {
        get
        {
            if (_commandBars == null)
                _commandBars = new CommandBarCollection(this);
            
            return _commandBars;
        }
    }

    public static Application Current
    {
        get { return _currentInstance; }
    }
}

SonOfPirate replied on Friday, December 08, 2006

Yea that'd work except that it relies on "who gets there first".  If the first call to Application.Current is made as:

app = Office.Application.Current;

then the instance will be of the Office.Application type; whereas, if the first call is:

app = Word.Application.Current;

then the instance will be of the Word.Application type.

I like the solution, but there is no assurance that it is the desired type that is set.

In the Word application, we need it to always be Word.Application.  If we use this approach and the call from within the Office base class library (first statement above) executes first and the _currentInstance is set to type Office.Application then not only will we not be able to take advantage of the additional properties and methods added by the derived Word.Application class, but we will generate a run-time exception when we try to access one of them.

Know what I mean?

 

Brian Criswell replied on Friday, December 08, 2006

I would assume that your UI framework library could only work in the context of an application UI.  Even if that is not the case, there is no reason why the first instance of an application running could not override the office application instance when it is created.  However, I would think that if you are inheriting the base application, then you could not have an instance where the base application was on its own.

public class BaseUIApplication
{
    private static _current;
    protected BaseUIApplication()
    {
       if (_current == null)
          _current = this;
    }

    public static BaseUIApplication Current
    {
       get { return _current; }
    }
}

public class SpecificUIApplication : BaseUIApplication
{
}

public static class Program
{
    public static void main()
    {
       using (new SpecificUIApplication())
       {
          using (new MainForm())
          {
          }
       }
    }
}

In this example, the UI application will have to be the specific one for all uses of BaseUIApplication.  You just would not have any case where BaseUIApplication would not be the inherited class.

SonOfPirate replied on Friday, December 08, 2006

So then, how will other classes in the base class library make use of the base Application class (BaseUIApplication)?

As I've said, that is one of the requirements.

Brian Criswell replied on Friday, December 08, 2006

The SpecificUIApplication isa BaseUIApplication.  It is both.

SonOfPirate replied on Friday, December 08, 2006

But the classes in the base library have no knowledge of the SpecificUIApplication.  You cannot code against classes you do not know exist not to mention that you lose the whole point of having a base class library if it is calling into a specific application's code.

Brian Criswell replied on Friday, December 08, 2006

Nor do they need to know about SpecificUIApplication.  You code your base class library to the classes within it.  If you derive some of the classes into new classes, the base class library will be able to use the new classes under the guise of the base classes.  That is part of what polymorphism is all about.  For instance, if I inherit from TextBox and modify some of the behaviour, the rest of System.Windows.Forms will still be able to work with my new TextBox, and the new TextBox will exhibit the new behaviour that I added.  Parts of CSLA work with this as well as they call methods on BusinessBase and BusinessListBase.  Some of those methods can be overridden by business objects, but CSLA still knows how to work with them.

Let's try this: come up with a scenario that you believe will not work with the code snippet previously posted, and we will look at coming up with a solution.

SonOfPirate replied on Friday, December 08, 2006

public class BaseUIClass
{
    public void Initialize()
    {
        BaseUIApplication.Current.CommandBars.Add(new CommandBar("myBar"));
    }
}

This code will ONLY work if code that initializes the SpecificUIApplication executes first.  In the scenerio you described this will be the case but since many clients applications of many types will make use of the base library there is no guarantee that this will be the case.  By making the BaseUIApplication abstract, all we do is ensure that a run-time exception is thrown when an attempt is made to access the BaseUIApplication.Current property before initialized by the client application.

I am looking for a better, more reliable and stable approach.  Also keep in mind that we will be using this to drive web applications as well as windows form apps.  Perhaps relying on Application and CommandBars as the example steered things in an unintended direction.  The whole point is to have a singleton class that is used in one class library extended and used in another.

I will have to setup and test some different scenerios and see if anything comes from it, I guess.  Not as easy an answer as I'd hoped to get.  Indifferent [:|]

Brian Criswell replied on Saturday, December 09, 2006

Well, I just do not see that as an issue as only one instance of a UIApplication class would be in use within a Windows or Web application.  So when the Windows or Web application starts, it creates the UIApplication that should be used and you are done with it.  But you do not like that, so I will offer a few other suggestions.

One is to keep in mind that the Office automation model does not use the singleton pattern except for the current document (I think).  Everything else requires a reference to an object to perform an operation.

The other is that you your base classes can instantiate objects they know nothing about.
Activator.CreateInstance("MySpecificApplicationLibrary",  "MySpecificApplicationLibrary.MySpecificApplication");
would create an instance of an inherited class even when called from base class code.  Your windows or web application could have the dll and type to create in the .config file.

Good luck!

Copyright (c) Marimer LLC