好的,所以在与您交谈之后,您似乎很愿意对转换的实际发生方式进行一些调整。因此,我为您构建了一个解决方案,它使用属性和DeviceTypeEnum 来解析您的转换器。工厂松散地遵循抽象工厂模式。工厂负责确定需要创建的实际 Type 是什么;然后它将该类型的实例化委托给委托人。在这种情况下,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,它将用于将转换器与其IIncomingMessage 和Message 类链接起来。
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>>。这使您可以重复使用相同的工厂实例,因为该实例未绑定到特定的泛型类型。
可转换属性
现在我们需要创建一个属性。该属性将用于告诉每个转换器,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 转换为AMessage 或BMessage。 TransformableAttribute 将告诉变压器支持哪种设备类型。变压器本身不依赖属性;我们将用于解析的工厂需要将设备类型链接到转换器的属性。
[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
现在是肉和土豆。工厂有一些事情要负责。它必须首先扫描一组程序集,才能找到它可以创建的所有转换器Types。它还需要接受一个委托工厂,它将在需要时用于实例化它已缓存的转换器Types。
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()MessageTransformFactory.SetTransformFactory()
来创建模拟转换器并构建一个返回模拟的简单委托
最后,您提到您更喜欢注入工厂以保持您已经使用的约定。虽然这个工厂没有任何依赖关系(因此可以在不需要任何 IoC 的情况下进行实例化),但您可以在接口后面抽象它拥有的 1 个实例方法,CreateTransformer<T> 方法。然后你用那个接口注册工厂到 Autofac 并注入它。这样一来,什么都没有工厂的具体实现。这也可以防止其他人访问工厂必须具有的静态方法,以促进与 Autofac 的映射和耦合。