在C++中使用模板而不是桥接模式



我有三种类型的数据链路:RS485,I2C和蓝牙。每个数据链路都具有连接、读取和写入数据等功能。在PC软件上,我必须实现应用程序/协议层才能与设备一起使用。 在我之前关于 OOP 的问题中,我得到了使用桥接模式或工厂方法的答案,但我认为这可以做得更好。 我会问使用模板来完成这项任务是否更好。这是我想要如何使用它的简单示例:

// Low level datalink class
class RS485
{
public:
void send(const char *data) {
// datalink function to send data using RS485
printf("RS485: %s n", data);
}
};
class I2C
{
public:
void send(const char *data) {
// datalink function to send data using I2C
printf("I2C: %s n", data);
}
};
class BT
{
public:
void send(const char *data) {
// datalink function to send data using Bluetooth
printf("BT %s n", data);
}
};
// Protocol class
template<typename Comm>
class MODBUS
{
public:
MODBUS(Comm *c) { _c = c; }
void send(const char *data) {
printf("MODBUSn");
_c->send(data);
}
private:
Comm *_c;
};
// Protocol class
template<typename Comm>
class TCP
{
public:
TCP(Comm *c) { _c = c; }
void send(const char *data) {
printf("TCPn");
_c->send(data);
}
private:
Comm *_c;
};
int main() {
// Modbus over I2C
I2C *i2c = new I2C();
MODBUS<I2C> *mb_i2c = new MODBUS<I2C>(i2c);
mb_i2c->send("Data ...");
// Modbus over RS485
RS485 *rs = new RS485();
MODBUS<RS485> *mb_rs = new MODBUS<RS485>(rs);
mb_rs->send("Data ...");
// Tcp over Modbus over RS485
TCP< MODBUS<RS485> > *tcp_modbus_rs = new TCP< MODBUS<RS485> >(mb_rs);
tcp_modbus_rs->send("Data ...");
return 0;
}

你可以使用mixins-from-here来减少一点样板。它与您的示例没有太大区别,但您的代码较少,周围没有指针。您仍然可以(让我说(从其部分编写您的协议。
这是您重新设计的代码片段以使用它们:

#include<cstdio>
// Low level datalink class
struct RS485 {
void send(const char *data) {
// datalink function to send data using RS485
printf("RS485: %s n", data);
}
};
struct I2C {
void send(const char *data) {
// datalink function to send data using I2C
printf("I2C: %s n", data);
}
};
struct BT {
void send(const char *data) {
// datalink function to send data using Bluetooth
printf("BT %s n", data);
}
};
// Protocol class
template<typename Comm>
struct MODBUS: private Comm {
void send(const char *data) {
printf("MODBUSn");
Comm::send(data);
}
};
// Protocol class
template<typename Comm>
struct TCP: private Comm {
void send(const char *data) {
printf("TCPn");
Comm::send(data);
}
};
int main() {
// Modbus over I2C
MODBUS<I2C> mb_i2c{};
mb_i2c.send("Data ...");
// Modbus over RS485
MODBUS<RS485> mb_rs{};
mb_rs.send("Data ...");
// Tcp over Modbus over RS485
TCP< MODBUS<RS485> > tcp_modbus_rs{};
tcp_modbus_rs.send("Data ...");
}

如果部件具有接受不同参数列表的构造函数,则可以使用转发引用和模板化构造函数来满足要求。
举个例子:

// Protocol class
template<typename Comm>
struct MODBUS: private Comm {
template<typename... T>
MODBUS(T&&... t): Comm{std::forward<T>(t)...} {}
void send(const char *data) {
printf("MODBUSn");
Comm::send(data);
}
};

以下是以这种方式重新设计的完整示例:

#include<cstdio>
#include<utility>
// Low level datalink class
struct RS485 {
RS485(int) {}
void send(const char *data) {
// datalink function to send data using RS485
printf("RS485: %s n", data);
}
};
struct I2C {
I2C(char, double) {}
void send(const char *data) {
// datalink function to send data using I2C
printf("I2C: %s n", data);
}
};
struct BT {
void send(const char *data) {
// datalink function to send data using Bluetooth
printf("BT %s n", data);
}
};
// Protocol class
template<typename Comm>
struct MODBUS: private Comm {
template<typename... T>
MODBUS(T&&... t): Comm{std::forward<T>(t)...} {}
void send(const char *data) {
printf("MODBUSn");
Comm::send(data);
}
};
// Protocol class
template<typename Comm>
struct TCP: private Comm {
template<typename... T>
TCP(T&&... t): Comm{std::forward<T>(t)...} {}
void send(const char *data) {
printf("TCPn");
Comm::send(data);
}
};
int main() {
// Modbus over I2C
MODBUS<I2C> mb_i2c{'c', .3};
mb_i2c.send("Data ...");
// Modbus over RS485
MODBUS<RS485> mb_rs{42};
mb_rs.send("Data ...");
// Tcp over Modbus over RS485
TCP< MODBUS<RS485> > tcp_modbus_rs{23};
tcp_modbus_rs.send("Data ...");
}

根据经验 - 不要优化(还(。如果对send的虚拟调用不是瓶颈,为什么还要用具有更多样板代码的模板替换接口呢?

从您的代码来看,似乎没有必要追求任何模式 - 只需对这些类进行硬编码,您将更快地完成工作。

编辑:如果你真的想把协议链接在一起,这里有一个功能方法:

struct TCP
{
void onsend(const char* data) {}
};
struct MODBUS
{
void onsend(const char* data) {}
};
struct RS485
{
void onsend(const char* data) {}
};
template<typename F, typename Prot, typename... TProtocols>
auto channel(F&& f, Prot&& prot, TProtocols&&... protocols)
{
return [&](const char* data)
{
f(prot, data);
channel(f, protocols...)(data);
};
}
template<typename F, typename Prot>
auto channel(F&& f, Prot&& prot)
{
return [&](const char* data)
{
f(prot, data);
};
}
int main()
{
TCP tcp;
MODBUS modbus;
RS485 rs;
auto chan = channel([](auto&& protocol, const char* data)
{
protocol.onsend(data);
}, 
tcp, modbus, rs);
const char msg[] = "asdfasdf";
chan(msg);
}

基本上,您希望对象一个接一个地接收消息,谁说它们的类型需要相关?

在这种情况下,模板解决方案似乎是一个坏主意。

你真的希望对象的类型取决于"实现"的内容吗?

使用虚函数似乎是正确的方法(将指向较低级别通道的指针作为构造函数中的基类指针传递(。

虚函数方法需要使用指针并仔细处理生存期,但为此,标准解决方案是使用智能指针。

#include <stdio.h>
#include <memory>
struct DataLink {
virtual void send(const char *data) = 0;
virtual ~DataLink(){}
};
typedef std::shared_ptr<DataLink> DLPtr;
struct RS485 : DataLink {
void send(const char *data) { printf("RS485: %s n", data);}
};
struct I2C : DataLink {
void send(const char *data) { printf("I2C: %s n", data); }
};
struct BT : DataLink {
void send(const char *data) { printf("BT %s n", data); }
};
struct MODBUS : DataLink {
DLPtr channel;
MODBUS(const DLPtr& channel) : channel(channel) {}
void send(const char *data) {
printf("MODBUSn");
channel->send(data);
}
};
struct TCP : DataLink {
DLPtr channel;
TCP(const DLPtr& channel) : channel(channel) {}
void send(const char *data) {
printf("TCPn");
channel->send(data);
}
};
int main() {
DLPtr dl1(new MODBUS(DLPtr(new I2C)));
dl1->send("data ...");
DLPtr dl2(new MODBUS(DLPtr(new RS485)));
dl2->send("data ...");
DLPtr dl3(new TCP(DLPtr(new MODBUS(DLPtr(new RS485)))));
dl3->send("data ...");
return 0;
}

虽然模板是可能的,但使用多态类并不复杂:

class Sender
{
public:
virtual ~Sender() = default;
virtual void send(const char *data) = 0;
};
// Low level datalink class
class RS485 : public Sender
{
public:
void send(const char *data) override {
// datalink function to send data using RS485
printf("RS485: %s n", data);
}
};
class I2C: public Sender
{
public:
void send(const char *data) override {
// datalink function to send data using I2C
printf("I2C: %s n", data);
}
};
class BT : public Sender
{
public:
void send(const char *data) override {
// datalink function to send data using Bluetooth
printf("BT %s n", data);
}
};
// Protocol class
class MODBUS : public Sender
{
public:
explicit MODBUS(Sender* sender) : sender(sender) {}
void send(const char *data) override {
printf("MODBUSn");
sender->send(data);
}
private:
Sender *sender;
};
// Protocol class
class TCPS : public Sender
{
public:
expolicit TCP(Sender* sender) : sender(sender) {}
void send(const char *data) override {
printf("TCPn");
sender->send(data);
}
private:
Sender* sender;
};
int main() {
// Modbus over I2C
I2C i2c;
MODBUS mb_i2c(&i2c);
mb_i2c.send("Data ...");
// Modbus over RS485
RS485 rs;
MODBUS mb_rs(&rs);
mb_rs.send("Data ...");
// Tcp over Modbus over RS485
TCP tcp_modbus_rs(mb_rs);
tcp_modbus_rs.send("Data ...");
}
// strong typedef:
struct sink:std::function<void(char const*)>{
using std::function<void(char const*)>::function; // inherit ctors
};
using step=std::function<void(char const*, sink)>;
inlne sink operator|( step s, sink e ){
return [=](char const* data){
s( data, e );
};
}
inlne step operator|( step one, step two ){
return [=](char const* data, sink end){
two( data, one|end );
};
}

现在我们可以链接了。

step fake_step(sts::string name){
return [name](char const* data, sink s){
std::cout<<name<<": n";
s(data);
};
}
auto tcp=fake_step("tcp");
auto modbus=fake_step("modbus");
sink fake_sink(std::string name){
return [name](char const* data){
std::cout << name << ": " << data << "n";
};
}
auto ABC=fake_sink("ABC");
auto XYZ=fake_sink("XYZ");

auto tcp_over_xyz = tcp|XYZ;

这使用类型擦除;CRTP 或 Koenig 操作员可以删除该擦除。 更多的样板文件,并且只有在您首先分析性能影响时才这样做。

这使用 std::function;你可以用其他方式进行组合。

有点晚了。问题是关于静态多态性与动态多态性。有人认为静态多态性更好 - 更安全,并且还可能导致更好的性能。

纯虚函数和抽象类的想法是强制执行接口。在这里,datalink服务于这一目的。有了 C++20 中的concepts,现在可以轻松实现编译时接口。

以下是我对上述内容的看法(大部分相同,只是添加了datalink概念:

#include <concepts>
#include <iostream>
template <typename T>
concept datalink = requires(T&& t) {
{ t.send(std::declval<const char*>()) } -> std::same_as<void>;
};
class rs485 {
public:
void send(char const *data) { std::cout << "rs485: " << data << 'n'; }
};
class i2c {
public:
void send(char const *data) { std::cout << "i2c: " << data << 'n'; }
};
class bt {
public:
void send(char const *data) { std::cout << "bt: " << data << 'n'; }
};
template <datalink Comm> 
class modbus {
private:
Comm m_comm;
public:
modbus(Comm const &comm) : m_comm{comm} {}
void send(char const *data) {
std::cout << "modbus over ";
m_comm.send(data);
}
};
template <datalink Comm> 
class tcp {
private:
Comm m_comm;
public:
tcp(Comm const &comm) : m_comm{comm} {}
void send(char const *data) {
std::cout << "tcp over ";
m_comm.send(data);
}
};
int main() {
i2c i2c_inst{};
modbus<i2c> mb_i2c_inst{i2c_inst};
mb_i2c_inst.send("Data...");
rs485 rs485_inst{};
modbus<rs485> mb_rs485_inst{rs485_inst};
mb_rs485_inst.send("Data...");
tcp<modbus<rs485>> tcp_mb_rs485_inst{mb_rs485_inst};
tcp_mb_rs485_inst.send("Data...");
}

最新更新