如何创建返回不同接口类型和抽象类的通用工厂

时间:2019-06-05 15:02:39

标签: c#

我有一个抽象基类,其中包含大量共享代码和配置属性。我将许多共享代码拆分为逻辑接口,这些逻辑接口也由基类实现。 每个客户都有一系列基本类的实现。

我目前为每个接口都有一家工厂。每个工厂都有相同的switch语句。我想创建一个通用工厂,该工厂将根据类的声明方式返回不同的功能子集。

我的基类:

public abstract class BaseParser : IDatabaseCreation, IBulkImport, IAnalyticFeature
// ...a number of configuration fields, methods and abstract methods

客户类别:

class AParser : BaseParser
{
    private int _componentIndicatorColumn;        

    public AParser(ILogger log) : base (log) {
// ...configuration values and abstract method implementations

当前基地工厂:

class BaseFactory
{
    public BaseParser CreateParser(string key, ILogger log)
    {
        switch (key)
        {
            case "A":
                return new AParser(log);
            case "B":
                return new BParser(log);
            case "C":
                return new CParser(log);
            default:
                throw new NotImplementedException("Not Recognized or Not Registered in Factory");
        }
    }
}

示例接口工厂:

class BulkImportFactory
{
    public IBulkImport CreateDatabaseCreationObject(string key, ILogger log)
    {
        switch (key)
        {
            case "A":
                return new AParser(log);
            case "B":
                return new BParser(log);
            case "C":
                return new CParser(log);
            default:
                throw new NotImplementedException("Not Recognized or Not Registered in Factory");
        }
    }
}

这是我尝试无法正常工作的GenericFactory:

public class GenericFactory<T>
{
    public T CreateVariableInterfaceObject<T>(string key, ILogger log)  where T: BaseParser
    {
        switch (key)
        {
            case "A":               
                return new AParser(log);
            case "B":
                return new BParser(log);
            case "C":
                return new CParser(log);
            default:
                throw new NotImplementedException("Not Recognized or Not Registered in GenericFactory");
        }
    }
}

如您所见,工厂中的逻辑是相同且重复的。但是我无法使通用解析器正常工作。不知道我缺少什么语法。

我想做的就是让所有这些都成为一个工厂:

ParserFactory parserFactory = new ParserFactory();
BaseParser parser = parserFactory.CreateParser(queueMessage.key,log);
BulkImportFactory bulkImportFactory = new BulkImportFactory();
IBulkImport bulkImporter = bulkImportFactory.CreateDatabaseCreationObject(key, log);
AnalyticFeatureFactory parserFactory = new AnalyticFeatureFactory();
IAnalyticFeature parser = parserFactory.CreateAnalyticFeatureObject(key, log);

3 个答案:

答案 0 :(得分:1)

这样适合您的需求吗?

sealed class GenericFactory<TKey, TOption, TObject>
{
    readonly IReadOnlyDictionary<TKey, Func<TKey, TOption, TObject>> _factories;

    public GenericFactory(
        IReadOnlyDictionary<TKey, Func<TKey, TOption, TObject>> factories)
    {
        _factories = factories;
    }

    public bool TryCreate(TKey key, TOption option, out TObject @object)
    {
        @object = default;
        if (!_factories.TryGetValue(key, out var factory))
            return false; // Cannot create; unknown key
        @object = factory(key, option);
        return true;
    }
}

static class GenericFactoryExtensions
{
    public static TObject CreateOrFail<TKey, TOption, TObject>(
        this GenericFactory<TKey, TOption, TObject> factory,
        TKey key,
        TOption option)
    {
        if (!factory.TryCreate(key, option, out var @object))
            throw new NotImplementedException($"Not Recognized or Not Registered in {nameof(GenericFactory<TKey, TOption, TObject>)}");
        return @object;
    }
}

void SimpleUseFactory()
{
    var baseParserFactory = new GenericFactory<string, ILogger, BaseParser>(new Dictionary<string, Func<string, ILogger, BaseParser>>
        {
            ["A"] = (key, logger) => new AParser(logger),
            ["B"] = (key, logger) => new BParser(logger)
        });

    var parser = baseParserFactory.CreateOrFail("A", logger);
    parser.DoStuff();
}

class Factories
{
    public Func<string, ILogger, BaseParser> BaseParserFactory { get; }
    public Func<string, ILogger, IBulkImport> BulkImportFactory { get; }
    public Func<string, ILogger, SomethingElse> SomethingElseFactory { get; }

    public Factories(
        Func<string, ILogger, BaseParser> baseParserFactory,
        Func<string, ILogger, IBulkImport> bulkImportFactory,
        Func<string, ILogger, SomethingElse> somethingElseFactory)
    {
        BaseParserFactory = baseParserFactory;
        BulkImportFactory = bulkImportFactory;
        SomethingElseFactory = somethingElseFactory;
    }
}

void ComplexUseFactory()
{
    var mappedFactories = new Dictionary<string, Factories>
    {
        ["A"] = new Factories(
            baseParserFactory: (key, logger) => new AParser(logger),
            bulkImportFactory: (key, logger) => new ABulkImport(logger),
            somethingElseFactory: (key, logger) => new ASomethingElse(logger)),
        ["B"] = new Factories(
            baseParserFactory: (key, logger) => new BParser(logger),
            bulkImportFactory: (key, logger) => new BBulkImport(logger),
            somethingElseFactory: (key, logger) => new BSomethingElse(logger))
    };

    var baseParserFactory = new GenericFactory<string, ILogger, BaseParser>(
        mappedFactories.ToDictionary(
            keySelector: kvp => kvp.Key,
            elementSelector: kvp => kvp.Value.BaseParserFactory));
    var bulkImportFactory = new GenericFactory<string, ILogger, IBulkImport>(
        mappedFactories.ToDictionary(
            keySelector: kvp => kvp.Key,
            elementSelector: kvp => kvp.Value.BulkImportFactory));
    var somethingElseFactory = new GenericFactory<string, ILogger, SomethingElse>(
        mappedFactories.ToDictionary(
            keySelector: kvp => kvp.Key,
            elementSelector: kvp => kvp.Value.SomethingElseFactory));

    var parser = baseParserFactory.CreateOrFail("A", logger);
    parser.DoStuff();
}

对于“复杂”用例演示:

Factories类强制执行以下操作:当“ A”有一个BaseParser时,还会有一个IBulkImport和一个SomethingElse。如果需要编译时保证也可以为所有情况创建一个YetAnotherThing,则只需将其添加为Factories类的必需属性,然后根据模式。

要为“ C”添加功能时,只需在GenericFactory词典中添加另一个条目即可。

请注意,mappedFactories可以被实例化,然后在不同的模块之间抛弃,以便在创建{{1之前,用所有必要的“ A”,“ B”,“ C”等情况填充它} s。或者,不是让模块接受mappedFactories对象,而是每个模块都可以实现仅生成一个GenericFactory实例的接口,并且可以收集“ A”,“ B”等键来自模块元数据。这样,您可以保证“ B”模块不会与“ A”模块的工厂混淆。

这可以进一步抽象吗?我是这样认为的,但是我怀疑没有编译时保证,当您可以创建Dictionary<string, Factories>时也可以创建Factories

对于这两种情况:

可能需要帮助您为需要BaseParser的语句(根据定义,不是为扩展而打开/为修改而闭合,也称为“打开/闭合”原理)发展出来的嗅觉,需要对其进行扩展功能。解决方法通常是使用字典。无休止的IBulkImport语句也是如此。

请注意,switchif,并且缺少GenericFactory关键字。那是故意的。该工厂的消费者应由该工厂组成,而不是从其继承。就像sealed方法组成工厂的实例,而不是从工厂继承的事物的实例一样。这是另一个起作用的原则:赞成继承而不是继承。

您还将注意到abstract实际上是由其他工厂组成的工厂,它委托其他工厂(注入的字典中的每个UseFactory本身就是工厂)。如果您确实需要它,那么这会通知我您可能没有使用IoC容器,因为IoC容器通常提供了这种构成工厂的机制,而您不必使用它。在这种情况下,可能会帮助您研究IoC容器。


编辑:You,我都提到了有关IoC的内容。

如果我有IoC,我会尽力去做以下情况,这样我什至不需要GenericFactory

(我很抱歉为无法为任何已知的IoC容器提供即开即用的伪代码)

ModuleA.cs

Func

ModuleB.cs

GenericFactory

CommonThing.cs

Register<AParser>().As<BaseParser>();
Register<ABulkImport>().As<IBulkImport>();

单一成分根

Register<BParser>().As<BaseParser>();
Register<BBulkImport>().As<IBulkImport>();

Application.cs

public class CommonThing
{
    readonly BaseParser _parser;
    readonly IBulkImport _bulkImport;

    public CommonThing(
        BaseParser parser,
        IBulkImport bulkImport)
    {
        _parser = parser;
        _bulkImport = bulkImport;
    }

    public void DoFancyStuff(string data)
    {
        var parsed = _parser.Parse(data);
        _bulkImport.Import(parsed);
    }
}

Program.cs (或您选择的入口点)

switch (module)
{
    case "A":
        RegisterModule<ModuleA>();
        break;
    case "B":
        RegisterModule<ModuleB>();
        break;
    default:
        throw new NotImplementedException($"Unexpected module {module}");
}
Register<CommonThing>();
Register<Application>();

注意:单一成分根通常不必遵循“打开/关闭”的原则。但是,如果您希望其中的public class Application { readonly CommonThing _commonThing; public Application( CommonThing commonThing) { _commonThing = commonThing; } public void Run() { var json = "{\"key\":\"value\"}"; _commonThing.DoFancyStuff(json); } } 语句消失,那么可以朝着这种设计前进:

ModuleNameAttribute.cs

var containerBuilder = new IoCBuilder();
containerBuilder.RegisterModule<SingleCompositionRoot.cs>();
using (var container = containerBuilder.Build())
    container.Resolve<Application>().Run();

ModuleA.cs

switch

ModuleB.cs

public class ModuleNameAttribute : Attribute
{
    public string Name { get; }
    ...
}

单一成分根

[ModuleName("A")]
public class ModuleA
{
    ...
}

请注意,使用依赖注入进行所有操作的好处之一(意味着像上面的[ModuleName("B")] public class ModuleB { ... } 那样注册/解析应用程序本身)是缺少注册会导致很早运行时异常。这通常消除了对所有正确的东西都放在正确位置的某种编译时保证的需要。

例如,如果将var moduleType = GetAllTypesInAppDomain() .Select(type => (type, type.GetCustomAttribute<ModuleNameAttribute>())) .Where(tuple => tuple.Item2 != null) .Where(tuple => tuple.Item2.Name == module) .FirstOrDefault(); if (moduleType == null) throw new NotImplementedException($"No module has a {nameof(ModuleNameAttribute)} matching the requested module {module}"); RegisterModule(moduleType); ... 定义为“ C”,则在“应用程序启动”时将抛出“单一组合根”中的Program.cs。或者,如果模块C确实存在但未能注册module的实现,则IoC容器将在尝试在应用程序启动时尝试为NotImplementedException解析IBulkImport时抛出运行时异常。因此,如果应用程序启动,那么您就会知道所有依赖性都已解决或可以解决。

答案 1 :(得分:0)

让我看看我是否可以澄清您要做什么。

您有一组解析器(AParser,BParser,CParser),它们具有BaseParser某些功能的特定实现。您要尝试的是在运行时为AParserBParser等特定实例提供一些特殊功能。假设您想要一个AParser,但是默认的AParser实现不支持ParseFoo(),但是在运行时您想赋予它ParseFoo()的功能而无需提前定义?

如果是这种情况,那么我认为您将不得不与工厂一起考虑使用Decorator Design Pattern。使用Decorator,您将在自己的类中实现新功能的功能,然后工厂将返回装饰器,该装饰器仅包装BaseParser具体类。

答案 2 :(得分:0)

似乎您的代码违反了Solid Principles,例如Single Purpose Class。如果您想解决此问题,而不是实现每个接口的基类,则它们可能包含每个属性,例如解析器,批量导入器,分析 但是,如果您想使用当前架构轻松解决此问题,则可以执行以下操作

您已经定义了基本解析器实现3个接口。从同一工厂获取每个接口

//Note: you probably want to change the name of your factory
ParserFactory parserFactory = new ParserFactory();
BaseParser parser = parserFactory.CreateParser(queueMessage.key,log);
IBulkImport bulkImporter =  parserFactory.CreateParser(queueMessage.key,log);

如果由于某种原因您需要添加不是解析器的其他批量导入器,则只需制作一个类即可封装逻辑,例如

在其他工厂旁边,您可以拥有一个基础工厂,您要求该工厂首先解析接口,例如

//Note you need to change the code so it doesnt throw in the default
IBreakSolidPrinciplesFactory principlesFactory = new BreakSolidPrinciplesFactory();

var multipurposeClass = principlesFactory.GetImplentation(key, log);
if(multipurposeClass != null)
{
   reutrn multipurposeClass;
}
switch (key)
{
    case "bulkImporter":
        return new BulkImporterOnly(log);
    default:
        throw new NotImplementedException("Not Recognized or Not Registered in Factory");
}