使用 属性 使用 Autofac 创建委托工厂

Creating a Delegating Factory with Autofac using a property

我正在尝试创建一个工厂来帮助将基于接口 (IIncomingMessage) 的 class 转换为其他基于 classes (AMessage, BMessage) 的新实例单曲class的18=],喜欢:

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);
    } 
}

我正在使用 Autofac,我很确定应该有一种方法可以创建一个工厂,它可以根据 DeviceType 的 属性 值返回正确的变压器。使用并看到了大量使用对象类型而不是值来做出决定的示例。 Named and Keyed Services 看起来很有前途,但看起来我仍然在某处得到一个静态列表。

根据您提供的示例,它看起来像:

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

可以改为:

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

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

然后您可以使用 RegisterAssemblyTypes().AsClosedTypesOf(typeof(IMessageTransformer<>)).

注册您的转换器

即,从 DeviceType 到消息类型的映射在哪里发挥作用并不是很清楚...

我想映射有一个很好的理由,但这个例子并没有真正显示它适合的位置。映射可以在 Autofac 之外,在调用 [=14= 的常规 ol' 方法中完成吗] 在容器上?

好吧,在与您交谈后,听起来您对转变的实际发生方式的一些调整持开放态度。因此,我为您构建了一个解决方案,它使用 DeviceTypeEnum 旁边的属性来解析您的转换器。工厂大致遵循抽象工厂模式。工厂负责确定需要创建的实际类型是什么;然后它将该类型的实例化委托给委托。在这种情况下,Autofac.

概述

为了让它工作,我不得不添加一个额外的界面。 IMessageTransformer。然后我让 IMessageTransformer<T> 从它继承。基本接口只不过是一个标记接口,这样我就可以使工厂本身成为非通用的,连同 Func 委托成为非通用的。您现在可以对多个 IMessageTransformer<T> 请求使用同一个工厂实例。您也可以根据需要只实例化工厂,而不是注入它,因为它没有任何依赖关系。

为了展示工厂实际工作,我已经为它写了一个单元测试。

    [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");
    }

请注意,本可以 简化,因为引入了一个属性,将 DeviceTypeEnum 一起删除。但是我把它留在原地,因为我不确定你的总要求是什么。

实施

现在回顾一下实际的实施。我在原始问题中获取了您的示例代码,并用它构建了一个示例项目。包含实现的示例项目是 available on GitHub 供您下拉。所有源代码如下所示; GitHub 存储库只是为了让您可以下载存储库并查看运行中的代码,确保它执行您想要的操作,然后再将其实现到您的项目中。

设备类型枚举

我做的第一件事是创建 enum,它将用于 linking 变压器及其 IIncomingMessageMessage 类。

public enum DeviceTypeEnum
{
    Foo,
    Bar,
}

留言类

接下来,我创建了两条消息 类,您要将其 转换为

public class AMessage
{
}

public class BMessage
{
}

IIncomingMessage

接下来是IIncomingMessage界面。该接口只包含属性、DeviceType。然后我创建了这个接口的两个实现,以便我可以测试实际的转换。

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> 已被修改为继承自接口的非通用变体。

public interface IMessageTransformer
{
}

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

这允许您指定 Autofac 在解析期间使用的委托方法,而不是通用委托。意思是您现在使用 Func<IMessageTransformer> 而不是 Func<IMessageTransformer<T>>。这使您可以重复使用同一个工厂实例,因为该实例未绑定到特定的通用类型。

TransformableAttribute

现在我们需要创建一个属性。此属性将用于告诉每个转换器,他们需要支持什么DeviceTypeEnum

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

    public DeviceTypeEnum DeviceType { get; private set; }
}

如果DeviceTypeEnum只是为了方便这个转换映射,那么你真的可以把枚举一起删除。您可以将 DeviceType 属性 更改为 public Type TargetType {get; private set;}。然后工厂(如下所示)将使用 TargetType 解析(简化我创建的工厂)。由于您的示例显示正在使用此枚举,因此我将其保留为要求。

请注意,删除枚举并使用 TargetType 属性 还可以让您构建更复杂的关系,并且不必在每次必须创建新映射时都更新枚举。而不是做:

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

它会让你把参数全部丢掉。不需要枚举,因为属性会让工厂知道每个转换器的目标转换结果需要是什么。

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

消息转换器

我们有属性和接口,所以我们可以继续创建几个转换器,它们将从 IIncomingMessage 转换为 AMessageBMessageTransformableAttribute 将告诉转换器支持哪种设备类型。转换器本身不依赖于属性;我们将用于解析的工厂,将需要 link 设备类型到变压器的属性。

[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

现在是肉和土豆。工厂负责一些事情。它必须首先扫描一组程序集,以便找到它可以创建的所有转换器 Type。它还需要接受一个委托工厂,它将用于实例化它在需要时缓存的转换器Type

MessageTransformFactory 松散地遵循抽象工厂模式。它将转换对象的实际创建委托给其他东西,在本例中是 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());
        }
    }
}

Autofac 设置

有了这个实现,映射的责任现在就落在了工厂身上。这样一来,我们的Autofac注册就变得非常简单了。

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;
});

完成 Autofac 注册后,我们将设置 MessageTransformFactory 的委托工厂方法来解析工厂使用的类型。

这也适用于测试,因为您现在可以自定义工厂在单元测试期间使用的实例化过程。您可以通过让测试调用 MessageTransformFactory.SetTransformFactory()

创建模拟转换器并构建一个 returns 模拟的简单委托

最后,您提到您更喜欢注入工厂以遵循您已经使用的约定。虽然这个工厂没有任何依赖关系(因此可以在不需要任何 IoC 的情况下进行实例化),但您可以抽象它具有的第一个实例方法,即 CreateTransformer<T> 方法,位于接口后面。然后,您使用该接口向 Autofac 注册工厂并注入它。这样,没有什么是工厂的具体实现。这也阻止了其他人访问工厂必须具有的静态方法,以便于与 Autofac 进行映射和耦合。