bitm0de
bitm0de

Reputation: 45

Building a common communication interface

I'm having trouble building an abstract interface for multiple communication types that have different settings to connect. I would like to be able to use a factory of some sort to instantiate one of the communication types (usb, serial, tcp) with their appropriate connection arguments without branching this off into a bunch of if statements everywhere to check the type and to use a single interface to control all the interaction with the specified communication type.

Some code below to help illustrate my problem:

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

Upvotes: 1

Views: 641

Answers (2)

Uladz
Uladz

Reputation: 1978

Just a quick and, probably, not so clean idea.

If you move TransportConnectionArgs dependency from your ITransport interface to concrete implementations constructors, you will be able to implement rather generic instantiation method.

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();

Nevertheless this won't work for sure if a single ITransport instance is expected to be reused with multiple TransportConnectionArgs instances, i.e expected to have multiple invocations of ConnectAsync with different options.

Upvotes: 1

Kevin
Kevin

Reputation: 2243

Hmmm, okay, depending on what you're looking for, this is either pretty easy or incredibly difficult.

Are you looking for a factory for the args class? If so, you're going to have a rough time - since they all have different properties that you have to populate. You don't want a global factory constructor that takes in every single possible field name and decides which type to use based on which values were populated.

But... if you're just looking for a constructor for the ITransport? Then you can simply do:

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

... or, if you're really opposed to those if lines?

Use reflection to find all non-abstract classes that implement ITransport, add an additional method to the ITransport, requiring implementers to return the type of args they work with, and then do something like:

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

... Ultimately, though, I think one problem is that there's no 'hook' between your two classes. You've got a transport type, and you've got an argument type... but you don't have any linking between them. Part of me wonders if you should have the args be something that the Transport can create/parse, instead of having a separate independent class for it.

Upvotes: 1

Related Questions