Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to dynamically create classes instead of using a switch block

Currently I have my VaryByCustom functionality implemented in classes that implement an interface IOutputCacheVaryByCustom

public interface IOutputCacheVaryByCustom
{
    string CacheKey { get; }
    HttpContext Context { get; }
}

A class implementing this interface has a few conventions the name of the class will be "OutputCacheVaryBy_______" where the blank is the value that is passed in from the varyByCustom property on pages. The other convention is that Context will be set through constructor injection.

Currently I'm basing this off an enum and a switch statement similar to

public override string GetVaryByCustomString(HttpContext context, 
                                              string varyByCustomTypeArg)
{
    //for a POST request (postback) force to return back a non cached output
    if (context.Request.RequestType.Equals("POST"))
    {
        return "post" + DateTime.Now.Ticks;
    }
    var varyByCustomType = EnumerationParser.Parse<VaryByCustomType?>
                            (varyByCustomTypeArg).GetValueOrDefault();


    IOutputCacheVaryByCustom varyByCustom;
    switch (varyByCustomType)
    {
        case VaryByCustomType.IsAuthenticated:
            varyByCustom = new OutputCacheVaryByIsAuthenticated(context);
            break;
        case VaryByCustomType.Roles:
            varyByCustom = new OutputCacheVaryByRoles(context);
            break;
        default:
            throw new ArgumentOutOfRangeException("varyByCustomTypeArg");
    }

    return context.Request.Url.Scheme + varyByCustom.CacheKey;
}

Since I always know that the class will be OutputCacheVaryBy + varyByCustomTypeArg and the only constructor argument will be context I realized I could bypass needing this glorified if else block and could just instantiate my own object with Activator.

With this being said, reflection is not my strong suit and I know that Activator is substantially slow comparatively to static creation and other ways to generate objects. Is there any reason why I should stick with this current code or should I use Activator or a similar way to create my object?

I've seen the blog http://www.smelser.net/blog/post/2010/03/05/When-Activator-is-just-to-slow.aspx but I'm not really sure how this would apply since I'm working with types at runtime not static T.

like image 286
Chris Marisic Avatar asked Aug 26 '10 22:08

Chris Marisic


4 Answers

If Reflection is too slow for you. You can probably get your own ObjectFactory working. It's really easy. Just add a new method to your interface.

    public interface IOutputCacheVaryByCustom
    {
        string CacheKey { get; }
        IOutputCacheVaryByCustom NewObject();
    }

Than create a static readonly CloneDictionary that holds the object templates.

    static readonly
        Dictionary<VaryByCustomType, IOutputCacheVaryByCustom> cloneDictionary
        = new Dictionary<VaryByCustomType, IOutputCacheVaryByCustom>
        {
            {VaryByCustomType.IsAuthenticated, new OutputCacheVaryByIsAuthenticated{}},
            {VaryByCustomType.Roles, new OutputCacheVaryByRoles{}},
        };

If you finished with that, you can use the enum that you already have in order to select the template in the dictionary and call NewObject()

        IOutputCacheVaryByCustom result = 
             cloneDictionary[VaryByCustomType.IsAuthenticated].NewObject();

Is just that simple. The NewObject() method you have to implement, will return a new Instance by directly creating the object.

    public class OutputCacheVaryByIsAuthenticated: IOutputCacheVaryByCustom
    {
        public IOutputCacheVaryByCustom NewObject() 
        {
            return new OutputCacheVaryByIsAuthenticated(); 
        }
    }

That's all you need to have. And it's incredible fast.

like image 155
BitKFu Avatar answered Oct 23 '22 05:10

BitKFu


You don't really need to use reflection since it's a rather limited set of possible values. You could however do something like this

internal class Factory<T,Arg>
{
   Dictionary<string,Func<Arg.T>> _creators;
   public Factory(IDictionary<string,Func<Arg,T>> creators)
  {
     _creators = creators;
  }
}

and substitute your creation logic with

_factory[varyByCustomTypeArg](context);

it's not as fast as a switch but it keeps construction and use nicely seperate

like image 24
Rune FS Avatar answered Oct 23 '22 05:10

Rune FS


I really like to have object creation taken care of by someone else. For example if i need different concrete implementations of an interface an IoC container has worked wonders for me.

As a simple example using unity you have a configuration portion linking up keys to implementations like so:

public void Register(IUnityContainer container)
{
   container.RegisterType<IOutputCacheVaryByCustom,OutputCacheVaryByIsAuthenticated>("auth");
   container.RegisterType<IOutputCacheVaryByCustom,OutputCacheVaryByRoles>("roles");
}

and your creation would look much simpler like so:

//injected in some form
private readonly IUnityContainer _container;

public override string GetVaryByCustomString(HttpContext context, 
                                              string varyByCustomTypeArg)
{
    //for a POST request (postback) force to return back a non cached output
    if (context.Request.RequestType.Equals("POST"))
    {
        return "post" + DateTime.Now.Ticks;
    }
    try
    {
    IOutputCacheVaryByCustom varyByCustom = _container.Resolve<IOutputCacheVaryByCustom>(varyByCustomTypeArg, new DependencyOverride<HttpContext>(context));
    }
    catch(Exception exc)
    {
       throw new ArgumentOutOfRangeException("varyByCustomTypeArg", exc);
    }
    return context.Request.Url.Scheme + varyByCustom.CacheKey;
}

Or if IoC is not an option i would let a factory create the concrete classes, so you never have to worry your actual methods about them.

like image 26
MrDosu Avatar answered Oct 23 '22 04:10

MrDosu


Stay with the switch statement. If you only have a couple of possible cases like this, then you are just trying to use clever abstraction to avoid sitting down and getting the hard parts of your program working...

That said, from your question it seems using the Activator might work for you. Have you tested it? Was it really too slow?

Alternatively, you could just keep a bunch of factory methods in a Dictionary<string, Func<IOutputCacheVaryByCustom>. This I would use, if you are creating these objects often (in a loop). You could then also optimize the string key to your enum and be done with the conversion. Going more abstract will just hide the intent of this piece of code...

like image 1
Daren Thomas Avatar answered Oct 23 '22 06:10

Daren Thomas