Внедрение в конструктор с независимыми параметрами - PullRequest
10 голосов
/ 21 июля 2011

У меня есть интерфейс 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 нужны глубокие знания о конкретной реализации.
Как решить эту дилемму?

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

Ответы [ 6 ]

3 голосов
/ 21 июля 2011

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

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

Вместо того, чтобы пытаться придумать 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 ITradingApiTrader, основанный на типе - любой идентификатор (такой как enum или string) мог бы сделать вместо этого.

Посетитель

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

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

По сути, это всего лишь вариант предыдущего «решения», в котором Product Trader реализован как посетитель.

2 голосов
/ 21 июля 2011

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

   ninjectKernel.Get<MainViewModel>().WithConstructorArgument("tradingApi", 
       kernel.Get<ITaxCalculator>() .WithConstructorArgument("additionalParameter","someValue")));
1 голос
/ 21 июля 2011

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

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

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

1 голос
/ 21 июля 2011

Решение состоит в том, чтобы использовать подход, изложенный в части обновления моего вопроса.ITradingApiProvider принимает роль абстрактной фабрики и, следовательно, должен быть переименован в ITradingApiFactory.Это предоставит список необходимых параметров, значения которых могут быть установлены.Этот список, в свою очередь, может использоваться представлением для автоматического предоставления пользователю формы ввода для ввода значения для каждого параметра, поскольку только значения параметров известны только пользователю.
В этом случае вызов 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; } }
}

Более подробную информацию можно найти в этом ответе .

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

1 голос
/ 21 июля 2011

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

Мы предоставляем посетителю ваш 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 нуждаются в дополнительных параметрах, мы отталкиваем логику в классе посетителя.Этот класс посетителя может быть создан только тогда, когда мы знаем, кто является посетителем, поэтому, безусловно, во время выполнения

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

В любом случае, мой лучший;)

0 голосов
/ 21 июля 2011

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

interface IConnectStrategy
{
    void Connect();
}

Добавьте connectstrategy в качестве аргумента метода 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().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...