我有一个包含大量共享代码和配置属性的抽象基类。我已将大部分共享代码拆分为也由基类实现的逻辑接口。每个客户都有一系列基类的实现。
我目前为每个接口都有一个工厂。每个工厂都有相同的 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
在创建GenericFactory
s之前,可以实例化 ,然后在不同的模块之间折腾,以便用所有必要的“A”、“B”、“C”等情况填充它。或者不是让模块接受一个Dictionary<string, Factories>
对象,也许每个模块都可以有一个接口的实现,它只生成一个Factories
实例,你可以从模块元数据中收集“A”、“B”等键。这样你就可以保证“B”模块不会与“A”模块的工厂混淆。
这可以进一步抽象吗?我认为是这样,但我怀疑它会在没有编译时保证的情况下出现,即当您可以创建一个时,您也BaseParser
可以创建一个IBulkImport
.
对于这两种情况:
您可能会有助于培养switch
对需要修改以扩展功能的语句的嗅觉(根据定义,这些语句不是对扩展开放/对修改封闭,也称为开放/封闭原则)。用字典编写通常是解决方案。无休止的if
语句也是如此。
请注意GenericFactory
issealed
和缺少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 容器将在尝试解析CommonThing
for时抛出运行时异常Application
,再次在应用程序启动时。因此,如果应用程序启动,那么您就知道所有依赖项要么已解决,要么可以解决。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句