【c++11】什么情况下需要封装set/get
文章目录
- 一、平凡类型与非平凡类型什么时候使用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 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 #include 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 #include #include #include 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 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 args;可以支持动态增加参数
- [[nodiscard]]如果没有使用,则产生告警
#include #include #include #include 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 args; }; template struct [[nodiscard]] ConnectionBuilder : ConnectionBuilderBase { [[nodiscard]] ConnectionBuilder &withAddress(std::string addr) { serverAddress = addr; return static_cast(static_cast(*this)); } [[nodiscard]] ConnectionBuilder &withPort(int p) { port = p; return *this; } [[nodiscard]] ConnectionBuilder &withAddressAndPort(std::string addr) { auto pos = addr.find(':'); serverAddress = addr.substr(0, pos); port = std::stoi(addr.substr(pos + 1)); return static_cast(static_cast(*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 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 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(food)) { eat->eat({5,100}); } if (auto drink = dynamic_cast(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 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 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
文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。