构建通用通信接口

Building a common communication interface

我在为具有不同连接设置的多种通信类型构建抽象接口时遇到问题。我希望能够使用某种工厂来实例化其中一种通信类型(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 class 的工厂?如果是这样,您将度过一段艰难的时光——因为它们都有不同的属性,您必须填充这些属性。您不希望全局工厂构造函数接受每个可能的字段名称并根据填充的值决定使用哪种类型。

但是...如果您只是在寻找 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 的非抽象 classes,向 ITransport 添加一个额外的方法,要求实现者 return 他们使用的 args 类型,然后执行类似于:

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

...不过,最终,我认为一个问题是您的两个 class 之间没有 'hook'。你有一个传输类型,你有一个参数类型……但它们之间没有任何链接。我的一部分想知道你是否应该让 args 成为 Transport 可以 create/parse 的东西,而不是为它单独设置 class。

只是一个快速的想法,可能不是那么清晰。

如果将 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选项。