简体   繁体   English

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

[英]How to create a generic factory returning different interface types and an abstract class

I have an abstract base class which contains a good deal of shared code and configuration properties. 我有一个抽象基类,其中包含大量共享代码和配置属性。 Much of the shared code I've split up into logical interfaces which are also implemented by the base class. 我将许多共享代码拆分为逻辑接口,这些逻辑接口也由基类实现。 There are a series of implementations of the base class for each customer. 每个客户都有一系列基本类的实现。

I currently have a factory for each of the interfaces. 我目前为每个接口都有一家工厂。 Each factory has identical switch statements. 每个工厂都有相同的switch语句。 I would like to create a generic factory that will return a different subset of functionality based on how the class is declared. 我想创建一个通用工厂,该工厂将根据类的声明方式返回不同的功能子集。

My base class: 我的基类:

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

Customer class: 客户类别:

class AParser : BaseParser
{
    private int _componentIndicatorColumn;        

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

Current Base Factory: 当前基地工厂:

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

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

This is my attempt at a GenericFactory that isn't working: 这是我尝试无法正常工作的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");
        }
    }
}

As you can see the logic in the factories is identical and repeated. 如您所见,工厂中的逻辑是相同且重复的。 However I can't get a generic parser to work. 但是我无法使通用解析器正常工作。 Not sure what syntax I'm missing. 不知道我缺少什么语法。

What I'd like to do is allow all of these to be one factory: 我想做的就是让所有这些都成为一个工厂:

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

Does something like this suit your needs? 这样的东西适合您的需求吗?

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

For the "complex" use case demonstrated: 对于演示的“复杂”用例:

The Factories class is what enforces that when there's a BaseParser for "A" then there's also a IBulkImport and a SomethingElse . Factories类是什么强制执行时,有一个BaseParser为“A”,那么也有是一个IBulkImportSomethingElse When you want the compile-time guarantee that you can also create a YetAnotherThing for all cases then just add that as a required property of the Factories class and create a new GenericFactory according to the pattern. 如果需要编译时保证,还可以为所有情况创建一个YetAnotherThing ,则只需将其添加为Factories类的必需属性,然后根据该模式创建一个新的GenericFactory

When you want to add functionality for "C" then all you have to do is add another entry in the mappedFactories dictionary. 当您想为“ C”添加功能时,您要做的就是在mappedFactories字典中添加另一个条目。

Note that the mappedFactories could be instantiated and then tossed around between different modules in order to populate it with all the necessary "A", "B", "C", etc cases before creating the GenericFactory s. 注意, mappedFactories可以被实例化,然后才能与所有必要的“A”,“B”来填充它不同模块之间就四处,“C”等创建之前案件GenericFactory秒。 Or instead of making the modules accept a Dictionary<string, Factories> object, maybe each module could have an implementation of an interface that generates just one Factories instance and you could gather the "A", "B", etc keys from module metadata. 或者不是让模块接受Dictionary<string, Factories>对象,而是每个模块都可以实现只生成一个Factories实例的接口的实现,并且您可以从模块元数据中收集“ A”,“ B”等键。 That way you could guarantee that the "B" module doesn't mess with "A" module's factories. 这样,您可以保证“ B”模块不会与“ A”模块的工厂混淆。

Can this be abstracted further? 可以进一步抽象吗? I think so, but I suspect it would come without the compile-time guarantee that when you can create a BaseParser then you can also create a IBulkImport . 我认为是这样,但是我怀疑它没有编译时保证,即当您可以创建BaseParser时也可以创建IBulkImport

For both cases: 对于这两种情况:

You might be helped to develop a sense of smell for switch statements (that by definition are not Open for extension/Closed for modification, also known as the Open/Closed principle) which need to be modified to extend functionality. 可能需要帮助您为switch语句(根据定义,不是为扩展而打开/为修改而闭合,也称为“打开/闭合”原理)而开发的,需要对其进行扩展以扩展功能。 Composing with dictionaries is often the solution. 解决方法通常是使用字典。 Same for endless if statements. 对于无尽的if语句也是如此。

Notice that the GenericFactory is sealed and missing the abstract keyword. 请注意, GenericFactorysealed并且缺少abstract关键字。 That's intentional. 那是故意的。 Consumers of this factory should be composed of this factory instead of inheriting from it. 该工厂的消费者应由该工厂组成,而不是从其继承。 Just like the UseFactory method composes instances of the factory instead of instances of things that inherit from it. 就像UseFactory方法组成工厂实例而不是从工厂继承的事物实例一样。 That's another principle at play: favor composition over inheritance. 那是起作用的另一条原则:赞成继承而不是继承。

You'll also notice that the GenericFactory is really a factory that is composed of other factories—it delegates to other factories (each Func in the injected dictionary is itself a factory). 您还会注意到GenericFactory实际上是由其他工厂组成的工厂,它委托给其他工厂(注入的字典中的每个Func本身都是工厂)。 If you truly need this then that signals to me that you probably aren't using an IoC container, because IoC containers typically give this mechanism of composing factories without you having to use this. 如果您确实需要它,那么这会通知我您可能没有使用IoC容器,因为IoC容器通常提供了这种构成工厂的机制,而您不必使用它。 In that case you might be helped to investigate IoC containers. 在这种情况下,可能会帮助您研究IoC容器。


Edit: You and I both mentioned something about IoC. 编辑: 和我都提到了有关IoC的内容。

If I had IoC, I would try really hard to get to the following scenario so that I wouldn't even need GenericFactory . 如果我有IoC,我将非常努力地尝试以下情形,以便甚至不需要GenericFactory

(My apologies in advance for making up pseudocode that doesn't work out of the box for any known IoC container) (我很抱歉要为任何已知的IoC容器开箱即用地创建伪代码)

ModuleA.cs ModuleA.cs

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

ModuleB.cs ModuleB.cs

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

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

Single Composition Root 单一成分根

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

Application.cs 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 (or entry point of your choice) Program.cs (或您选择的入口点)

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

Note: the Single Composition Root often doesn't have to obey Open/Closed. 注意:“单一成分根”通常不必遵循“打开/关闭”的原则。 But if you'd like the switch statement there to go away then one could move toward this kind of design: 但是,如果您希望在那里的switch语句消失,那么可以朝着这种设计前进:

ModuleNameAttribute.cs ModuleNameAttribute.cs

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

ModuleA.cs ModuleA.cs

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

ModuleB.cs ModuleB.cs

[ModuleName("B")]
public class ModuleB
{
    ...
}

Single Composition Root 单一成分根

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

...

Note that one of the benefits of going all the way with Dependency Injection (meaning register/resolve the application itself like Program.cs does above) is that missing registrations cause very early runtime exceptions. 请注意,使用依赖注入一直进行的好处之一(意味着像上面的Program.cs一样注册/解析应用程序本身)是缺少注册会导致非常早的运行时异常。 This often eliminates the need for some kind of compile-time guarantee that all the correct things are in the correct places. 这通常消除了对所有正确的东西都放在正确位置的某种编译时保证的需要。

For example, if module were defined as "C" then that NotImplementedException in "Single Composition Root" would be thrown at application launch. 例如,如果module定义为“ C”,则在“应用程序启动”时将NotImplementedException “单一组合根”中的NotImplementedException Or if module C does exist but fails to register an implementation of IBulkImport then the IoC container would throw a runtime exception while trying to resolve CommonThing for Application , again at application launch. 或者,如果模块C确实存在但未能注册IBulkImport的实现,则IoC容器将在尝试在应用程序启动时再次尝试解析Application CommonThing时抛出运行时异常。 So if the application launches then you know that all dependencies either were resolved or can be resolved. 因此,如果应用程序启动,那么您就会知道所有依赖性都已解决或可以解决。

Let me see if I can clarify what you are trying to do. 让我看看是否可以澄清您要做什么。

You have a set of parsers (AParser, BParser, CParser) that have specific implementations of some of the functions of BaseParser . 你有一组具有的一些功能的具体实现解析器(AParser,BParser,CParser)的BaseParser What you are trying to do is to give specific instances of AParser , BParser etc some special feature at runtime. 您想要做的是在运行时为AParserBParser等特定实例提供一些特殊功能。 So say that you want an AParser but the default AParser implementation doesn't support ParseFoo() but at runtime you want to give it ParseFoo() ability without defining this ahead of time? 假设您想要一个AParser但是默认的AParser实现不支持ParseFoo()但是在运行时您想赋予它ParseFoo()功能而无需提前定义呢?

If this is the case then I think you are going to have to consider a Decorator Design Pattern possibly along with your factory. 如果是这种情况,那么我认为您可能需要与工厂一起考虑装饰器设计模式 With the Decorator, you will implement the functionality of the new features in their own classes and then your factory will return the decorator which simply wraps the BaseParser concrete class. 使用Decorator,您将在自己的类中实现新功能的功能,然后工厂将返回装饰器,该装饰器仅包装了BaseParser具体类。

Seems like your code is breaking the Solid Principles, eg Single purpose class. 似乎您的代码违反了Solid Principles,例如Single Purpose类。 If you want to fix this instead of your base class implementing each interface they could contain a property for each, eg parser, bulk importer, analytics But if you want to solve this an easy way with your current architecture you can do the following 如果要解决此问题,而不是实现每个接口的基类,则可以为每个接口包含一个属性,例如,解析器,批量导入器,分析。但是,如果要使用当前体系结构解决此简单方法,可以执行以下操作

You've defined that a base parser implements 3 interfaces. 您已经定义了基本解析器实现3个接口。 Obtain each interface from the same factory 从同一工厂获取每个接口

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

If for some reason you need to add additional bulk importer that are not parsers then just make a single class which encapsulates the logic eg 如果由于某种原因您需要添加不是解析器的其他批量导入器,则只需制作一个封装了逻辑的类,例如

in side your other factories you can have a base factory which you ask to resolve the interfaces first eg 在其他工厂旁边,您可以有一个基础工厂,您要求该工厂首先解析接口,例如

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM