使用泛型接口交换客户端代码中的实现

时间:2019-05-03 18:00:19

标签: c# generics interface abstraction

我正在尝试为消息传递队列(例如RabbitMQ)创建抽象,但是遇到了问题。

假设我有以下内容:

interface IMessagingQueue { }

interface IMessagingExchange
{
    Bind(IMessagingQueue queue);
}

class RabbitQueue : IMessagingQueue { }

class RabbitExchange : IMessagingExchange
{
    // FIX
    Bind(IMessagingQueue queue) { }
}

class InMemoryQueue : IMessagingQueue {  }

这很好用,因为客户端不依赖于特定的实现。示例:

class Client
{
    public Client(IMessagingExchange exchange) { }
}

但是,在我标记为// FIX的地方出现了问题。

在此示例中,InMemoryQueue可以通过RabbitExchange传递到Bind,这在体系结构上没有意义。

当然,我可以介绍泛型la:

interface IMessagingExchange<TQueue> where TQueue : IMessagingQueue
{
    Bind(TQueue queue);
}

但是,由于引入了泛型,现在客户端代码已绑定到实现:

class RabbitExchange : IMessagingExchange<RabbitQueue> { }

class Worker
{
    public Worker(RabbitExchange exchange) { }
}

这确实引入了编译时安全性,但是却牺牲了交换实现的能力。

我是否想得太多?有办法解决这个问题吗?

1 个答案:

答案 0 :(得分:1)

您希望能够无缝交换实现,但是不能只接受任何实现,因此这是我过去所做的事情,并且效果很好:

创建两个版本的接口,以便无需指定类型即可无缝交换:

interface IMessagingExchange
{
    void Bind(IMessagingQueue queue);
}

interface IMessagingExchange<TQueue> : IMessagingExchange where TQueue : IMessagingQueue
{
    void Bind(TQueue queue);
}

创建一个接口,以标识可与特定交换机一起使用的队列的子集。在这种情况下,我们确定Rabbit Exchange可以使用的队列:

interface IRabbitQueue : IMessagingQueue { }

RabbitQueue类现在实现了以下接口:

class RabbitQueue : IRabbitQueue { }

我们添加了一个基类来处理类型检查:

abstract class ExchangeBase<TQueue> : IMessagingExchange<TQueue>  where TQueue : class, IMessagingQueue
{
    public abstract void Bind(TQueue queue);

    public void Bind(IMessagingQueue queue)
    {
        var typedQueue = queue as TQueue;
        if (typedQueue == null)
            throw new InvalidOperationException($"This exchange only supports queues that implement {typeof(TQueue).FullName}");
        Bind(typedQueue);
    }
}

请注意,基类将实现未类型化的版本,为您进行类型检查,并将请求转发至具体类提供的类型化版本。

RabbitExchange类现在从基类继承并提供绑定逻辑:

class RabbitExchange : ExchangeBase<IRabbitQueue>
{
    public override void Bind(IRabbitQueue queue)
    {

    }
}

执行:

//Using untyped versions
IMessagingExchange exchange = new RabbitExchange();
IMessagingQueue queue = new RabbitQueue();

//This works fine
exchange.Bind(queue);

//Attempt to use the wrong queue
IMessagingQueue memoryQueue = new InMemoryQueue();
//This results in an error
exchange.Bind(memoryQueue);
  

此交换仅支持实现以下内容的队列   SomeNameSpace.IRabbitQueue

//We use a typed exchange this time
var rabbitExchange = exchange as RabbitExchange;

//This works
rabbitExchange.Bind(queue);
//This is still allowed because of the untyped interface, but causes an error because type is still checked
rabbitExchange.Bind(memoryQueue);

//Use a typed queue this time
var rabbitQueue = queue as RabbitQueue;
//This skips the base class validation because it calls the typed method in the concrete class
rabbitExchange.Bind(rabbitQueue);
相关问题