具有非依赖性参数的构造函数注入

时间:2011-07-21 07:25:49

标签: c# .net architecture dependency-injection

我有一个界面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。 View可以自动为这些参数创建一个输入表单,该表单将数据绑定到ITradingApiProvider中的参数。现在,当从提供程序请求ITradingApi实例时,它可以使用这些参数来创建具体实现的实例。显然,ITradingApiProviderITradingApi的实现是紧密耦合的,但我认为只要ITradingApi的每个实现带有ITradingApiProvider的相应实现,这都不是问题。

6 个答案:

答案 0 :(得分:3)

根据迄今为止提出的信息,我想指出一两件事:

首先,无论具体配置值是在合成时提供还是在运行时真正首次提供,因为用户输入会产生巨大差异。只要它们可以在组合时解决,事情很简单,因为您可以简单地从环境中读取值并将它们提供给适当的构造函数。因此,对于本答案的其余部分,我将假设事情变得更加困难,并且您实际上需要在运行时从用户那里获取这些值。

我没有试图提出通用配置API,而是更多地模拟实际发生的事情。在这种情况下,我觉得我们正在收集用户的配置值,为什么不明确地对此进行建模?

产品交易员

定义这样的界面:

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

这里假设apiType可以转换为ITradingApi,但编译器无法强制执行此操作。 (我称之为“交易者”的原因是因为这是产品交易者模式(PLoPD 3)的变体。)

这与以前有什么不同?

嗯,您可以通过为每种类型的ITradingApi显示用户界面实现 Create方法。每个具体的用户界面都会收集其自己的具体ITradingApi实现所需的值,然后返回正确配置的实例。

如果您在编译时知道具体类型,其他变体包括:

public interface ITradingApiTrader
{
    ITradingApi CreateMT4TradingApi();

    ITradingApi CreateFooTradingApi();

    ITradingApi CreateBarTradingApi();

    // etc.
}

也许你也可以这样做(虽然我还没有尝试过编译):

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

另请注意,您不需要根据类型定义第一个ITradingApiTrader的Create方法 - 任何标识符(例如枚举或字符串)都可以改为。

<强>访问者

如果ITradingApi的集合在设计时已知(有限且已知),则访问者设计模式也可能提供替代方案。

如果您使用访问者,则可以使访问方法显示适当的用户界面,然后使用从用户界面收集的值来创建适当的ITradingApi实例。

基本上这只是以前'解决方案'的变体,其中产品交易者被实现为访客。

答案 1 :(得分:2)

这是你的事吗?

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

答案 2 :(得分:1)

好吧,我的两分钱,我不确定你知道什么。这只是为了帮助和尝试......

我们将访问者的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需要额外的参数,我们正在推动访客类的逻辑。 只有当ewe知道谁是访问者时才可以创建此访问者类,因此肯定是在运行时

希望它可以给你一些观点。我不知道整个理论是否适用于你的确切情况。

无论如何,

我最好的;)

答案 3 :(得分:1)

解决方案是使用我的问题的更新部分中概述的方法。 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; } }
}

可以在this answer找到更多信息。

通过为每个Factory实现提供参数作为属性并更改Parameter类以使用表达式树直接在这些属性上工作,可以进一步提高这一点以使其更易于使用。如果有人对这种先进的工厂设计感兴趣,请发表评论。

答案 4 :(得分:1)

我认为您的提供商方法没有任何问题。你有两个问题:

  1. 可操作的:您的ITradingAPI,它定义了您可以执行的操作的合同。
  2. 元数据之一:描述实际实现属性的东西(元数据可能不是很安静,但不能想到更好的名称)
  3. 现在显然你需要能够在两者之间建立联系的东西,那就是你的ITradingAPIProvider。看似合理的直接前进,很有可能在一年后两年内回到它时仍然能理解你的代码;)

答案 5 :(得分:0)

尝试类似strategy pattern的内容怎么样?创建一个名为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()