Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refactoring a static class to separate its interface from implementation

I am working on a .NET based application, where some of the core application classes were designed with only static methods.

Example usage:

// static access.
Parameters.GetValue("DefaultTimeout");

// static access.
Logger.Log("This is an important message!");

There's already code out there that uses these static methods, so this "interface" cannot be changed.

These classes currently implement no interface. I would like to be able to separate the actual implementation of these classes from their interface.

The reason for this refactoring is that these objects will be used across AppDomain boundaries. I would like to be able to inject a "proxy" object that on non main-appdomains will invoke some other implementation instead of the default one.

To sum up, my questions are:

  1. How can i easily transform objects with static-only access to an interface based design, such that their implementation may be replaced when needed (but keeping static access).

  2. Once refactored, how/WHEN is the actual injection of the non-default implementation should occur?

like image 577
lysergic-acid Avatar asked May 27 '12 16:05

lysergic-acid


1 Answers

Disclaimer: The following suggestion is based on the importance of not changing the calling side. I'm not saying it's the best option, just that I think it's suitable.

Disconnecting the Implementation

There is no way to have interfaces on static members, so if you don't want to change the calling code, the static will likely have to remain. That said, you can simply have your static class wrap an interface inside, so the static class itself doesn't have any implementation - it delegates all calls to the interface.

This all means you can leave your static class and any code that calls it in place. This will be like treating the static class as the interface (or contract), but having it internally swap out implementations based on the situation.

It also means your interface can have a different signature to the static class, as the interface doesn't have to conform to the calling code expectations - basically, it will turn your static class into a sort of Bridge.

Injecting the Implementation

In short: use a static constructor in order to resolve the given implementation of this interface.

Statics are per AppDomain normally (unless decorated with ThreadStaticAttribute, then per AppDomain/thread) so you can determine where you are and what implementation you need based on the current AppDomain (the static constructor will be called whenever the static is first used in the AppDomain). This means that once constructed, that particular static class's wrapped implementation will remain for the duration of the AppDomain (though you could implement methods to flush the implementation).

Cross AppDomain Calling

The code responsible for this can either be in the static classes or you can make one of the interface implementations simply a proxy manager to an AppDomain type. Any type for cross AppDomain calls will need to inherit MarshalByRefObject.

http://msdn.microsoft.com/en-us/library/ms173139.aspx

CreateInstance of a Type in another AppDomain

Simplest way to make cross-appdomain call?

Sample Application

You should just be able to copy and paste this into a new Console application. What this is doing is registering an implementation for the default AppDomain and one for the user-made AppDomains. The default simply creates a remote implementation of the interface (in the other AppDomain). Just to demonstrate the "static per AppDomain" idea, the remote implementation delegate to yet another implementation for non-default domains.

You can change implementations on the fly, all you need to change is the static class constructor (to decide what implementation to pick). Notice that you do not need to change the Main method, our calling code in this case.

using System;
using System.Reflection;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
        Console.WriteLine(Parameters.GetValue(""));
        Console.Read();
    }
}

static class Parameters
{
    private static IParameterProvider _provider;

    static Parameters()
    {
        if (AppDomain.CurrentDomain.IsDefaultAppDomain())
        {
            _provider = new ParameterProviderProxy(AppDomain.CreateDomain(Guid.NewGuid().ToString()));
        }
        else
        {
            // Breakpoint here to see the non-default AppDomain pick an implementation.
            _provider = new NonDefaultParameterProvider();
        }
    }

    public static object GetValue(string name)
    {
        return _provider.GetValue(name);
    }
}

interface IParameterProvider
{
    object GetValue(string name);
}

class CrossDomainParameterProvider : MarshalByRefObject, IParameterProvider
{
    public object GetValue(string name)
    {
        return Parameters.GetValue(name);
    }
}

class NonDefaultParameterProvider : IParameterProvider
{
    public object GetValue(string name)
    {
        return AppDomain.CurrentDomain.FriendlyName;
    }
}

class ParameterProviderProxy : IParameterProvider
{
    private IParameterProvider _remoteProvider;

    public ParameterProviderProxy(AppDomain containingDomain)
    {
        _remoteProvider = (CrossDomainParameterProvider)containingDomain.CreateInstanceAndUnwrap(
            Assembly.GetExecutingAssembly().FullName,
            typeof(CrossDomainParameterProvider).FullName);
    }

    public object GetValue(string name)
    {
        return _remoteProvider.GetValue(name);
    }
}

A Note on Life Span

One of the main problems with managing a refactoring of static classes isn't usually the changing of the client code (as this is supported by lots of refactoring tools and there are techniques to get it done safely), but managing the life span of the object. Instance objects rely on living references (otherwise they are garbage collected), these can usually be made "easily accessible" by keeping one in a public static member somewhere, but usually this is what you are trying to avoid by refactoring in the first place.

It doesn't seem like you will have to worry about this concern, as you are leaving the calling code attached to the static classes, therefore the life span will remain the same.

like image 181
Adam Houldsworth Avatar answered Oct 30 '22 13:10

Adam Houldsworth