建设一个更好的工厂

时间:2012-10-11 17:16:13

标签: c# factory-pattern

所以我正在开发一个项目,我从数据库中获取一些数据 - 有两个数据使这个项目很好,一个我有类型(他们称之为事件,但它基本上只是转换为.NET我创建的类型然后我有XML,我设计了对象,所以他们只是反序列化很好。所有这些都很精彩,经过单元测试,所有课程和方法都遵循单一责任原则。

我的架构技能变得模糊的是我在创建工厂时构建业务逻辑来处理我从XML创建的.NET对象。

基本上这就是我所拥有的。

  public class EventProcessorFactory : IEventProcessorFactory
    {
        private readonly List<IEventProcessor> _eventProcessors;

        public EventProcessorFactory()
        {
            _eventProcessors = new List<IEventProcessor>();
        }

        public IEventProcessor GetProcessor(Type eventType)
        {
            var typeOfEventProcessor = GetProcessorFromEventType(eventType);
            if (_eventProcessors.Any(x => x.GetType() == typeOfEventProcessor))
                return _eventProcessors.Single(x => x.GetType() == typeOfEventProcessor);
            var processor = BuildProcessorFromType(typeOfEventProcessor);
            _eventProcessors.Add(processor);
            return processor;
        }

        private static Type GetProcessorFromEventType(Type eventType)
        {
            if (eventType == typeof(EnrollmentEventType))
                return typeof(EnrollmentEventProcessor);
            if (eventType == typeof(ClaimantAccountInfoEventType))
                return typeof(ClaimantAccountInfoEventProcessor);
            if (eventType == typeof(PhoneUpdateEventType))
                return typeof(PhoneUpdateEventProcessor);
            if (eventType == typeof(AddressUpdateEventType))
                return typeof(AddressUpdateEventProcessor);
            if (eventType == typeof(ClientAccountInfoEventType))
                return typeof(ClientAccountInfoEventProcessor);
            return null;
        }

        private IEventProcessor BuildProcessorFromType(Type typeOfEventProcessor)
        {
            return ((IEventProcessor)Activator.CreateInstance(typeOfEventProcessor));
        }
    }

这样可行,但看起来很笨重。我读过一些关于使用工厂的文章,但要么我没有读过正确的,要么我没有得到它。我上面的代码有两个问题。

1)如果你添加一个新事件你需要修改它,我会让后来的开发人员只能删除“MyCoolNewEventType”和“MyCoolNewEventProcessor”而不必修改匹配事件的方法到处理器。

2)我现在正在创建一个能够调用.CreateInstance();这很好,因为我没有任何依赖,但“事件处理器”可能至少在数据库上具有依赖性。我不是100%肯定如何处理,我不想随机调用Container.Resolve()。

如果有人能指出正确的方向,那将是巨大的。

4 个答案:

答案 0 :(得分:4)

您正在寻找的是可配置工厂。它可以通过依赖注入框架或最简单的方式完成 - 外部配置(xml文件浮现在脑海中),它将存储您可能的实现列表,并将按需加载/修改。

使用此解决方案,您将拥有类似

的内容
<Processors>
  <Processor dllname='' classname=''>
   ....
</Processors>

并且您必须使用反射/任何xml读取技术加载它.net为您提供。

答案 1 :(得分:3)

您可以使用reflection并在这些属性中设置处理器类型。

[ProcessorType(typeof(EnrollmentEventProcessor)]
class EnrollmentEvent { ... }

简单清洁的解决方案。对于创建的类型,我建议使用Dictionary<Type, Object>来加速访问。

答案 2 :(得分:1)

这取决于您希望存储映射的位置,但您可以编写自定义配置部分:

public class EventProcessorMapping : ConfigurationElement
{

    [ConfigurationProperty("event", IsRequired = true)]
    public string Event
    {
        get
        {
            return this["event"] as string;
        }
    }

    [ConfigurationProperty("processor", IsRequired = true)]
    public string Processor
    {
        get
        {
            return this["processor"] as string;
        }
    }
}

[ConfigurationCollection(typeof(EventProcessorMapping), CollectionType = ConfigurationElementCollectionType.AddRemoveClearMap)]
public class EventProcessors : ConfigurationElementCollection
{
    public EventProcessorMapping this[int index]
    {
        get
        {
            return BaseGet(index) as EventProcessorMapping;
        }
        set
        {
            if (BaseGet(index) != null)
            {
                BaseRemoveAt(index);
            }
            BaseAdd(index, value);
        }
    }

    protected override ConfigurationElement CreateNewElement()
    {
        return new EventProcessorMapping();
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((EventProcessorMapping)element).Event;
    }
}

public class RegisterEventProcessorsConfig : ConfigurationSection
{

    public static RegisterEventProcessorsConfig GetConfig()
    {
        return (RegisterEventProcessorsConfig)ConfigurationManager.GetSection("RegisterEventProcessors") ?? new RegisterEventProcessorsConfig();
    }

    [ConfigurationProperty("EventProcessors")]
    public EventProcessors EventProcessors
    {
        get
        {
            var o = this["EventProcessors"];
            return o as EventProcessors;
        }
    }

}

然后在您的App.config中,您可以:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="RegisterEventProcessors" type="UnitTestProject1.RegisterEventProcessorsConfig, UnitTestProject1"></section>
  </configSections>

  <RegisterEventProcessors>
    <EventProcessors>
      <add event="UnitTestProject1.ClaimantAccountInfoEventType, UnitTestProject1" processor="UnitTestProject1.ClaimantAccountInfoEventProcessor, UnitTestProject1" />
      <add event="UnitTestProject1.EnrollmentEventType, UnitTestProject1" processor="UnitTestProject1.EnrollmentEventProcessor, UnitTestProject1" />
    </EventProcessors>
  </RegisterEventProcessors>
</configuration>

所以,至少重新定位映射配置。至于工厂,如果你不介意在制造工厂时实例化处理器类,你可以这样做:

public class EventProcessorFactory : IEventProcessorFactory
{
    private readonly Dictionary<Type, IEventProcessor> _eventProcessors;

    public EventProcessorFactory(IEnumerable<EventProcessorMapping> eventProcessorMappings)
    {
        _eventProcessors = new Dictionary<Type, IEventProcessor>();
        foreach (var mapping in eventProcessorMappings)
        {
            AddMapping(Type.GetType(mapping.Event), Type.GetType(mapping.Processor));
        }
    }

    public IEventProcessor GetProcessor<T>() where T : IEventType
    {
        return _eventProcessors[typeof(T)];
    }

    private void AddMapping(Type eventType, Type processorType)
    {
        var processor = (IEventProcessor)Activator.CreateInstance(processorType);
        _eventProcessors[eventType] = processor;
    }
}

在构造函数中,我们传入映射配置元素的集合,然后创建处理器并存储在私有集合中。然后从工厂获得处理器基本上只是字典查找。

这两部分合在一起:

[TestMethod]
public void TestFactory()
{
    var config = RegisterEventProcessorsConfig.GetConfig();
    var factory = new EventProcessorFactory(config.EventProcessors.Cast<EventProcessorMapping>());

    var processor = factory.GetProcessor<EnrollmentEventType>();
    Assert.IsInstanceOfType(processor, typeof(EnrollmentEventProcessor));
}

答案 3 :(得分:1)

在这种情况下,我肯定会在DI中使用一个命名工厂,但我没有Unity的经验(我自己就是Autofac人)。但我有点惊讶没有人提出命名约定方法:让EventType的名称决定使用什么处理器。这将不需要额外的代码,因为只要处理器的名称遵循约定,就会添加新的事件类型和处理器。

private static Type GetProcessorFromEventType(Type eventType)
{
    // Assuming phrase "Type" only is present at the end of the EventType name
    var processorName = eventType.Name.Replace("Type", "Processor");
    var processorType = Type.GetType(processorName);
    return processorType;
}

如果类型和处理器位于不同的程序集中,获取Type.GetType方法从名称解析类型变得更加困难,这是我使用DI的主要原因。