Depenancy Injection и Fluent APIs - сталкиваются с некоторыми проблемами - PullRequest
3 голосов
/ 30 мая 2011

Я пишу свободный интерфейс, который используется следующим образом:

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

Метод IgnoreSchema() может иметь вместо него WithSchema() или WithDiffGrame().Они сопоставляются с методом WriteXml() DataSet, который принимает следующее перечисление:

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

Это работает, конечно, но когда я собираюсь использовать этот код с Dependency Injection, у меня возникают проблемы с методами в моем беглом интерфейсе, упомянутом в начале.Ниже приведена реализация этих методов:

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

Прямо сейчас я не использую Dependency Injection (DI), так как я обновляю зависимые IDataSetXMLFactory объекты.Если бы я изменил код для использования DI, как бы класс узнал, какую реализацию IDataSetXmlFactory использовать?Если я правильно понимаю DI, это решение нужно будет принимать выше стека вызовов (в частности, в корне композиции), но там код не будет знать, какая именно реализация необходима.Если бы я использовал контейнер DI для разрешения (определения местоположения) необходимой реализации в описанных выше методах, то я бы использовал контейнер DI в качестве локатора службы, который считается анти-шаблоном.

На этом этапе было бы намного проще просто передать перечисление методу xmlFactory.CreateXmlDocument() в экземпляре IXmlDataSetFactory.Это, конечно, намного проще и требует меньше кода, но я уверен, что с этой проблемой ранее сталкивались шаблоны состояний и DI.Как с этим справиться?Я новичок в DI и начал читать Внедрение зависимостей в .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
    }
}

Ответы [ 2 ]

6 голосов
/ 30 мая 2011

Мне кажется, что вы открываете границы определения Fluent API с точки зрения объектной модели, на которую нацелен API. Как указывает Джереми Миллер, часто лучше, чтобы Fluent API создавал семантическую модель , которую затем можно использовать для построения нужного графа объектов.

Это опыт, которым я поделюсь и который, как мне кажется, помогает преодолеть очевидный разрыв между Fluent API и DI.


Основываясь на представленном оригинальном Fluent API, семантическая модель может быть простой:

public class MySemanticModel
{
    public DataSet DataSet { get; set; }
    public bool IgnoreSchema { get; set; }
    // etc...
}
0 голосов
/ 01 июня 2011

Ваш дизайн может быть настроен для работы с DI.Хитрость заключается в том, чтобы вводить фабрики, а не сами компоненты.Вы абсолютно правы в своем анализе, что решение о том, какую реализацию DataSetXmlFactory должен принимать составной корень, однако вы ошиблись в первом шаге.

Свободный компоновщик имеет все права для запроса различных реализаций.исходя из того, что ему на самом деле нужно.Тот факт, что все эти фабрики подтверждают тот же интерфейс, является скорее побочным эффектом их работы, а не частью их идентичности .Думайте об этом как о возможности фабрики XML создавать документ, а не объект 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.Вы можете посмотреть отличную презентацию Уди Дахана о , где роли явно указаны , чтобы узнать больше о стиле программирования с явным намерением.

...