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

罗文

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

我目前为每个接口都有一个工厂。每个工厂都有相同的 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);
马特·托马斯

像这样的东西是否适合您的需求?

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类是什么强制执行,有一个当BaseParser为“A”,那么还有一个IBulkImportSomethingElse当您想要编译时保证您也可以YetAnotherThing为所有情况创建一个时,只需将其添加为类的必需属性FactoriesGenericFactory根据模式创建一个新的。

当您想为“C”添加功能时,您所要做的就是在mappedFactories字典中添加另一个条目

请注意,mappedFactories在创建GenericFactorys之前,可以实例化 ,然后在不同的模块之间折腾,以便用所有必要的“A”、“B”、“C”等情况填充它或者不是让模块接受一个Dictionary<string, Factories>对象,也许每个模块都可以有一个接口的实现,它只生成一个Factories实例,你可以从模块元数据中收集“A”、“B”等键。这样你就可以保证“B”模块不会与“A”模块的工厂混淆。

这可以进一步抽象吗?我认为是这样,但我怀疑它会在没有编译时保证的情况下出现,即当您可以创建一个时,您也BaseParser可以创建一个IBulkImport.

对于这两种情况:

您可能会有助于培养switch对需要修改以扩展功能的语句的嗅觉(根据定义,这些语句不是对扩展开放/对修改封闭,也称为开放/封闭原则)。用字典编写通常是解决方案。无休止的if语句也是如此。

请注意GenericFactoryissealed和缺少abstract关键字。那是故意的。这个工厂的消费者应该由这个工厂组成,而不是从它继承。就像UseFactory方法组合工厂的实例而不是从它继承的事物的实例一样。这是另一个在起作用的原则:支持组合而不是继承。

您还会注意到,GenericFactory实际上是一个由其他工厂组成的工厂——它委托给其他工厂(Func注入字典中的每个工厂本身就是一个工厂)。如果您真的需要这个,那么这向我表明您可能没有使用 IoC 容器,因为 IoC 容器通常会提供这种组合工厂的机制,而您不必使用它。在这种情况下,您可能会被帮助调查 IoC 容器。


编辑:和我都提到了一些关于 IoC 的事情。

如果我有 IoC,我会非常努力地达到以下场景,这样我就不需要GenericFactory.

(我提前为编造伪代码而道歉,这些伪代码不适用于任何已知的 IoC 容器)

模块A.cs

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

模块B.cs

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

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

单一组合根

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

应用程序.cs

public class Application
{
    readonly CommonThing _commonThing;

    public Application(
        CommonThing commonThing)
    {
        _commonThing = commonThing;
    }

    public void Run()
    {
        var json = "{\"key\":\"value\"}";
        _commonThing.DoFancyStuff(json);
    }
}

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

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

注意:Single Composition Root 通常不必遵守 Open/Closed。但是,如果您希望switch那里声明消失,那么可以转向这种设计:

模块名称属性.cs

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

模块A.cs

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

模块B.cs

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

...

请注意,一直使用依赖注入(意味着像Program.cs上面那样注册/解析应用程序本身的好处之一是缺少注册会导致非常早的运行时异常。这通常消除了对某种编译时保证所有正确内容都在正确位置的需要。

例如,如果module定义为“C”,那么NotImplementedException在应用程序启动时将抛出“Single Composition Root”中的那个或者,如果模块 C 确实存在但未能注册 的实现,IBulkImport则 IoC 容器将在尝试解析CommonThingfor时抛出运行时异常Application,再次在应用程序启动时。因此,如果应用程序启动,那么您就知道所有依赖项要么已解决,要么可以解决。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

抽象类实现通用接口类型

来自分类Dev

工厂方法如何返回接口和抽象类的实例?

来自分类Dev

如何创建一个工厂,该工厂根据Java中的接口类型创建类?

来自分类Dev

如何使用工厂构造函数扩展抽象类?

来自分类Dev

与通用工厂的接口

来自分类Dev

抽象类和接口

来自分类Dev

如何使用静态工厂方法创建抽象类?

来自分类Dev

如何编写指定创建逻辑的接口或抽象类?

来自分类Dev

如何编写指定创建逻辑的接口或抽象类?

来自分类Dev

接口,抽象类和抽象类的方法

来自分类Dev

如何在抽象类和接口之间进行选择

来自分类Dev

从通用抽象类java返回子类

来自分类Dev

接口、抽象类和继承子类具有相同的方法,获得不同的类型参数,使用哪个?

来自分类Dev

组合的类和接口类型

来自分类Dev

如何返回抽象类型?

来自分类Dev

如何指定抽象类方法的返回类型

来自分类Dev

如何指定抽象类方法的返回类型

来自分类Dev

如何创建使用guice工厂辅助注入创建并从抽象类扩展的类的实例?

来自分类Dev

比较开放通用参数和开放通用接口类型

来自分类Dev

在抽象类和接口之间选择

来自分类Dev

Java-抽象类和接口

来自分类Dev

接口,抽象类和实现

来自分类Dev

使用接口和抽象类

来自分类Dev

通用抽象类

来自分类Dev

与抽象类的接口

来自分类Dev

无法创建类型X的实例。类型是接口或抽象类,无法实例化

来自分类Dev

Typescript-根据参数返回通用接口类型

来自分类Dev

如何获取工厂方法的类属性的接口类型

来自分类Dev

具有返回不同类型子项的抽象类中的C#抽象方法