Ввод конструктора с параметрами без зависимостей

У меня есть интерфейс ITradingApi следующим образом:

public interface ITradingApi
{
    IOrder CreateOrder(...);
    IEnumerable<Symbol> GetAllSymbols();
    // ...
}

Это должно быть фасадом для различных API-интерфейсов поставщиков торгового программного обеспечения. Моя модель просмотра имеет зависимость от этого торгового API в своем конструкторе:

public class MainViewModel
{
    public MainViewModel(ITradingApi tradingApi) { /* ... */ }
    // ...
}

Я использую Ninject в качестве контейнера IoC, поэтому я создам экземпляр моей модели представления следующим образом:

var vm = kernel.Get<MainViewModel>();

Теперь моя проблема:

Для реализации ITradingApi могут потребоваться дополнительные параметры для работы.
Пример:

  • API-интерфейс одного поставщика использует TCP/IP внутренне, поэтому мне нужно имя хоста и порт.
  • Другой поставщик использует COM-объект. Здесь мне не нужна информация.
  • Третьему поставщику требуется имя пользователя и пароль учетной записи.

В духе недопущения неполных объектов я добавил их в качестве параметров к конструкторам конкретных реализаций.

Теперь я не уверен, как это будет работать. Очевидно, что эти дополнительные параметры не относятся к интерфейсу, поскольку они специфичны для каждой реализации.
С другой стороны, эти дополнительные параметры должны вводиться конечным пользователем, а затем передаваться в реализацию ITradingApi, что означает, что пользователь ITradingApi нуждается в глубоких знаниях о конкретной реализации.
Как решить эту дилемму?

UPDATE:
Один из подходов может заключаться в создании ITradingApiProvider, который предоставляет список требуемых параметров. View мог автоматически создать форму ввода для этих параметров, которая привязана к параметрам в ITradingApiProvider. Теперь, когда экземпляр ITradingApi запрашивается у поставщика, он может использовать эти параметры для создания экземпляра конкретной реализации. Очевидно, что реализация ITradingApiProvider и ITradingApi тесно связана, но я думаю, что это не проблема, если каждая реализация ITradingApi поставляется с соответствующей реализацией ITradingApiProvider.

Ответ 1

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

Прежде всего, независимо от того, поставляются ли конкретные значения конфигурации во время компоновки или действительно впервые доступны во время выполнения, поскольку пользовательский ввод имеет огромное значение. Пока они могут быть разрешены во время составления, все просто, потому что вы можете просто прочитать значения из среды и предоставить их соответствующим конструкторам. Итак, для остальной части этого ответа я собираюсь предположить, что все намного сложнее, и вам действительно нужно получить эти значения от пользователя во время выполнения.

Вместо того, чтобы пытаться разработать API конфигурации общего назначения, я бы скорее смоделировал то, что происходит на самом деле. В этом случае мне кажется, что мы собираем значения конфигурации у пользователя, так почему бы не моделировать это явно?

Торговец товарами

Определите такой интерфейс:

public interface ITradingApiTrader
{
    ITradingApi Create(Type apiType);
}

Здесь он предположил, что apiType может быть применен к ITradingApi, но это не может быть реализовано компилятором. (Причина, по которой я называю это "трейдером", заключается в том, что это вариация шаблона Product Trader (PLoPD 3).)

Как это отличается от предыдущего?

Ну, вы можете реализовать метод Create, показывая пользовательский интерфейс для каждого типа ITradingApi. Каждый конкретный пользовательский интерфейс собирает значения, необходимые для собственной конкретной реализации ITradingApi, и затем возвращает правильно сконфигурированный экземпляр.

Если вы знаете конкретные типы во время компиляции, другие варианты включают следующие:

public interface ITradingApiTrader
{
    ITradingApi CreateMT4TradingApi();

    ITradingApi CreateFooTradingApi();

    ITradingApi CreateBarTradingApi();

    // etc.
}

Возможно, вы также можете это сделать (хотя я не пытался скомпилировать это):

public interface ITradingApiTrader
{
    ITradingApi Create<T>() where T : ITradingApi;
}

Обратите внимание также, что вам не нужно определять первый метод Create ITApractorAprayer, основанный на типе - любой идентификатор (например, перечисление или строка) мог бы сделать вместо этого.

Visitor

Если набор ITradingApi (конечный и) известен во время разработки, шаблон проектирования Visitor также может предложить альтернативу.

Если вы используете посетителя, вы можете заставить метод Visit показать соответствующий пользовательский интерфейс, а затем использовать значения, собранные из пользовательского интерфейса, для создания соответствующего экземпляра ITradingApi.

В основном это всего лишь вариация предыдущего "решения", в которой продукт-трейдер реализован как посетитель.

Ответ 2

Это то, что вы после?

   ninjectKernel.Get<MainViewModel>().WithConstructorArgument("tradingApi", 
       kernel.Get<ITaxCalculator>() .WithConstructorArgument("additionalParameter","someValue")));

Ответ 3

Хорошо, мои два цента, я не уверен в том, что вы знаете. Это просто помочь и попробовать...

Мы предоставляем посетителю ваш api как конструкцию интерфейса:

public interface ITradingApi
{
    Object CreateOrder();
    IEnumerable<Object> GetAllSymbols();
}

public class TradingApi : ITradingApi
{
    IvisitorAPI _VisitorAPI;

    public TradingApi(IvisitorAPI visitorAPI)
    {
        _VisitorAPI = visitorAPI;
    }


    public Object CreateOrder()
    {
        var Order = new Object();
        //bla bla bla

        //here code relative to different visitor
        _VisitorAPI.SaveOrder(Order);

        return Order;
    }
}

Это ваш посетитель, который знает, как обрабатывать некоторые действия, потому что в зависимости от посетителя он будет использовать ваш api разными способами для достижения того же действия (здесь SaveOrder).

public interface IvisitorAPI
{
    bool SaveOrder(Object order);
}



public class visitorApiIP : IvisitorAPI
{
    public string HostName { get; set; }
    public int Port { get; set; }

    public visitorApiIP(string hostname, int port)
    {
        HostName = hostname;
        Port = port;
    }


    public bool SaveOrder(Object order)
    {
        //save the order using hostname and ip
        //...
        //....
        return true;
    }
}

Только посетитель знает, что ему нужно для достижения своей версии действия. Поэтому APi не нуждается в дополнительных параметрах, мы отталкиваем логику в классе посетителей. Этот класс посетителя может быть создан только тогда, когда eve знает, кто является посетителем, поэтому, безусловно, во время выполнения

Надеюсь, это может дать вам некоторую перспективу. Я не знаю, может ли вся теория применить вашу точную ситуацию.

В любом случае, лучше всего;)

Ответ 4

Решение заключается в использовании подхода, описанного в части обновления моего вопроса. ITradingApiProvider берет на себя роль абстрактного factory и поэтому его следует переименовать в ITradingApiFactory. Он предоставит список необходимых параметров, значения которых могут быть установлены. Этот список, в свою очередь, может использоваться View для автоматического представления пользователю формы ввода для ввода значения для каждого параметра, поскольку только пользователь знает значения параметров для параметров.
Тогда вызов Create будет использовать следующие параметры:

public interface ITradingApiFactory
{
    ITradingApi Create();
    IEnumerable<Parameter> Parameters { get; }
}

public class Parameter
{
    public Parameter(Type type, string name, string description)
    { Type = type; Name = name; Description = description; }

    public Type Type { get; private set; }
    public string Name { get; private set; }
    public string Description { get; private set; }
    public object Value { get; set; }
}

public class MT4TradingApiFactory : ITradingApiFactory
{
    Dictionary<string, Parameter> _parameters;

    public MT4TradingApiFactory()
    { /* init _parameters */ }

    public ITradingApi Create()
    {
        return new MT4TradingApi(_parameters["hostname"].ToString(),
                                 (int)_parameters["port"]);
    }

    IEnumerable<Parameter> Parameters { get { return _parameters.Values; } }
}

Дополнительную информацию можно найти в этом ответе.

Это можно продвинуть дальше, чтобы упростить его использование, предоставив каждой реализации factory параметры как свойства и изменив класс Parameter, чтобы работать непосредственно с этими свойствами с помощью деревьев выражений. Если кто-то заинтересован в этом расширенном дизайне factory, оставьте комментарий.

Ответ 5

Я думаю, что с вашим провайдером нет ничего плохого. У вас есть две проблемы:

  • Действующий: ваш ITradingAPI, который определяет контракт для операций, которые вы можете выполнять.
  • Метаданные: что-то, что описывает свойства реальной реализации (метаданные могут быть неточными, но не могут думать о лучшем имени для него)

Теперь, очевидно, вам нужно что-то, что может сделать связь между двумя, и это ваш ITradingAPIProvider. Кажется разумным прямо вперед и имеет хорошие шансы на то, что вы все равно поймете свой код, вернувшись к нему через год два;)

Ответ 6

Как пробовать что-то похожее на шаблон стратегии? Создайте новый интерфейс IConnectStrategy:

interface IConnectStrategy
{
    void Connect();
}

Добавьте соединительную стратегию как аргумент метода void CreateOrder(IConnectStrategy connectStrategy) в ITradingApi и пусть каждый поставщик создает/задает свой собственный метод для подключения. Например. для одного производителя:

public class TCPConnectStrategy : IConnectStrategy
{
    public TCPConnectStrategy(string hostName, int port)
    {
        /* ... */
    }

    public void Connect()
    {
        /* ... tcp connect ... */
    }
}

(Connect может не быть лучшим именем или даже тем, что вы на самом деле делаете, но, пожалуйста, примените его к тому, что работает для вашего проекта.)

Изменить после комментариев: Создайте стратегию, которая имеет только контракты для каждого метода, которые имеют параметры, зависящие от поставщика. Затем добавьте метод void SetVendorStrategy(IVendorStrategy vendorStrategy) (или свойство) в интерфейс ITradingAPI. Каждая реализация стратегии имеет свой собственный конструктор с собственными параметрами, и каждый метод (требующий конкретных параметров поставщика) в каждой реализации интерфейса ITradingAPI просто вызывает vendorStrategy.DoSomethingWithVendorSpecificData().