文章目录
- 一、平凡类型与非平凡类型什么时候使用set/get
- 1.平凡类型
- 2.非平凡类型
- 二、构造函数参数较多解决办法
- 1.把所有参数放到一个结构体里面
- 2.使用build设计模式
- 三、如果构造函数众多(参数很多)
- 1.模仿make_unique,就地构造
- 2.基于build设计模式只定义移动版本的成员函数
- 三、不同子类需要实现不同的接口,如何设计?
- 1.使用RTTI
- 2.定义接口接管RTTI
- 3.使用访问者模式接管RTTI
- 参考
一、平凡类型与非平凡类型什么时候使用set/get
1.平凡类型
平凡类型,里面的成员不使用set/get模式
C++版本:C++26
#include <print>struct Point {double x;double y;Point operator+(Point const &other) const {return Point(x + other.x, y + other.y);}
};int main() {Point a = Point{ .x = 1, .y = 2 }; // 等价于 Point{1, 2}Point b = Point{ .x = 2, .y = 3 }; // 等价于 Point{2, 3}Point c = a + b;std::println("{} {}", c.x, c.y);c.x = 1;return 0;
}
测试:
Program returned: 0
Program stdout
3 5
2.非平凡类型
非平凡类型,防止用户修改里面的成员,造成类不可以用,封装成set/get
#include <print>
#include <cstddef>struct Vector {
private:int *m_data;size_t m_size;public:Vector() : m_data(new int[4]), m_size(4) {}void setSize(size_t newSize) {m_size = newSize;delete[] m_data;m_data = new int[newSize];}int *data() const {return m_data;}size_t size() const {return m_size;}
};int main() {Vector v;v.setSize(14);v.setSize(11);return 0;
}
测试:
在这里插入代码片
二、构造函数参数较多解决办法
1.把所有参数放到一个结构体里面
要点:
- 参数里面的某些配置需要绑定在一起使用,则把这些封装成optional
- connection仅仅管理fd,在包装参数的类中调用connect,构造一个connection
#include <optional>
#include <print>
#include <chrono>
#include <string>using namespace std::chrono_literals;struct Connection {int fd;explicit Connection(int fd_) : fd(fd_) {}
};struct ConnectionBuilder {std::string serverAddress;int port;struct SSHParams {std::string sshCertPath = "";std::string sshPKeyPath = "";std::string sshCAFilePath = "";};std::optional<SSHParams> useSSH;std::string username = "admin";std::string password = "password";bool enableFastTCPOpen = true;int tlsVersion = 1;std::chrono::seconds connectTimeout = 10s;std::chrono::seconds readTimeout = 5s;Connection connect() {int fd = 0;// fd = open(serverAddress, port);return Connection(fd);}
};Connection c = ConnectionBuilder{.serverAddress = "localhost",.port = 8080,.useSSH = std::nullopt,}.connect();int main() {return 0;
}
2.使用build设计模式
多个参数的builder设计模式,同样可以解决某些参数需要绑定设置
- 给ConnectionBuilder 增加模板参数,用于标记什么时候能构造connection,因为前面with都是指定需要使用的参数嘛
- std::vector<std::string> args;可以支持动态增加参数
- [[nodiscard]]如果没有使用,则产生告警
#include <optional>
#include <chrono>
#include <string>
#include <vector>using namespace std::chrono_literals;struct Connection {int fd;explicit Connection(int fd_) : fd(fd_) {}Connection &read();
};struct ConnectionBuilderBase {std::string serverAddress;int port;bool useSSH = false;std::string sshCertPath = "";std::string sshPKeyPath = "";std::string sshCAFilePath = "";std::string username = "admin";std::string password = "password";bool enableFastTCPOpen = true;int tlsVersion = 1;std::chrono::seconds connectTimeout = 10s;std::chrono::seconds readTimeout = 5s;std::vector<std::string> args;
};template <bool Ready = false>
struct [[nodiscard]] ConnectionBuilder : ConnectionBuilderBase {[[nodiscard]] ConnectionBuilder<true> &withAddress(std::string addr) {serverAddress = addr;return static_cast<ConnectionBuilder<true> &>(static_cast<ConnectionBuilderBase &>(*this));}[[nodiscard]] ConnectionBuilder &withPort(int p) {port = p;return *this;}[[nodiscard]] ConnectionBuilder<true> &withAddressAndPort(std::string addr) {auto pos = addr.find(':');serverAddress = addr.substr(0, pos);port = std::stoi(addr.substr(pos + 1));return static_cast<ConnectionBuilder<true> &>(static_cast<ConnectionBuilderBase &>(*this));}[[nodiscard]] ConnectionBuilder &withSSH(std::string cert, std::string pkey, std::string caf = "asas") {useSSH = true;sshCertPath = cert;sshPKeyPath = pkey;sshCAFilePath = caf;return *this;}[[nodiscard]] ConnectionBuilder &addArg(std::string arg) {args.push_back(arg);return *this;}[[nodiscard]] Connection connect() {static_assert(Ready, "你必须指定 addr 参数!");int fd = 0;// fd = open(serverAddress, port);return Connection(fd);}
};Connection c = ConnectionBuilder<>().withSSH("1", "2").addArg("asas").addArg("bsbs").withAddressAndPort("localhost:8080").addArg("baba").connect();int main() {return 0;
}
三、如果构造函数众多(参数很多)
1.模仿make_unique,就地构造
struct Cake {int handle;explicit Cake(int han) : handle(han) {}static Cake makeOrig() {// 构造原味蛋糕int han = 0;return Cake(han);}static Cake makeChoco(double range) {// 构造巧克力蛋糕int han = (int)range;return Cake(han);}static Cake makeMoca(int flavor) {// 构造抹茶味蛋糕int han = flavor;return Cake(han);}
};Cake origCake = Cake::makeOrig();
Cake chocoCake = Cake::makeChoco(1.0);
Cake matchaCake = Cake::makeMoca(1);int main() {return 0;
}
2.基于build设计模式只定义移动版本的成员函数
右值引用版本的build设计模式,如果涉及到管理资源的类,可以使用这个
#include <utility>struct [[nodiscard]] Cake {int handle;Cake() {}[[nodiscard]] Cake &&setOrig() && {// 构造原味蛋糕handle = 0;return std::move(*this);}[[nodiscard]] Cake &&setChoco(double range) && {// 构造巧克力蛋糕handle = (int)range;return std::move(*this);}[[nodiscard]] Cake &&setMoca(int flavor) && {// 构造抹茶味蛋糕handle = flavor;return std::move(*this);}Cake(Cake &&) = default;Cake(Cake const &) = delete;
};void func(Cake &&c) {}
void func(Cake const &c);Cake origCake = Cake().setOrig().setChoco(1.0);
Cake chocoCake = Cake().setChoco(1.0);
Cake matchaCake = Cake().setMoca(1);
int main() {Cake c;std::move(c).setOrig();Cake().setOrig();func(std::move(c));return 0;
}
三、不同子类需要实现不同的接口,如何设计?
如果不同的子类需要实现不同的接口,就把这些接口单独拎出来分别使用接口继承。
- 注意:使用虚继承,否则padding类就有两个food虚基类
- C++多用接口继承,少用实现继承
1.使用RTTI
使用dynamic_cast统一接管
#include <print>struct EatParams {int amount;int speed;
};struct DrinkParams {int volume;int temperature;
};struct Food {virtual ~Food() = default;
};struct Drinkable : virtual Food {virtual void drink(DrinkParams drinkParams) = 0;
};struct Eatable : virtual Food {virtual void eat(EatParams eatParams) = 0;
};struct Cake : Eatable {void eat(EatParams eatParams) override {std::println("Eating cake...");std::println("Amount: {}", eatParams.amount);std::println("Speed: {}", eatParams.speed);}
};struct Milk : Drinkable {void drink(DrinkParams drinkParams) override {std::println("Drinking milk...");std::println("Volume: {}", drinkParams.volume);std::println("Temperature: {}", drinkParams.temperature);}
};struct Pudding : Eatable, Drinkable {void eat(EatParams eatParams) override {std::println("Eating pudding...");std::println("Amount: {}", eatParams.amount);std::println("Speed: {}", eatParams.speed);}void drink(DrinkParams drinkParams) override {std::println("Drinking pudding...");std::println("Volume: {}", drinkParams.volume);std::println("Temperature: {}", drinkParams.temperature);}
};void dailyRun(Food* food)
{if (auto eat = dynamic_cast<Eatable*>(food)){ eat->eat({5,100});}if (auto drink = dynamic_cast<Drinkable*>(food)){ drink->drink({5,100});}}int main() {Cake cake;Milk milk;Pudding pudding;dailyRun(&cake);dailyRun(&milk);dailyRun(&pudding);return 0;
}
测试:
Program returned: 0
Program stdout
Eating cake...
Amount: 5
Speed: 100
Drinking milk...
Volume: 5
Temperature: 100
Eating pudding...
Amount: 5
Speed: 100
Drinking pudding...
Volume: 5
Temperature: 100
2.定义接口接管RTTI
#include <print>struct EatParams {int amount;int speed;
};struct DrinkParams {int volume;int temperature;
};struct Drinkable;
struct Eatable;struct Food {virtual ~Food() = default;virtual Drinkable* toDrinkable(){return nullptr;}virtual Eatable* toEatable(){return nullptr;}};struct Drinkable : virtual Food {virtual void drink(DrinkParams drinkParams) = 0;Drinkable* toDrinkable() override{return this;}
};struct Eatable : virtual Food {virtual void eat(EatParams eatParams) = 0;Eatable* toEatable() override{return this;}
};struct Cake : Eatable {void eat(EatParams eatParams) override {std::println("Eating cake...");std::println("Amount: {}", eatParams.amount);std::println("Speed: {}", eatParams.speed);}
};struct Milk : Drinkable {void drink(DrinkParams drinkParams) override {std::println("Drinking milk...");std::println("Volume: {}", drinkParams.volume);std::println("Temperature: {}", drinkParams.temperature);}
};struct Pudding : Eatable, Drinkable {void eat(EatParams eatParams) override {std::println("Eating pudding...");std::println("Amount: {}", eatParams.amount);std::println("Speed: {}", eatParams.speed);}void drink(DrinkParams drinkParams) override {std::println("Drinking pudding...");std::println("Volume: {}", drinkParams.volume);std::println("Temperature: {}", drinkParams.temperature);}
};void dailyRun(Food* food)
{if (auto eat = food->toEatable()){ eat->eat({5,100});}if (auto drink = food->toDrinkable()){ drink->drink({5,100});}}int main() {Cake cake;Milk milk;Pudding pudding;dailyRun(&cake);dailyRun(&milk);dailyRun(&pudding);return 0;
}
但是还是违背开闭原则,如果在food的基础上增加接口,修改的地方不少
- 在struct Food处需要修改,增加virtual Layable* toLayable(){}…
- 还有增加前向声明
3.使用访问者模式接管RTTI
- 还是会影响开闭原则
- 优点是如果增加接口,修改的地方不多:(1)struct FoodVisitor增加一个重载,(2)struct PengUser 去实现具体的访问行为
#include <print>struct EatParams {int amount;int speed;
};struct DrinkParams {int volume;int temperature;
};//访问者模式特点:需要访问的数据对象构成重载
struct FoodVisitor {virtual void visit(struct Eatable *eat) {}virtual void visit(struct Drinkable *drink) {}virtual ~FoodVisitor() = default;
};struct Food {//最根本的虚基类需要定义accept接口去接受这个访问者virtual void accept(FoodVisitor *visitor) = 0;virtual ~Food() = default;
};#define DEF_FOOD_ACCEPT void accept(FoodVisitor *visitor) override { visitor->visit(this); }struct Drinkable : virtual Food {virtual void drink(DrinkParams drinkParams) = 0;DEF_FOOD_ACCEPT
};struct Eatable : virtual Food {virtual void eat(EatParams eatParams) = 0;DEF_FOOD_ACCEPT
};struct Cake : Eatable {void eat(EatParams eatParams) override {std::println("Eating cake...");std::println("Amount: {}", eatParams.amount);std::println("Speed: {}", eatParams.speed);}
};struct Milk : Drinkable {void drink(DrinkParams drinkParams) override {std::println("Drinking milk...");std::println("Volume: {}", drinkParams.volume);std::println("Temperature: {}", drinkParams.temperature);}
};struct Pudding : Eatable, Drinkable {void eat(EatParams eatParams) override {std::println("Eating pudding...");std::println("Amount: {}", eatParams.amount);std::println("Speed: {}", eatParams.speed);}void drink(DrinkParams drinkParams) override {std::println("Drinking pudding...");std::println("Volume: {}", drinkParams.volume);std::println("Temperature: {}", drinkParams.temperature);}void accept(FoodVisitor *visitor) override {Eatable::accept(visitor);Drinkable::accept(visitor);}
};//实际的访问者实现如何去访问:具体的访问行为
struct PengUser : FoodVisitor {void visit(Eatable *eat) override {eat->eat({5, 10});}void visit(Drinkable *drink) override {drink->drink({10, 20});}
};void pengEat(Food *food) {PengUser user;/*一般都是user.eat(),user.drink()....访问者模式刚好相反*/food->accept(&user);food->accept(&user);food->accept(&user);
}int main() {Cake cake;Milk milk;Pudding pudding;pengEat(&cake);pengEat(&milk);pengEat(&pudding);return 0;
}
测试:
Program returned: 0
Program stdout
Eating cake...
Amount: 5
Speed: 10
Eating cake...
Amount: 5
Speed: 10
Eating cake...
Amount: 5
Speed: 10
Drinking milk...
Volume: 10
Temperature: 20
Drinking milk...
Volume: 10
Temperature: 20
Drinking milk...
Volume: 10
Temperature: 20
Eating pudding...
Amount: 5
Speed: 10
Drinking pudding...
Volume: 10
Temperature: 20
Eating pudding...
Amount: 5
Speed: 10
Drinking pudding...
Volume: 10
Temperature: 20
Eating pudding...
Amount: 5
Speed: 10
Drinking pudding...
Volume: 10
Temperature: 20
参考
- code
- 【C/C++】什么情况下需要封装get/set