[英]How to create a generic factory returning different interface types and an abstract class
我有一个抽象基类,其中包含大量共享代码和配置属性。 我将许多共享代码拆分为逻辑接口,这些逻辑接口也由基类实现。 每个客户都有一系列基本类的实现。
我目前为每个接口都有一家工厂。 每个工厂都有相同的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”,那么也有是一个IBulkImport
和SomethingElse
。 如果需要编译时保证,还可以为所有情况创建一个YetAnotherThing
,则只需将其添加为Factories
类的必需属性,然后根据该模式创建一个新的GenericFactory
。
当您想为“ C”添加功能时,您要做的就是在mappedFactories
字典中添加另一个条目。
注意, mappedFactories
可以被实例化,然后才能与所有必要的“A”,“B”来填充它不同模块之间就四处,“C”等创建之前案件GenericFactory
秒。 或者不是让模块接受Dictionary<string, Factories>
对象,而是每个模块都可以实现只生成一个Factories
实例的接口的实现,并且您可以从模块元数据中收集“ A”,“ B”等键。 这样,您可以保证“ B”模块不会与“ A”模块的工厂混淆。
可以进一步抽象吗? 我认为是这样,但是我怀疑它没有编译时保证,即当您可以创建BaseParser
时也可以创建IBulkImport
。
对于这两种情况:
可能需要帮助您为switch
语句(根据定义,不是为扩展而打开/为修改而闭合,也称为“打开/闭合”原理)而开发的,需要对其进行扩展以扩展功能。 解决方法通常是使用字典。 对于无尽的if
语句也是如此。
请注意, GenericFactory
已sealed
并且缺少abstract
关键字。 那是故意的。 该工厂的消费者应由该工厂组成,而不是从其继承。 就像UseFactory
方法组成工厂实例而不是从工厂继承的事物实例一样。 那是起作用的另一条原则:赞成继承而不是继承。
您还会注意到GenericFactory
实际上是由其他工厂组成的工厂,它委托给其他工厂(注入的字典中的每个Func
本身都是工厂)。 如果您确实需要它,那么这会通知我您可能没有使用IoC容器,因为IoC容器通常提供了这种构成工厂的机制,而您不必使用它。 在这种情况下,可能会帮助您研究IoC容器。
编辑: 您和我都提到了有关IoC的内容。
如果我有IoC,我将非常努力地尝试以下情形,以便甚至不需要GenericFactory
。
(我很抱歉要为任何已知的IoC容器开箱即用地创建伪代码)
ModuleA.cs
Register<AParser>().As<BaseParser>();
Register<ABulkImport>().As<IBulkImport>();
ModuleB.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>();
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();
注意:“单一成分根”通常不必遵循“打开/关闭”的原则。 但是,如果您希望在那里的switch
语句消失,那么可以朝着这种设计前进:
ModuleNameAttribute.cs
public class ModuleNameAttribute : Attribute
{
public string Name { get; }
...
}
ModuleA.cs
[ModuleName("A")]
public class ModuleA
{
...
}
ModuleB.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
“单一组合根”中的NotImplementedException
。 或者,如果模块C确实存在但未能注册IBulkImport
的实现,则IoC容器将在尝试在应用程序启动时再次尝试解析Application
CommonThing
时抛出运行时异常。 因此,如果应用程序启动,那么您就会知道所有依赖性都已解决或可以解决。
让我看看是否可以澄清您要做什么。
你有一组具有的一些功能的具体实现解析器(AParser,BParser,CParser)的BaseParser
。 您想要做的是在运行时为AParser
, BParser
等特定实例提供一些特殊功能。 假设您想要一个AParser
但是默认的AParser
实现不支持ParseFoo()
但是在运行时您想赋予它ParseFoo()
功能而无需提前定义呢?
如果是这种情况,那么我认为您可能需要与工厂一起考虑装饰器设计模式 。 使用Decorator,您将在自己的类中实现新功能的功能,然后工厂将返回装饰器,该装饰器仅包装了BaseParser
具体类。
似乎您的代码违反了Solid Principles,例如Single Purpose类。 如果要解决此问题,而不是实现每个接口的基类,则可以为每个接口包含一个属性,例如,解析器,批量导入器,分析。但是,如果要使用当前体系结构解决此简单方法,可以执行以下操作
您已经定义了基本解析器实现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");
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.