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

发布于 11-25 06:53 字数 1285 浏览 2 评论 0 原文

我有一个如下所示的接口 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 实例时,它可以利用这些参数来创建具体实现的实例。显然,ITradingApiProviderITradingApi 的实现是紧密耦合的,但我认为只要 ITradingApi 的每个实现都带有一个ITradingApiProvider 的相应实现。

I have an interface ITradingApi like so:

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

This is meant to be a facade for the different APIs of the vendors of trading software.
My view model has a dependency on this trading API in its constructor:

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

I use Ninject as an IoC container, so I will create an instance of my view model like this:

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

Now, my problem:

The implementation of ITradingApi might need additional parameters to work.
Example:

  • One vendors API uses TCP/IP internally, so I need a hostname and a port.
  • Another vendor uses a COM object. Here I don't need any info.
  • A third vendor needs username and password of the account.

In the spirit of not allowing incomplete objects, I added these as parameters to the constructors of the concrete implementations.

Now, I am not sure, how this would work. Clearly, these additional parameters do not belong into the interface, because they are specific to each implementation.
On the other hand, these additional parameters need to be entered by the end-user and then passed to the implementation of ITradingApi, meaning that the user of ITradingApi needs intimate knowledge about the concrete implementation.
How to solve this dilemma?

UPDATE:
One approach could be to create an ITradingApiProvider that exposes a list of required parameters. The View could automatically create an input form for these parameters that is databound to the parameters in ITradingApiProvider. Now, when an ITradingApi instance is requested from the provider, it can make use of these parameters to create an instance of the concrete implementation. Clearly the implementation of ITradingApiProvider and ITradingApi are tightly coupled, but I think that is not a problem as long as each implementation of ITradingApi comes with a corresponding implementation of ITradingApiProvider.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

甜`诱少女 2024-12-02 06:53:01

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

首先,具体的配置值是否在组合时提供,或者在运行时真正首先在用户输入时可用,这会产生巨大的影响。不同之处。只要它们可以在组合时解决,事情就很容易,因为您可以简单地从环境中读取值并将它们提供给适当的构造函数。因此,对于这个答案的其余部分,我将假设事情要困难得多,并且您实际上需要在运行时从用户那里获取这些值。

我宁愿对实际发生的情况进行建模,而不是尝试提出通用配置 API。在这种情况下,我觉得我们正在从用户那里收集配置值,那么为什么不明确地对此进行建模呢?

Product Trader

定义如下接口:

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 方法 :类型 - 任何标识符(例如枚举或字符串)都可以代替。

Visitor

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

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

基本上,这只是之前“解决方案”的变体,其中产品交易者作为访问者实现。

Based on the information so far put forth here, I'd like to point out one or two things:

First of all, whether or not the concrete configuration values are supplied at composition time or truly first available at runtime as user input makes a huge difference. As long as they can be resolved at composition time things are easy because you can simply read the values from the environment and supply them to the appropriate constructors. So, for the rest of this answer I'm going to assume that things are much harder and you actually need to get those values from the user at runtime.

Instead of attempting to come up with a general-purpose configuration API I'd much rather model what's actually going on. In this case it sounds to me like we're collecting configuration values from the user, so why not model this explicitly?

Product Trader

Define an interface like this:

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

Here, it's assumed that apiType can cast to ITradingApi, but this can't be enforced by the compiler. (The reason I'm calling this a 'Trader' is because this is a variation of the Product Trader pattern (PLoPD 3).)

How is this different than before?

Well, you can implement the Create method by showing a user interface for each type of ITradingApi. Each concrete user interface gathers the values required for its own concrete ITradingApi implementation and subsequently returns a correctly configured instance.

If you know the concrete types at compile time, other variations include these:

public interface ITradingApiTrader
{
    ITradingApi CreateMT4TradingApi();

    ITradingApi CreateFooTradingApi();

    ITradingApi CreateBarTradingApi();

    // etc.
}

Perhaps you can also do this (although I haven't tried to compile this):

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

Note also that you don't need to define the first ITradingApiTrader's Create method based on a Type - any identifier (such as an enum or string) might do instead.

Visitor

If the set of ITradingApi is (finite and) known at design time, the Visitor design pattern might also offer an alternative.

If you use a Visitor, you can make the Visit method show an appropriate user interface and then subsequently use the values collected from the user interface to create the appropriate ITradingApi instance.

Basically this is just a variation on the previous 'solution' where the Product Trader is implemented as a Visitor.

向日葵 2024-12-02 06:53:01

这就是你的追求吗?

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

Is this what your after?

   ninjectKernel.Get<MainViewModel>().WithConstructorArgument("tradingApi", 
       kernel.Get<ITaxCalculator>() .WithConstructorArgument("additionalParameter","someValue")));
清晰传感 2024-12-02 06:53:01

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

我们为您的 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 不需要额外的参数,我们将逻辑推到访问者类中。
仅当我们知道谁是访问者时,才可能创建此访问者类,因此,肯定是在运行时

希望它能给您一些视角。我不知道整个理论是否可以应用于您的具体情况。

无论如何,我最好的;)

Ok my two cents, I am not sure of anything you know. It is just to help and try...

We give a visitor to your api as construction of the interface:

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

It is your visitor that knows how to handle some of the action, because depending on the visitor he will use your api in different ways to achieve the same action ( here 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;
    }
}

Only the visitor has a knowledge of what he needs to achieve his version of the action.
Therefore it is not the APi that needs additionnal parameters, we are pushing the logic away in the visitor class.
This visitor class might be created only when ewe know who is the visitor therefore, surely at runtime

Hope it might give you some perspective. I do not know if the whole theory can be applied your exact situation.

My best anyway ;)

唱一曲作罢 2024-12-02 06:53:01

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

更多信息可以在 这个答案

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

The solution is to use the approach as outlined in the update part of my question. ITradingApiProvider takes the role of an abstract factory and thus should be renamed to ITradingApiFactory. It would expose a list of needed parameters whose values can be set. This list in turn can be used by the View to automatically present the user with an input form to enter a value for each parameter, because only the user knows the values of for the parameters.
The call to Create would then use these parameters:

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

More info can be found in this answer.

This can be advanced further to make it easier to use, by giving each Factory implementation the parameters as properties and change the Parameter class to work directly on these properties using expression trees. If someone is interested in this advanced factory design, please leave a comment.

只为一人 2024-12-02 06:53:01

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

  1. 一个是操作问题:您的 ITradingAPI 定义了您可以执行的操作的合同。
  2. 元数据:描述实际实现的属性的东西(元数据可能不太安静,但想不出更好的名称)

现在显然您需要一些可以在两者之间建立联系的东西,那就是您的ITradingAPIProvider。看起来很合理,直接,并且很有可能在一年或两年后回到代码时您仍然会理解您的代码;)

I think there is nothing wrong with your provider approach. You have two concerns here:

  1. An operational one: your ITradingAPI which defines a contract for operations you can perform.
  2. A meta-data one: something which describes properties of an actual implementation (meta data might not be quiet right but can't think of a better name for it)

Now apparently you need something which can make the connection between the two and that is your ITradingAPIProvider. Seems reasonable straight forward and has good chance of that you will still understand your code when coming back to it after a year ot two ;)

筱武穆 2024-12-02 06:53:01

尝试类似于策略模式怎么样?创建一个名为 IConnectStrategy 的新接口:

interface IConnectStrategy
{
    void Connect();
}

将 connectstrategy 作为参数添加到 ITradingApi 中的方法 void CreateOrder(IConnectStrategy connectStrategy) 中,并让每个供应商创建/指定自己的连接方法。例如,对于一个供应商创建:

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

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

(Connect 可能不是最好的名称,甚至不是您实际正在做的事情,但请将其应用于适合您的项目的任何内容。)

评论后编辑:
创建一个策略,该策略仅包含具有特定于供应商参数的每种方法的合同。然后将方法 void SetVendorStrategy(IVendorStrategyvendorStrategy)(或属性)添加到 ITradingAPI 接口。该策略的每个实现都有自己的构造函数和自己的参数,ITradingAPI 接口的每个实现中的每个方法(需要供应商特定参数)只需调用 vendorStrategy.DoSomethingWithVendorSpecificData()

How about trying something similar to the strategy pattern? Create a new interface called IConnectStrategy:

interface IConnectStrategy
{
    void Connect();
}

Add the connectstrategy as an argument to the method void CreateOrder(IConnectStrategy connectStrategy) in ITradingApi and let each vendor create/specify their own method for connecting. E.g. for one vendor create:

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

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

(Connect might not be the best name or even what you are actually doing, but please apply it to whatever works for your project.)

Edit after comments:
Create a strategy that only have contracts for each method that have vendor-specific parameters. Then add a method void SetVendorStrategy(IVendorStrategy vendorStrategy) (or a property) to the ITradingAPI-interface. Each implementation of the strategy has their own constructor with their own parameters, and each method (that require vendor specific parameters) in each implementation of the ITradingAPI-interface simply calls vendorStrategy.DoSomethingWithVendorSpecificData().

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文