我正在使用一个实现不同类型网络协议的库,如下面的简化示例,希望能够说明我遇到的问题。注意:这只是为了显示整体问题的伪代码。
class Network
{
virtual void connect() {...}
void readPacket() = 0;
};
class NetworkClient : public Network
{
virtual void connect(int ip, int port) {super::connect() ...}
};
class NetworkServer : public Network
{
virtual void connect(int port) {super::connect() ...}
};
class ProtocolAClient : public NetworkClient
{
void readPacket() {...}
};
class ProtocolAServer : public NetworkServer
{
void readPacket() {...}
};
class ProtocolBClient : public NetworkClient
{
void readPacket() {...}
};
class ProtocolBServer : public NetworkServer
{
void readPacket() {...}
};
现在在我的应用程序中,我希望有一个客户端和一个服务器,它应该是ProtocolA或ProtocolB客户端/服务器,具体取决于用户选择连接的协议。
所以我认为我可以像这样创建我的应用程序类。
class AppClient : public NetworkClient
{
... custom functionality needed by the app client ...
void sendAppData(...)
};
class AppServer : public NetworkServer
{
... custom functionality needed by the app server ...
void sendAppData(...)
};
然后我想当我需要一个客户端时,例如,在应用程序中我可以这样做。
AppClient *client;
if(useProtocalA)
client = new ProtocolAClient;
else
client = new ProtocolBClient;
client->sendAppData();
但编译器很快就让我知道这是不可能的,因为ProtocolAClient和ProtocolBClient不是AppClient。这是确切的编译器错误。
error C2440: '=' : cannot convert from 'ProtocolAClient *' to 'AppClient *'
所以我的下一个想法是创建AppClient和AppServer模板类,但这不起作用,因为你不能获得指向实例的指针,因为指针没有像这样的模板参数。
AppClient *client; <--- Uh oh... missing template argument!
if(useProtocalA)
client = new AppClient<ProtocolAClient>;
else
client = new AppClient<ProtocolBClient>;
client->sendAppData();
这似乎应该是一个很难解决的问题,但我似乎无法看到解决方案。
答案 0 :(得分:1)
所以你有以下课程:
class Network
{
virtual void connect() {...}
void readPacket() = 0;
}
class NetworkClient : public Network
{
virtual void connect(int ip, int port) {super::connect() ...}
}
class AppClient : public NetworkClient
{
... custom functionality needed by the app client ...
void sendAppData(...)
}
class ProtocolAClient : public NetworkClient
{
void readPacket() {...}
}
class ProtocolBClient : public NetworkClient
{
void readPacket() {...}
}
问题是您需要AppClient
个对象,但ProtocolAClient
的类型为NetworkClient
,而不是AppClient
类型。
你的继承看起来像这样:
Network ---- NetworkClient ---- ProtocolAClient
|---- ProtocolBClient
|---- AppClient
正如您所看到的,ProtocolAClient
和ProtocolBClient
都不属于AppClient
类型,但所有类型都属于NetworkClient
类型。
因此,如果您希望此代码有效:
AppClient *client;
if(useProtocalA)
client = new ProtocolAClient;
else
client = new ProtocolBClient;
client->sendAppData();
您必须执行以下更改之一:
A)更改您的ProtocolAClient
和ProtocolBClient
以继承AppClient
类:
class ProtocolAClient : public AppClient
{
void readPacket() {...}
}
class ProtocolBClient : public AppClient
{
void readPacket() {...}
}
现在你的继承看起来像这样:
Network ---- NetworkClient ---- AppClient ---- ProtocolAClient
|---- ProtocolBClient
或B) - 推荐:不要使用AppClient,因为你已经有了NetworkClient:
NetworkClient*client;
if(useProtocalA)
client = new ProtocolAClient;
else
client = new ProtocolBClient;
client->sendAppData();
答案 1 :(得分:1)
我想我会立刻停下来:
class Network {};
class NetworkClient : public Network {};
class NetworkServer : public Network {};
乍一看,这看起来像是一个严重的问题。公共继承意味着Liskov替换原则 - 您应该能够在需要基类的实例的任何地方替换派生类的实例。
NetworkClient(或NetworkServer)不是网络,我怀疑甚至一个情况,其中任何一个都可以替代网络(更不用说所有可能的情况) 。因此,在我看来,您的基本设计从一开始就存在问题。
我的直接反应是你可能想要这样的东西:
class ProtocolA {};
class ProtocolB {};
template <class Protocol>
class Client {};
template <class Protocol>
class Server {};
通过这种方式,您可以使用任一协议轻松实例化客户端或服务器。但这不支持 ,支持运行时替换。也就是说,你不会得到类似基类的东西,它可以让你完全透明地(在运行时)处理可能是两种或更多种类型的对象。
如果你真的需要,你可能想要更像的东西:
class Protocol {};
class ProtocolA : public Protocol {};
class ProtocolB : public Protocol {};
class Client {
Protocol *p;
public:
Client(Protocol *p) : p(p) {}
};
class Server {
Protocol *p;
public:
Server(Protocol *p) : p(p) {}
};
[旁白:尽管合理地遵循了语法,但这实际上是伪代码而不是C ++。在代码实际可以使用之前,您显然需要添加许多其他内容,例如虚拟析构函数。]
然后,您实例化一个正确的Protocol实例,并传递(指向该实例)以创建一个Client或Server对象。
使用其中任何一个,客户端/服务器使用协议进行通信,但(与公共继承不同)我们没有做出关于能够替换客户端/服务器的无意义断言网络(或网络协议)。
答案 2 :(得分:0)
对我来说,你的类层次结构没有精确定义。特别是:NetworkClient
类到底是什么以及你打算用它做什么?如果要使用它以便必须调用client->sendAppData()
,那么ProtocolAClient
类不应该从中派生。你给出的这个解决方案似乎是最好的:
AppClient *client; <--- Uh oh... missing template argument!
if(useProtocalA)
client = new AppClient<ProtocolAClient>;
else
client = new AppClient<ProtocolBClient>;
client->sendAppData();
但是你说你没有指定协议客户端类就无法拥有这个AppClient
。
您可以使用虚拟模板对习惯用法(或其使用的任何名称)来解决此问题:
class AppClient { /* ... */ virtual void sendAppData() = 0; };
template <class ProtocolClientClass>
class AppClientSpec: public AppClient
{
/* ... */
ProtocolClientClass* inproto; // or whatever...
virtual void sendAppData() { return inproto->sendAppData(); }
};
现在您的示例可以修复为:
AppClient *client; <--- Uh oh... missing template argument!
if(useProtocalA)
client = new AppClientSpec<ProtocolAClient>;
else
client = new AppClientSpec<ProtocolBClient>;
client->sendAppData();