具有非依赖参数的构造函数注入
我有一个如下所示的接口 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
的相应实现。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
发布评论
评论(6)
好吧,我的两分钱,我不确定你知道什么。这只是为了帮助和尝试...
我们为您的 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 不需要额外的参数,我们将逻辑推到访问者类中。
仅当我们知道谁是访问者时,才可能创建此访问者类,因此,肯定是在运行时
希望它能给您一些视角。我不知道整个理论是否可以应用于您的具体情况。
无论如何,我最好的;)
解决方案是使用我的问题的更新部分中概述的方法。 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
类以使用表达式树直接处理这些属性,可以进一步改进以使其更易于使用。如果有人对这种先进的工厂设计感兴趣,请发表评论。
尝试类似于策略模式怎么样?创建一个名为 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()
。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
根据迄今为止在此提供的信息,我想指出一两件事:
首先,具体的配置值是否在组合时提供,或者在运行时真正首先在用户输入时可用,这会产生巨大的影响。不同之处。只要它们可以在组合时解决,事情就很容易,因为您可以简单地从环境中读取值并将它们提供给适当的构造函数。因此,对于这个答案的其余部分,我将假设事情要困难得多,并且您实际上需要在运行时从用户那里获取这些值。
我宁愿对实际发生的情况进行建模,而不是尝试提出通用配置 API。在这种情况下,我觉得我们正在从用户那里收集配置值,那么为什么不明确地对此进行建模呢?
Product Trader
定义如下接口:
这里假设
apiType
可以转换为 ITradingApi,但这不能由编译器强制执行。 (我之所以称其为“交易者”,是因为这是产品交易者模式 (PLoPD 3) 的变体。)这与以前有何不同?
那么,您可以通过显示每种类型的 ITradingApi 的用户界面来实现 Create 方法。每个具体用户界面都会收集其自己的具体 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:
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:
Perhaps you can also do this (although I haven't tried to compile this):
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.