views:

102

answers:

1

The below code is a factory class that is delivers objects of type IGraph that have the GraphTypeAttribute implemented. Inside the static constructor of the GraphFactory, a list is built by using Linq to collect the appropriate classes to be delivered by the Factory. Normally without Linq I had a buch of loops and if-then's that could be wrapped easily with appropriate try-catch blocks. Since all is stuffed in one query now I am a bit confused on how to implement proper exceptionhandling here.

So my question(s) is/are

  • What is the best pattern to handle exceptions on the linq query.
  • Should I split it in different queries or not use linq at all?
  • Or am I mising something in the query that can eliminate non-existing elements, scanning wrong classes etc, querying duplicate values etc (optimizing the query ;).

The result of the query must be a list of all classes that the factory can deliver. E.g. decorated with the attribute and the interface implemented.

A "Factory" that creates objects for graphical representation of data:

    public sealed class GraphFactory 
    {
    static readonly GraphFactory _instance = new GraphFactory();
    static readonly IDictionary<string, Type> _items;
    static readonly Assembly _assembly = Assembly.GetExecutingAssembly();

    public static GraphFactory Instance { get { return _instance; } }
    GraphFactory() { }

    static GraphFactory() {
        try
        {
            _items = (from type in _assembly.GetTypes()
                      // filter from thatonly the classes with IGraph implemented
                      where type.GetInterface(typeof(IGraph).FullName) != null
                      // filter from thatonly the classes with GraphTypeAttribute imp.
                      from attribute in type.GetCustomAttributes(true)
                      where attribute is GraphTypeAttribute
                      select new { attribute, type })
                     // convert the result from anonymous to a dictionary
                      .ToDictionary(k => (k.attribute as GraphTypeAttribute).CustomType, 
                                          e => e.type);
        }
        /** EXH: non pokemon exception handling  * ........... * **/
    }

    public static IEnumerable<string> FriendlyNames  { get { return _items.Keys; } }

    public static IGraph CreateGraph(string friendlyName)
    {
        /** inspect argument, check it's a key 
            in the dictionary and throw exeptions if needed **/     

        IGraph result = null;
        try
        {
            result = _assembly.CreateInstance(_items[friendlyName].FullName) as IGraph;
        }
        /** non pokemon exception handling * ...........  * **/
        return result;
    }
}

the interface (members ommitted):

public interface IGraph { } 

attribute to decorate the appropriate classes for factory assigment

[AttributeUsage(AttributeTargets.Class, AllowMultiple=false,Inherited=true)]
public class GraphTypeAttribute : System.Attribute 
{ public GraphTypeAttribute(string friendlyName)  { } }

the classes decorated with the attribute

[GraphTypeAttribute("piechart")]
public class PieChart : IGraph{ }

[GraphTypeAttribute("map")]
public class WorldMap : IGraph { }

[GraphTypeAttribute("horizontalbar")]
public class Bar : IGraph { }

[GraphTypeAttribute("verticalbar")]
public class VerticalBar : Bar { }

sample usage:

  foreach (string friendlyName in GraphFactory.FriendlyNames)
  {
   IGraph auth = GraphFactory.CreateGraph(friendlyName);
  }

Any other comments or advise on the class is thankfully appreciated.

+2  A: 

I think this is a good example of the dynamic factory pattern. I do this all the time. I understand your concern about exception handling but I think there is no need for this, simply because your unit tests will prevent this factory from ever throwing during production and a good unit test can explain as clearly the problem as an exception message.

But if you really want to do error checking, your LINQ query will never throw an exception. It is the ToDictionary that will throw when there is a double key. What you can do is validate the results of the LINQ query and communicate the double keys:

static GraphFactory()
{ 
    var items = (
        from type in _assembly.GetTypes()
        where type.GetInterface(typeof(IGraph).FullName) != null
        from attribute in type.GetCustomAttributes(true)
            .OfType<GraphTypeAttribute>
        select new { attribute, type }).ToArray();

    ValidateTypes(items);

    _item = items.ToDictionary(
        k => k.attribute.CustomType, e => e.type);
}

private static void ValidateTypes<T>(T[] items)
{
    var firstDoubleCustomType = (
        from item in items
        group item by item.attribute.CustomType into g
        where g.Count() > 1
        select g.Key).FirstOrDefault();

    if (firstDoubleCustomType != null)
    {
        throw new InvalidProgramException(
           "Doube: " + firstDoubleCustomType.ToString());
    }
}
Steven
Sorry I cannot have functions outside the constructor, exceptions wil not be exposed outside the class that way ;) .OfType<GraphTypeAttribute> looks nice anyway ;) thanks! And the link to the doc is great!
Caspar Kleijne
@Casper: I just wanted my example to be readable. You can place it all inline if you wish.
Steven
@Casper: You can also create all instances once in your static constructor (and why not simply use `Activator.CreateInstance(Type)`). This will make the system fail fast when there is an invalid type. When this is a problem, putting your usage example in a unit test seems like a good idea. Having enough unit tests can save you from having a lot of ugly error handling.
Steven
With some tiny modifications on the code it works great. Thanks!
Caspar Kleijne
Geen probleem ;-)
Steven