构建通用通信接口



我在为具有不同连接设置的多种通信类型构建抽象接口时遇到问题。我希望能够使用某种工厂来实例化其中一种通信类型(USB、串行、tcp(及其适当的连接参数,而无需将其分支到任何地方的一堆 if 语句中以检查类型并使用单个接口来控制与指定通信类型的所有交互。

下面的一些代码可以帮助说明我的问题:

public interface ITransport : IDisposable
{
event EventHandler Connected;
event EventHandler Disconnected;
event EventHandler<byte[]> SentData;
event EventHandler<byte[]> ReceivedData;
event EventHandler<string> ErrorOccurred;
event HostCertificateReceivedDelegate HostValidation;
Task<bool> ConnectAsync(TransportConnectionArgs connectionArgs);
Task<bool> SendAsync(byte[] buffer);
void Disconnect();
}
public class TransportConnectionArgs
{
public static readonly TransportConnectionArgs Empty = new TransportConnectionArgs();
protected TransportConnectionArgs() { }
}
public class SshTransportConnectionArgs : TransportConnectionArgs
{
public string Host { get; }
public int Port { get; }
public string Username { get; }
public string Password { get; }
public SshTransportConnectionArgs(string host, int port, string username = "root", string password = "")
{
Host = host;
Port = port;
Username = username;
Password = password;
}
}
public class SerialTransportConnectionArgs : TransportConnectionArgs
{
... does not need password but needs com port, baud, etc...
}
public class UsbTransportConnectionArgs : TransportConnectionArgs
{
... does not need address or port or serial connection settings
}
public sealed class SshDebugClient : ITransport
{
public async Task<bool> ConnectAsync(SshTransportConnectionArgs connectionArgs)
{
... access properties specific to a regular TCP connection
}
}
public sealed class SerialDebugClient : ITransport
{
public async Task<bool> ConnectAsync(SerialTransportConnectionArgs connectionArgs)
{
... access properties specific to a regular serial/COM connection
}
}
public sealed class UsbDebugClient : ITransport
{
public async Task<bool> ConnectAsync(UsbTransportConnectionArgs connectionArgs)
{
... access properties specific to a regular USB connection
}
}

嗯,好吧,这取决于你要找什么,这要么非常简单,要么非常困难。

您是否正在寻找args类的工厂? 如果是这样,您将度过一段艰难的时光 - 因为它们都具有您必须填充的不同属性。 您不希望全局工厂构造函数接收每个可能的字段名称,并根据填充的值决定使用哪种类型。

但。。。如果您只是在为 ITransport 寻找构造函数? 然后你可以简单地做:

public class Factory
{
public static ITransport CreateTransport(TransportConnectionArgs args)
{
if (args is SshTransportConnectionArgs)
return new SshDebugClient((SshTransportConnectionArgs)args);
if (args is SerialTransportConnectionArgs)
return new SerialDebugClient((SerialTransportConnectionArgs)args);
// etc
}
}

。或者,如果你真的反对那些IF线?

使用反射查找实现 ITransport 的所有非抽象类,向 ITransport 添加一个附加方法,要求实现者返回他们使用的参数类型,然后执行以下操作:

public static ITransport CreateTransport(TransportConnectionArgs args)
{
List<ITransport> allTranports = GetAllImplementersOf<ITransport>();
foreach(ITransport transport in allTransports)
{
if (transport.NeededArgType == typeof(args))
return transport;
}
}

。不过,最终,我认为一个问题是你的两个类之间没有"钩子"。 您有一个传输类型,并且您有一个参数类型...但是它们之间没有任何链接。 我想知道您是否应该让 args 成为传输可以创建/解析的东西,而不是为它提供一个单独的独立类。

只是一个快速且可能不那么干净的想法。

如果TransportConnectionArgs依赖项从ITransport接口移动到具体的实现构造函数,您将能够实现相当通用的实例化方法。

ITransport Create(TransportConnectionArgs connectionOptions)
{
if (connectionOptions is SshTransportConnectionArgs sshOptions) 
{
return new SshDebugClient(sshOptions);
} 
else if (...)
...
else 
{
throw new NotSupportedException("Unknown connection type.");
}
}
var transport = Create(new SshTransportConnectionArgs { ... });
var connected = await transport.ConnectAsync();

然而,如果单个ITransport实例预计将与多个TransportConnectionArgs实例重用,即预期使用不同的选项多次调用ConnectAsync,这将不起作用。

最新更新