我正在编写一个流畅的界面,使用如下:
xmlBuilder
.CreateFrom()
.DataSet(someDataSet) //yes I said Dataset, I'm working with a legacy code
.IgnoreSchema()
.Build
IgnoreSchema()
方法可替代WithSchema()
或WithDiffGrame()
。这些映射到DataSet的WriteXml()
方法,该方法接受以下枚举:
我的流畅API正在调用相当于将从数据集创建XML的各种工厂对象。我有一个具有核心功能的抽象类型,然后是3个派生类型,它们反映了实现WriteXmlFromDataSet
方法的各种状态(我相信这种方法称为状态模式)。这是抽象基类:
public abstract class DataSetXmlBaseFactory : IDataSetXmlFactory
{
...
protected abstract void WriteXmlFromDataSet(XmlTextWriter xmlTextWriter);
public XmlDocument CreateXmlDocument()
{
XmlDocument document = new XmlDocument();
using (StringWriter stringWriter = new StringWriter())
{
using (XmlTextWriter xmlTextWriter = new XmlTextWriter(stringWriter))
{
WriteXmlFromDataSet(xmlTextWriter);
string content = stringWriter.ToString();
document.LoadXml(content);
return document;
}
}
}
}
这当然有效,但是当我使用这个带有依赖注入的代码时,我在开始时提到的流畅界面中的方法遇到了麻烦。以下是这些方法的实现:
public IXmlBuild<T> WithSchema()
{
var xmlFactory = new DataSetXmlWithSchemaFactory(this.DataSet);
return GetIXmlBuild(xmlFactory);
}
public IXmlBuild<T> IgnoreSchema()
{
var xmlFactory = new DataSetXmlIgnoreSchemaFactory(this.DataSet);
return GetIXmlBuild(xmlFactory);
}
public IXmlBuild<T> WithSchemaAndDiffGram()
{
var xmlFactory = new DataSetXmlWithDiffGramFactory(this.DataSet);
return GetIXmlBuild(xmlFactory);
}
private static IXmlBuild<T> GetIXmlBuild(IDataSetXmlFactory xmlFactory)
{
string content = xmlFactory.CreateXmlDocument().InnerXml;
return new clsXmlDataSetBuild<T>(content);
}
现在我没有使用依赖注入(DI),因为我正在新建依赖的IDataSetXMLFactory
对象。如果我更改代码以使用DI,那么该类将如何知道要使用哪个IDataSetXmlFactory实现?如果我正确地理解了DI,则需要在调用堆栈(特别是在组合根)上更高地做出该决定,但在那里代码将不知道需要哪个确切的实现。如果我使用DI容器来解析(定位)上述方法中所需的实现,那么我将使用DI容器作为服务定位器,这被认为是反模式。
此时,将枚举传递给IXmlDataSetFactory实例上的xmlFactory.CreateXmlDocument()
方法会容易得多。这肯定更容易,而且代码更少,但我确信这个问题之前已经遇到过状态模式和DI。有什么方法可以解决这个问题?我是DI的新手并已开始阅读Dependency Injection in .NET,但尚未阅读有关此特定问题的任何内容。
希望,我只是错过了一小块拼图。
以下界面的语义模型是什么样的?示例将不胜感激。
public interface IXmlBuilder<T>
{
IXmlSourceContent<T> CreateFrom();
}
public interface IXmlSourceContent<T>
{
IXmlOptions<T> Object(T item);
IXmlOptions<T> Objects(IEnumerable<T> items);
IXmlDataSetOptions<T> DataSet(T ds);
IXmlBuild<T> InferredSchema();
}
public interface IXmlOptions<T> : IXmlBuild<T>
{
IXmlBuild<T> WithInferredSchema();
}
public interface IXmlDataSetOptions<T> : IXmlDataSetSchema<T>
{
IXmlDataSetSchema<T> IncludeTables(DataTableCollection tables);
IXmlDataSetSchema<T> IncludeTable(DataTable table);
}
public interface IXmlBuild<T>
{
XmlDocument Build();
}
public interface IXmlDataSetSchema<T>
{
IXmlBuild<T> WithSchemaAndDiffGram();
IXmlBuild<T> WithSchema();
IXmlBuild<T> IgnoreSchema();
}
除了上面提到的IDataSetXMLFactory,我还有以下扩展方法:
static class XmlDocumentExtensions
{
[Extension()]
public static void InsertSchema(XmlDocument document, XmlSchema schema)
{
...
}
}
static class XmlSchemaExtensions
{
[Extension()]
public static string ToXmlText(XmlSchema schema)
{
...
}
}
以及这些课程:
public class XmlFactory<T>
{
...
public XmlFactory(IEnumerable<T> objects)
{
this.Objects = objects;
}
public XmlDocument CreateXml()
{
// serializes objects to XML
}
}
public class XmlSchemaFactory<T> : IXmlSchemaFactory<T>
{
public XmlSchema CreateXmlSchema()
{
// Uses reflection to build schema from type
}
}
答案 0 :(得分:6)
在我看来,您正在发现根据API所针对的对象模型定义Fluent API的局限性。作为Jeremy Miller points out, it's often better to let the Fluent API build a Semantic Model,然后可以用它来构造所需的对象图。
这是我分享的经验,我发现这有助于弥合Fluent API和DI之间的明显差距。
基于最初提供的Fluent API,语义模型可能就像这样简单:
public class MySemanticModel
{
public DataSet DataSet { get; set; }
public bool IgnoreSchema { get; set; }
// etc...
}
答案 1 :(得分:0)
您的设计可以与DI一起使用。这里的诀窍是注入工厂,而不是组件本身。在您的分析中,您完全正确地决定哪个DataSetXmlFactory
的实现应该由组合根进行,但是您的第一步是错误的。
流利的构建器完全有权根据实际需要请求不同的实现。所有这些工厂确认相同界面的事实更多的是他们的操作的副作用,而不是他们的身份的一部分。可以将其视为XML工厂HAS-A创建文档的功能,而不是创建文档的IS-A [n]实体。
IoC容器辅助系统(主要是构造函数注入风格)不能很好地处理接收未在注册时定义的构造函数参数的组件。这意味着某些框架甚至不允许您执行此类解析:var myClass = container.Resolve<MyClass>(additionalConstructorArgument);
。这意味着为我们添加另一层次的间接 - 即工厂 -
以下是我要使用的设计。这个特殊的设计有点尴尬,一个天真的实现会看到像DataSetXmlWithSchemaFactoryFactory
这样的类。所以我要做的第一件事就是将DataSetXmlWithSchemaFactory
重命名为DataSetXmlBuilderBase
。恕我直言,这些类所做的更接近构建器模式而不是抽象工厂模式。我还将介绍一组流水构建器将使用的构建器工厂接口。
public abstract class DataSetXmlBuilderBase : IDataSetXmlBuilder
{
//existing implementation
}
public interface IDataSetXmlBuilderFactory
{
IDataSetXmlBuilder Create(DataSet dataset);
}
//Marker interfaces for different builder facotries
public interface IDataSetWithSchemaBuilderFactory : IDataSetXmlBuilderFactory
{
}
public interface IDataSetXmlIgnoreSchemaBuilderFactory : IDataSetXmlBuilderFactory
{
}
public interface IDataSetXmlWithDiffGramBuilderFactory : IDataSetXmlBuilderFactory
{
}
//factory implementation
public class DataSetWithSchemaBuilderFactory : IDataSetWithSchemaBuilderFactory
{
public IDataSetXmlBuilder Create(DataSet dataset)
{
return new DataSetWithSchemaBuilder(dataset);
}
}
//Our fluent builder now receives multiple factories in its constructor and can perform its task without referencing the IoC container
public class FluentXmlbuilder
{
readonly IDataSetWithSchemaBuilderFactory _withSchemaBuilderFactory;
readonly IDataSetXmlIgnoreSchemaBuilderFactory _ignoreSchemaBuilderFactory;
readonly IDataSetXmlWithDiffGramBuilderFactory _withDiffGramBuilderFactory;
public FluentXmlbuilder(IDataSetWithSchemaBuilderFactory withSchemaBuilderFactory, IDataSetXmlIgnoreSchemaBuilderFactory ignoreSchemaBuilderFactory,IDataSetXmlWithDiffGramBuilderFactory withDiffGramBuilderFactory)
{
_withSchemaBuilderFactory = withSchemaBuilderFactory;
_ignoreSchemaBuilderFactory = ignoreSchemaBuilderFactory;
_withDiffGramBuilderFactory = withDiffGramBuilderFactory;
}
public IXmlBuild<T> WithSchema()
{
var xmlFactory = _withSchemaBuilderFactory.Create(this.DataSet);
return GetIXmlBuild(xmlFactory);
}
public IXmlBuild<T> IgnoreSchema()
{
var xmlFactory = _ignoreSchemaBuilderFactory.Create(this.DataSet);
return GetIXmlBuild(xmlFactory);
}
public IXmlBuild<T> WithSchemaAndDiffGram()
{
var xmlFactory = _withDiffGramBuilderFactory.Create(this.DataSet);
return GetIXmlBuild(xmlFactory);
}
private static IXmlBuild<T> GetIXmlBuild(IDataSetXmlFactory xmlFactory)
{
string content = xmlFactory.CreateXmlDocument().InnerXml;
return new clsXmlDataSetBuild<T>(content);
}
}
关于标记接口的说明。这些接口实际上并没有向它们继承的接口添加任何新东西,但它们在为该组件注册 intent 时非常有用。因此,当我们要求IDataSetWithSchemaBuilderFactory
时,我们要求容器为我们提供一个构建器工厂,该工厂使用模式创建XML文档。因为它是一个接口,我们可以在容器级别将其换出另一个工厂,而不会触及FluentXmlBuilder
。您可以观看Udi Dahan在making roles explicit上的精彩演示,了解有关编程风格的更多背景知识。