【问题标题】:Dependancy Injection and Fluent APIs - running into some problems依赖注入和 Fluent API - 遇到一些问题
【发布时间】:2011-05-30 02:28:29
【问题描述】:

我正在编写一个流畅的界面,使用如下:

xmlBuilder
    .CreateFrom()
    .DataSet(someDataSet) //yes I said Dataset, I'm working with a legacy code
    .IgnoreSchema()
    .Build

IgnoreSchema() 方法可以用WithSchema()WithDiffGrame() 代替。这些映射到接受以下枚举的 DataSet 的 WriteXml() 方法:

  • XmlWriteMode.WriteSchema
  • XmlWriteMode.DiffGram
  • XmlWriteMode.IgnoreSchema

我的 fluent 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,但尚未阅读有关此特定问题的任何内容。

希望,我只是错过了一小块拼图。


更新 (基于 Mark Seemann 的回答)

下面界面的语义模型是什么样的?示例将不胜感激。

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
    }
}

【问题讨论】:

    标签: .net dependency-injection enums fluent-interface


    【解决方案1】:

    在我看来,您发现了根据 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...
    }
    

    【讨论】:

    • 感谢您来到这里添加您的意见。我已经阅读了这篇文章,但我仍然有点挣扎,因为这是我对内部 DSL 和 DI 的第一次真正尝试。在我的示例中,经过深思熟虑,我不能只注入(通过构造函数)三个 IDataSetXMLFactory 类型的依赖项(三个类别中的每一个)吗?我认为这会解决问题,不是吗?至于语义模型,我肯定看到它有什么帮助,但我没有看到它在这里具体有什么帮助。
    • 问题是您正在尝试构建表达式并使其同时可执行(正如 WithSchemaAndDiffGram 方法所证明的那样)。只需添加更多选项,您就会有太多的排列无法管理。最好先使用 Fluent API 构建语义模型。一旦你有了它,你基本上可以将它注入你的真实对象模型并从中提供它。您不想在语义模型中使用 DI,因为它基本上只是一个数据图,您可以随后将其提供给您的对象模型(它可以以有意义的方式使用 DI)。
    • 我已经更新了我的问题以包括定义如何使用的 Fluent API 的接口(这是简单的部分)。我还定义了域和辅助对象。所以我想这是我正在努力解决的语义模型。使用我的代码的语义模型示例肯定会有所帮助。
    • 再次感谢您抽出宝贵时间提供帮助。因此,与其在构建表达式时直接调用 Fluent API 中的 IDataSetXMLFactory,不如使用语义模型来存储我的数据?然后,当我想在最后构建所有内容时,我将语义模型注入域对象(在这种情况下为 IDataSetXMLFactory 类)?
    • 这听起来很对。 Build() 方法触发了从语义模型到底层对象模型的转换。
    【解决方案2】:

    您的设计可以与 DI 一起使用。这里的技巧是注入工厂,而不是组件本身。您的分析绝对正确,应该由组合根决定DataSetXmlFactory 的哪个实现,但是您第一步错了。

    fluent 构建器完全有权根据其实际需要请求不同的实现。所有这些工厂都向同一个界面确认这一事实更多地是它们操作的副作用,而不是它们身份的一部分。将此视为 XML 工厂具有创建文档的能力,而不是创建文档的 IS-A[n] 实体。

    IoC 容器辅助系统(主要是构造函数注入风格)不能很好地处理接收未在注册时定义的构造函数参数的组件。这意味着某些框架甚至不允许您进行这种解析:var myClass = container.Resolve&lt;MyClass&gt;(additionalConstructorArgument);。这意味着为我们添加另一个级别的间接性 - 即工厂 - 。

    以下是我会采用的设计。这种特殊的设计有点笨拙,幼稚的实现会看到像DataSetXmlWithSchemaFactoryFactory 这样的类。所以我要做的第一件事就是将DataSetXmlWithSchemaFactory 重命名为DataSetXmlBuilderBase。恕我直言,这些类的作用更接近 builder 模式,而不是 抽象工厂模式。我还将介绍一套 fluent builder 将使用的构建器工厂接口。

    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。您可以在 making roles explicit 上观看 Udi Dahan 的精彩演讲,了解更多关于具有明确意图的编程风格的背景知识。

    【讨论】:

      猜你喜欢
      • 2010-12-10
      • 1970-01-01
      • 2011-07-26
      • 1970-01-01
      • 2015-11-21
      • 1970-01-01
      • 1970-01-01
      • 2014-05-27
      • 2014-12-27
      相关资源
      最近更新 更多