Creating Factory Delegation with Autofac Using Property

I am trying to create a factory to help convert an interface based class (IIncomingMessage) to new instances of other classes (AMessage, BMessage) based on a property of one class, e.g .:

public interface IIncomingMessage
{
    public DeviceTypeEnum DeviceType {get;}
}

public class IncomingMessage : IIncomingMessage
{

    public DeviceTypeEnum DeviceType {get {return DeviceTypeEnum.TypeA;}}

    Public Byte[] RawData {get; set;}
}      

public interface IMessageTransformer<out T> where T:class
{
    T Transform(IIncomingMessage message);
}

public class AMessage 
{
    public int ChoppedUp1 {get; set;}
    public int ChoppedUp2 {get; set;}
    public int ChoppedUp3 {get; set;}       
}

public class BMessage
{
    public string SomeData {get; set;}
    public string SomeMoreData {get; set;}
}

public class AMessageTransformer : IMessageTransformer<AMessage>
{
    public AMessage Transform(IIncomingMessage message)
    {
        var result = new AMessage();

        result.ChoppedUp1 = message.RawData[1];
        result.ChoppedUp2 = message.RawData[2];
        ....
        return result;
    }
}

public class BMessageTransformation : IMessageTransformer<BMessage>
{
    public BMessage Transform(IIncomingMessage message)
    {
        throw new NotImplementedException();
    }
}

public class MessageTransformFactory<T> where T : class
{
    private readonly Func<DeviceTypeEnum, IMessageTransformer<T>> _create;

    public MessageTransformFactory(Func<DeviceTypeEnum, IMessageTransformer<T>> create)
    {
        _create = create;
    }

    public IMessageTransformer<T> GetTransformer(DeviceTypeEnum device)
    {
        return _create(device);
    } 
}

      

I am using Autofac and I am sure there must be a way to create a factory that can return the correct transformer based on the value of the DeviceType property. Many examples are used and discussed using the type of object to make a decision, but not the value. Named and Keyed Services looked promising, but looked like I still have a static list.

+3


source to share


2 answers


Okay, so after talking to you, it sounds like you're pretty open to some of the tweaks to how the transformation happens. As a result, I developed a solution for you that uses the attributes next to DeviceTypeEnum

to solve your transformers. factory follows the abstract factory pattern loosely. The factory is responsible for determining that the actual type should be created; it then delegates an instance of that type to the delegate. In this case Autofac.

Overview

To get this to work, I had to add an additional interface. IMessageTransformer

... Then I inherited from it IMessageTransformer<T>

. The base interface is nothing more than a marker interface, so I can make the factory itself non-equivalent and the delegate is Func

not generic. Now you can use the same factory instance for multiple requests IMessageTransformer<T>

. You can also just specify the factory as needed rather than injecting it since it doesn't have any dependencies.

To actually make the factory work, I wrote a unit test for it.

    [TestMethod]
    public void Shared_factory_instance_resolves_multiple_transformers()
    {
        // Arrange
        var factory = new MessageTransformFactory();
        IMessageTransformer<AMessage> aTransformer = factory.CreateTransformer<AMessage>(DeviceTypeEnum.Foo);
        IMessageTransformer<BMessage> bTransformer = factory.CreateTransformer<BMessage>(DeviceTypeEnum.Bar);

        // Act
        AMessage aMessage = aTransformer.Transform(new IncomingFooMessage());
        BMessage bMessage = bTransformer.Transform(new IncomingBarMessage());

        // Assert
        Assert.IsNotNull(aMessage, "Transformer failed to convert the IncomingMessage");
        Assert.IsNotNull(bMessage, "Transformer failed to convert the IncomingMessage");
    }

      

Note that this could have been simplified by introducing the attribute by removing DeviceTypeEnum

everything together. I left it in place as I'm not sure what your general requirements are.

Implementation

Now let's move on to the actual implementation. I took your sample code in the original question and used it to create a sample project. A sample project containing the implementation is available on GitHub for you. All source codes are shown below; the GitHub repository is just so you can download the repo and see the code in action, making sure it does what you want it to do before you deploy it to your project.

DeviceTypeEnum

The first thing I did was create enum

one that will be used to associate transformers with their classes IIncomingMessage

and Message

.

public enum DeviceTypeEnum
{
    Foo,
    Bar,
}

      

Message classes

next I created two message classes that you want to convert to.

public class AMessage
{
}

public class BMessage
{
}

      

IIncomingMessage

Next is the interface IIncomingMessage

. This interface contains only one property DeviceType

. Then I created two implementations of this interface so that I can test the actual conversion.

public interface IIncomingMessage
{
    DeviceTypeEnum DeviceType { get; }
}

public class IncomingFooMessage : IIncomingMessage
{
    public DeviceTypeEnum DeviceType { get { return DeviceTypeEnum.Foo; } }
}

public class IncomingBarMessage : IIncomingMessage
{
    public DeviceTypeEnum DeviceType { get { return DeviceTypeEnum.Bar; } }
}

      

IMessageTransformer

IMessageTransformer<T>

you specified in your example has been modified to inherit from a non-generic version of the interface.

public interface IMessageTransformer
{
}

public interface IMessageTransformer<T> : IMessageTransformer where T : class
{
    T Transform(IIncomingMessage message);
}

      

This allows you to specify the delegate method used by Autofac during resolution to not be a generic delegate. The value instead of Func<IMessageTransformer<T>>

, you are now using Func<IMessageTransformer>

. This allows the same factory instance to be reused because the instance is not tied to a specific generic type.



TransformableAttribute

Now we need to create an attribute. This attribute will be used to tell each transformer what DeviceTypeEnum

they need to support.

[AttributeUsage(AttributeTargets.Class)]
public class TransformableAttribute : Attribute
{
    public TransformableAttribute(DeviceTypeEnum deviceType)
    {
        this.DeviceType = deviceType;
    }

    public DeviceTypeEnum DeviceType { get; private set; }
}

      

If DeviceTypeEnum

only used to make the transformation easier to display, you can really remove the list all together. You would change the property DeviceType

to public Type TargetType {get; private set;}

. After that, the factory (shown below) will resolve using TargetType (a simplified factory I created). Since your example showed that this enumeration is in use, I left it as a requirement.

Note that removing the enumeration and using the property TargetType

will also allow you to create more complex relationships, and you do not need to update the enumeration every time a new mapping needs to be created. Instead of this:

var factory = new MessageTransformFactory();
IMessageTransformer<AMessage> aTransformer = factory.CreateTransformer<AMessage>(DeviceTypeEnum.Foo);

      

This will allow you to completely remove the parameter. The enumeration would not be necessary because the attribute let the factory know what the result of the target transformation should be for each transformer.

var factory = new MessageTransformFactory();
IMessageTransformer<AMessage> aTransformer = factory.CreateTransformer<AMessage>();

      

Message Transformers

We have an attribute and an interface, so we can go and create a couple of transformers that will convert from IIncomingMessage

to AMessage

or BMessage

. TransformableAttribute

will tell the transformer what type of device will be supported. The transformer itself is independent of the attribute; The factory we will use for resolution will require an attribute to bind device types to transformers.

[Transformable(DeviceTypeEnum.Foo)] // or [Transformable(typeof(AMessage)] if you replace the enum with a Target Type
public class AMessageTransformer : IMessageTransformer<AMessage>
{
    public AMessage Transform(IIncomingMessage message)
    {
        if (!(message is IncomingFooMessage))
        {
            throw new InvalidCastException("Message was not an IncomingFooMessage");
        }

        return new AMessage();
    }
}

[Transformable(DeviceTypeEnum.Bar)]
public class BMessageTransformer : IMessageTransformer<BMessage>
{
    public BMessage Transform(IIncomingMessage message)
    {
        if (!(message is IncomingBarMessage))
        {
            throw new InvalidCastException("Message was not an IncomingBarMessage");
        }

        return new BMessage();
    }
}

      

MessageTransformFactory

Now for meat and potatoes. A factory has several things that it is responsible for. He must first check the assembly collection to find all the transformer Type

he can create. It also needs to accept a factory delegate that it will use to instantiate the transformer Type

, which it has cached as needed.

MessageTransformFactory

loosely follows the abstract factory pattern. It delegates the actual creation of transformation objects to something else, in this case Autofac.

public class MessageTransformFactory
{
    /// <summary>
    /// The assemblies to cache. Defaults to including the assembly this factory exists in.
    /// if there are additional assemblies that hold transformers, they can be added via the 
    /// MessageTransformFactory.ScanAssembly(Assembly) method.
    /// </summary>
    private static List<Assembly> assembliesToCache
        = new List<Assembly> { typeof(MessageTransformFactory).GetTypeInfo().Assembly };

    /// <summary>
    /// The factory method used to instance a transformer
    /// </summary>
    private static Func<Type, IMessageTransformer> factoryMethod;

    /// <summary>
    /// The DeviceType to Transformer mapping cache
    /// </summary>
    private static Dictionary<DeviceTypeEnum, Type> deviceTransformerMapCache
        = new Dictionary<DeviceTypeEnum, Type>();

    /// <summary>
    /// Initializes the <see cref="CommandFormatterFactory"/> class.
    /// This will build the initial device to transformer mapping when the
    /// Factory is first used.
    /// </summary>
    static MessageTransformFactory()
    {
        BuildCache();
    }

    /// <summary>
    /// Sets the transformer factory used to instance transformers.
    /// </summary>
    /// <param name="factory">The factory delegate used to instance new IMessageTransformer objects.</param>
    public static void SetTransformerFactory(Func<Type, IMessageTransformer> factory)
    {
        if (factory == null)
        {
            throw new ArgumentNullException("factory", "Factory delegate can not be null.");
        }

        MessageTransformFactory.factoryMethod = factory;
    }

    /// <summary>
    /// Scans a given assembly for IMessageTransformer implementations.
    /// </summary>
    /// <param name="assemblyName">Name of the assembly to scan.</param>
    public static void ScanAssembly(AssemblyName assemblyName)
    {
        if (assemblyName == null)
        {
            throw new ArgumentNullException("assemblyName", "A valid assembly name must be provided.");
        }

        Assembly assembly = Assembly.Load(assemblyName);

        if (assembliesToCache.Any(a => a.FullName == assemblyName.FullName))
        {
            return;
        }

        assembliesToCache.Add(assembly);
        MapDeviceTypesFromAssembly(assembly);
    }

    /// <summary>
    /// Gets the available transformer types that have been registered to this factory.
    /// </summary>
    public static Type[] GetAvailableTransformerTypes()
    {
        return deviceTransformerMapCache.Values.ToArray();
    }

    /// <summary>
    /// Gets an IMessageTransformer implementation for the Device Type given.
    /// </summary>
    /// <param name="deviceType">The DeviceType that the factory must create an IMessageTransformer for.</param>
    public IMessageTransformer<T> CreateTransformer<T>(DeviceTypeEnum deviceType) where T : class
    {
        // If we have a factory method, then we use it.
        if (factoryMethod == null)
        {
            throw new NullReferenceException("The MessageTransformerFactory did not have its factory method set.");
        }

        // Cast the non-generic return value to the generic version for the caller.
        Type transformerType = MessageTransformFactory.deviceTransformerMapCache[deviceType];
        return factoryMethod(transformerType) as IMessageTransformer<T>;
    }

    /// <summary>
    /// Builds the cache of IMessageTransformer Types that can be used by this factory.
    /// </summary>
    private static void BuildCache()
    {
        foreach (var assembly in assembliesToCache)
        {
            MapDeviceTypesFromAssembly(assembly);
        }
    }

    /// <summary>
    /// Creates a DeviceType to IMessageTransformer Type mapping.
    /// </summary>
    /// <param name="assembly"></param>
    private static void MapDeviceTypesFromAssembly(Assembly assembly)
    {
        var transformableTypes = assembly.DefinedTypes
            .Where(type => type
            .ImplementedInterfaces
            .Any(inter => inter == typeof(IMessageTransformer)) && !type.IsAbstract);

        foreach (TypeInfo transformer in transformableTypes)
        {
            var commandCode = transformer.GetCustomAttribute<TransformableAttribute>();
            deviceTransformerMapCache.Add(commandCode.DeviceType, transformer.AsType());
        }
    }
}

      

Configuring Autofac

With this implementation, the responsibility for matching has now dropped to the factory. As a result, our Autofac registration becomes very easy.

var builder = new ContainerBuilder();

// Register all of the available transformers.
builder
    .RegisterTypes(MessageTransformFactory.GetAvailableTransformerTypes())
    .AsImplementedInterfaces()
    .AsSelf();

// Build the IoC container
this.container = builder.Build();

// Define our factory method for resolving the transformer based on device type.
MessageTransformFactory.SetTransformerFactory((type) =>
{
    if (!type.IsAssignableTo<IMessageTransformer>())
    {
        throw new InvalidOperationException("The type provided to the message transform factory resolver can not be cast to IMessageTransformer");
    }

    return container.Resolve(type) as IMessageTransformer;
});

      

After we have finished registering Autofac, we will then set up the MessageTransformFactory

delegate factory method to resolve the type set for using the factory.

This also works well for testing, as you can now customize the instantiation process that the factory uses during unit testing. You can create modified transformers and create a simple delegate that returns a mock by having a test callMessageTransformFactory.SetTransformFactory()

Finally, you mentioned that you prefer to introduce a factory in order to stay up to date with the conventions you already used. While this factory doesn't have any dependencies (so it can be programmed without the need for IoC), you can abstract the 1 instance method it has, method CreateTransformer<T>

, behind an interface. Then you register a factory with that interface for Autofac and inject it. So nothing has a concrete factory implementation. It also prevents someone from accessing the static methods that the factory should have in order to make it easier to display and connect with Autofac.

+2


source


From the sample you provided looks like this:

public IMessageTransformer<T> GetTransformer(DeviceTypeEnum device)
{
    return _create(device);
}

      

Instead, you can write:

// (Injected via Ctor.)
Func<IMessageTransformer<T>> _create;

public IMessageTransformer<T> GetTransformer(DeviceTypeEnum device)
{
    return _create();
}

      



Then you can register your transformers with RegisterAssemblyTypes().AsClosedTypesOf(typeof(IMessageTransformer<>))

.

Ie, it's not entirely clear where mapping from DeviceType

to message type comes into play ...

I would guess there is a good reason for the comparison, but the example doesn't show where it fits in. Can the mapping be done outside Autofac, in the usual ol 'method that calls Resolve()

on the container

0


source







All Articles