单例模式
- 一个类不管创建对象多少次,都只能得到该类型一个对象的实例
- 原因:日志类、数据库访问等,每次调用函数直接用,而不需要创建对象增大开销
实现
- 构造函数私有化
- 提供唯一实例对象
- 提供唯一对象获取方式(一般返回指针)
1. 饿汉式(直接产生)
注意类的静态成员数据需要在类外初始化,静态数据成员在类中只是声明,需要在类外初始化分配内存
类中的数据成员都只是声明,如
int a =10;只是在声明默认值为10,真正初始化分配内存要等到调用构造函数特殊:
- cpp17:inline+static可以实现类内部直接初始化,而不需要再在外部初始化,使得含静态数据成员类能写在头文件中,
inline static int count = 0; // C++17 直接初始化- const+static整型或枚举类型:也能直接在类内部初始化,
const static int MAX = 100; // 合法(C++98 起支持),c++17开始这两者组合默认含有inline,所以其他今天类型也可以在类中初始化
class Singleton {
public:
static Singleton* getSingleton() {
return &singleton;
}
void print() {
std::cout << "Singleton instance" << std::endl;
}
private:
Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton singleton;
};
Singleton Singleton::singleton;
2. 懒汉式(首次调用才产生)
注意多线程问题 局部静态变量只会初始化一次,且初始化时是线程安全的
class Singleton {
public:
static Singleton* getSingleton() {
// 静态局部变量初始化是线程安全的,汇编代码会自动加锁
// 静态局部变量只会初始化一次,如果已经初始化,则会直接跳过
static Singleton singleton;
return &singleton;
}
void print() {
std::cout << "Singleton instance" << std::endl;
}
private:
Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
工厂模式
- 封装对象的创建过程
- 将对象创建与使用解耦合
- 比如在使用数据库时,用户代码只管获得数据库操作对象,然后进行增删改查,如果底层换了数据库,只需要增加对应的对象,然后去工厂函数中修改返回的对象即可,用户代码不需要改变
- 开闭原则:对新功能
- 对拓展开放:新增类和方法
- 对修改关闭:不能直接修改现有的类和方法中的代码
1. 简单工厂
- 本质
一个工厂类封装对象的创建逻辑,客户端只需传入参数,无需知道具体实现类
- 缺陷
没有做到“开闭原则”,由于使用
if-else,增加功能要对现有方法进行更改即添加else if
// 简单工厂类
class ShapeFactory {
public:
static std::unique_ptr<Shape> createShape(const std::string& type) {
if (type == "Circle") {
return std::make_unique<Circle>();
}
else if (type == "Rectangle") {
return std::make_unique<Rectangle>();
}
return nullptr;
}
};
// 客户端代码
auto circle = ShapeFactory::createShape("Circle");
2. 工厂方法
- 本质
- 借助继承与多态来实现不同对象的实例化
- 首先创建工厂抽象类(用于客户代码指向具体工厂实现多态),然后为每个对象实现一个具体的工厂类继承自工厂抽象类
- 需要实例化对象时,调用对应的具体工厂类即可
- 优缺点
- 实现了开闭原则,新增产品只需要添加新工厂类即可
- 工厂类会很多,具体工厂与类是一一对应
- 很多产品是有关联的
// 抽象工厂类
class Application {
public:
virtual std::unique_ptr<Document> createDocument() = 0;
void newDocument() {
auto doc = createDocument();
doc->save();
}
virtual ~Application() = default;
};
// 具体工厂类
class TextApplication : public Application {
public:
std::unique_ptr<Document> createDocument() override {
return std::make_unique<TextDocument>();
}
};
class SpreadsheetApplication : public Application {
public:
std::unique_ptr<Document> createDocument() override {
return std::make_unique<SpreadsheetDocument>();
}
};
// 客户端代码
int main() {
std::unique_ptr<Application> app = std::make_unique<TextApplication>();
app->newDocument();
app = std::make_unique<SpreadsheetApplication>();
app->newDocument();
return 0;
}
3. 抽象工厂
- 本质
- 与工厂区别在于,是对一组关联关系的产品簇提供产品对象的统一创建
- 工厂与类是一对多的
- 优缺点
- 可以确保创建的对象是兼容的,适合产品族场景
- 扩展新产品簇困难,需要修改抽象工厂接口
// 抽象工厂
class GUIFactory {
public:
virtual std::unique_ptr<Button> createButton() = 0;
virtual std::unique_ptr<Checkbox> createCheckbox() = 0;
virtual ~GUIFactory() = default;
};
// 具体工厂1
class WindowsFactory : public GUIFactory {
public:
std::unique_ptr<Button> createButton() override {
return std::make_unique<WindowsButton>();
}
std::unique_ptr<Checkbox> createCheckbox() override {
return std::make_unique<WindowsCheckbox>();
}
};
// 具体工厂2
class MacOSFactory : public GUIFactory {
public:
std::unique_ptr<Button> createButton() override {
return std::make_unique<MacOSButton>();
}
std::unique_ptr<Checkbox> createCheckbox() override {
return std::make_unique<MacOSCheckbox>();
}
};
// 上面工厂能对多个类实例化
代理模式
通过代理类,来控制实际对象的访问权限,比如电影分为免费、vip和用券购买
- 实现
- 存在抽象类
- 代理类替代委托类,两者为兄弟关系(继承同一个抽象类),每个代理代表一种身份,比如普通用户和vip用户会有两个对应的代理类
- 代理类中存在委托类实例(组合关系),也实现了委托类的方法,内部进行权限控制后调用委托类的方法
- 客户直接访问的是代理对象
装饰器模式
不改变现有对象的结构下强化类的功能,和代理模式基本一致,区别在于用途,代理一般是一对一,而装饰器可能因为要强化功能而多层嵌套
- 实现(和代理相同)
- 定义一个公共接口
- 实现具体组件类
- 创建包装类(装饰器/代理)持有组件引用
- 在包装类中添加额外逻辑
- 与代理模式区别 装饰器模式让蛋糕更美味,代理模式决定谁能吃这个蛋糕
适配器模式
让不兼容的接口可以在一起工作(转换头)
- 实现
- 适配器是一个中间对象,继承自老接口
- 适配器存在新接口的引用
- 使用新接口的方法来实现老接口相关方法
- 用户代码:1.适配器接受新接口,2.用户代码原本的方法接受适配器
观察者-监听模式(发布-订阅模式)
多个对象依赖一个对象时(如曲线图、柱状图依赖数据对象),依赖的对象发生改变后其他对象能收到相应的通知,建立对象间一对多的依赖关系
- 实现
- 主题内有依赖他的观察者的引用
- 主题更新,则调用观察者的相关方法