依赖注入和Fluent API - 遇到一些问题

时间:2011-05-30 02:28:30

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

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

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

我的流畅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
    }
}

2 个答案:

答案 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上的精彩演示,了解有关编程风格的更多背景知识。