这是一个通用的C ++设计问题。
我正在编写一个使用客户端/服务器模型的应用程序。现在我正在编写服务器端。许多客户已经存在(一些由我自己编写,另一些由第三方编写)。问题是这些现有客户端都使用不同的协议版本(多年来已经有2-3次协议更改)。
由于我正在重写服务器,我认为现在是设计我的代码的好时机,这样我就可以透明地处理许多不同的协议版本。在所有协议版本中,来自客户端的第一个通信包含协议版本,因此对于每个客户端连接,服务器都确切地知道它需要谈论哪个协议。
执行此操作的天真方法是使用以下语句来丢弃代码:
if (clientProtocolVersion == 1)
// do something here
else if (clientProtocolVersion == 2)
// do something else here
else if (clientProtocolVersion == 3)
// do a third thing here...
由于以下原因,此解决方案非常糟糕:
if (clientProtoVersion == 5 || clientProtoVersion == 6)
。我正在寻找的是一种使用C ++语言的功能智能地处理不同协议的方法。我已经考虑过使用模板类,可能使用模板参数指定协议版本,或者可能是类层次结构,每个不同的协议版本都有一个类......
我确信这是一种非常常见的设计模式,所以很多人以前一定遇到过这个问题。
修改
你们中的许多人都建议继承heirarchy,最旧的协议版本在顶部,就像这样(请原谅我的ASCII艺术):
IProtocol
^
|
CProtoVersion1
^
|
CProtoVersion2
^
|
CProtoVersion3
......就重复使用而言,这似乎是一件明智的事情。但是,当您需要扩展协议并添加基本的新消息类型时会发生什么?如果我在IProtocol
中添加虚拟方法,并在CProtocolVersion4
中实现这些新方法,那么在早期协议版本中如何处理这些新方法?我想我的选择是:
EDIT2:
除上述问题外,当较新的协议消息需要比旧版本更多的输入时会发生什么?例如:
在protocl版本1中,我可能有:
ByteArray getFooMessage(string param1, int param2)
在协议版本2中,我可能想要:
ByteArray getFooMessage(string param1, int param2, float param3)
两个不同的协议版本现在有不同的方法签名,这很好,除了它强制我通过所有调用代码并将所有调用更改为2个参数到3个参数,具体取决于所使用的协议版本,这是我首先要避免的是什么!
将协议版本信息与其他代码分开的最佳方法是什么,以便隐藏当前协议的具体内容?
答案 0 :(得分:10)
由于您需要动态选择要使用的协议,因此使用不同的类(而不是模板参数)来选择协议版本似乎是正确的方法。基本上这是战略模式,但如果你想要真正精心设计,访客也是可能的。
由于这些是同一协议的所有不同版本,您可能在基类中有共同的东西,然后是子类的差异。另一种方法可能是使基类用于最旧版本的协议,然后让每个后续版本都具有继承自先前版本的类。这是一个有点不寻常的继承树,但它的好处是它保证为更高版本所做的更改不会影响旧版本。 (我假设旧版协议的类会很快稳定下来,然后很少会改变。
但是,如果您决定组织层次结构,则需要在知道协议版本后立即选择协议版本对象,然后将其传递给需要“协商”协议的各种事物。 / p>
答案 1 :(得分:4)
我已经使用(并听说其他人使用)模板来解决这个问题。我们的想法是将您的不同协议分解为基本的原子操作,然后使用boost::fusion::vector之类的东西来构建各个块之外的协议。
以下是一个非常粗糙(缺少很多部分)的例子:
// These are the kind of atomic operations that we can do:
struct read_string { /* ... */ };
struct write_string { /* ... */ };
struct read_int { /* ... */ };
struct write_int { /* ... */ };
// These are the different protocol versions
typedef vector<read_string, write_int> Protocol1;
typedef vector<read_int, read_string, write_int> Protocol2;
typedef vector<read_int, write_int, read_string, write_int> Protocol3;
// This type *does* the work for a given atomic operation
struct DoAtomicOp {
void operator ()(read_string & rs) const { ... }
void operator ()(write_string & ws) const { ... }
void operator ()(read_int & ri) const { ... }
void operator ()(write_int & wi) const { ... }
};
template <typename Protocol> void doProtWork ( ... ) {
Protocl prot;
for_each (prot, DoAtomicOp (...));
}
因为protocl版本是动态的,所以你需要一个顶级switch语句来确定使用哪个protocl。
void doWork (int protocol ...) {
switch (protocol) {
case PROTOCOL_1:
doProtWork<Protocol1> (...);
break;
case PROTOCOL_2:
doProtWork<Protocol2> (...);
break;
case PROTOCOL_3:
doProtWork<Protocol3> (...);
break;
};
}
要添加新协议(使用现有类型),您只需要定义protocl序列:
typedef vector<read_int, write_int, write_int, write_int> Protocol4;
然后在switch语句中添加一个新条目。
答案 2 :(得分:0)
我倾向于使用不同的类来为同一接口的不同协议实现适配器。
根据协议和差异,您可能会使用TMP获取状态机或协议详细信息,但生成六组使用六种协议版本的任何代码可能都不值得;运行时多态性就足够了,对于大多数情况来说,TCP IO可能足够慢,不想硬编码所有内容。
答案 3 :(得分:0)
可能过于简单,但这听起来像是继承的工作? 一个基类IProtocol,它定义了协议的功能(可能还有一些常用的方法),然后为每个协议实现IProtocol的一个实现?
答案 4 :(得分:0)
您需要一个协议工厂,为适当的版本返回协议处理程序:
ProtocolHandler& handler = ProtocolFactory::getProtocolHandler(clientProtocolVersion);
然后,您可以使用继承来仅更新版本之间已更改的协议部分。
请注意,ProtocolHandler(基类)基本上是一种模式。因此它不应该保持自己的状态(如果需要通过方法传递,并且stratergy将更新状态对象的属性)。
因为stratergy不维护状态我们可以在任意数量的线程之间共享ProtcolHandler,因此所有权不需要离开工厂对象。因此工厂只需要为它理解的每个协议版本创建一个处理程序对象(这甚至可以懒得完成)。因为工厂对象保留所有权,您可以返回协议处理程序的引用。
答案 5 :(得分:0)
我会同意Pete Kirkham的观点,我认为维护6个不同版本的类来支持不同的协议版本会非常不愉快。如果你能做到这一点,似乎最好让旧版本只实现适配器转换为最新协议,这样你只需要维护一个工作协议。你仍然可以使用如上所示的继承层次结构,但旧的协议实现只需要进行调整然后调用最新的协议。