文章目录
- 一、平凡类型与非平凡类型什么时候使用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