好的,下面是一篇关于C++常用设计模式(单例、工厂、代理、建造者)的博客大纲:
C++常用设计模式
设计模式是在软件设计过程中,针对特定问题所形成的解决方案。它们提供了一种经过验证的方法来解决常见的设计问题,同时也提高了代码的可维护性、可复用性和可读性。在C++中,有一些设计模式被广泛应用于实际开发中。本文将介绍其中一些常用的设计模式:单例模式、工厂模式、代理模式和建造者模式。
1. 单例模式 (Singleton Pattern)
介绍: 单例模式确保类只有一个实例,并提供一个全局访问点。
应用场景: 当系统中只需要一个全局对象来协调行为时,使用单例模式是很有用的,比如配置管理器、日志记录器等。
当谈到单例模式时,常常提到的是懒汉和饿汉单例模式。它们之间的区别在于单例对象的初始化时机不同。下面将分别介绍懒汉和饿汉单例模式的特点以及示例代码。
懒汉单例模式 (Lazy Singleton Pattern)
特点:
- 懒汉单例模式的特点是在需要时才创建单例对象。
- 第一次调用获取实例的方法时,如果实例还没有创建,就会创建一个新的实例并返回,以后再次调用时会返回同一个实例。
优点:
- 延迟加载,节约资源。
- 在需要时才创建实例,避免了不必要的初始化开销。
缺点:
- 需要考虑线程安全问题。如果在多线程环境下,可能会导致创建多个实例。(但在c++11后静态变量的创建是线程安全的)
cpp
class LazySingleton {
public:
static LazySingleton& getInstance() {
static LazySingleton instance;
return instance;
}
// 禁止拷贝构造函数和赋值运算符
LazySingleton(const LazySingleton&) = delete;
LazySingleton& operator=(const LazySingleton&) = delete;
private:
LazySingleton() {} // 私有构造函数,防止外部创建实例
};
饿汉单例模式 (Eager Singleton Pattern)
特点:
- 饿汉单例模式的特点是在程序启动时就创建单例对象。
- 即使程序中没有使用该单例对象,也会在程序启动时创建并占用资源。
优点:
- 线程安全,不需要考虑多线程环境下的同步问题。
缺点:
- 占用内存,如果单例对象很大或者在程序中很少使用,会造成资源浪费。
cpp
class EagerSingleton {
public:
static EagerSingleton& getInstance() {
return instance;
}
// 禁止拷贝构造函数和赋值运算符
EagerSingleton(const EagerSingleton&) = delete;
EagerSingleton& operator=(const EagerSingleton&) = delete;
private:
EagerSingleton() {} // 私有构造函数,防止外部创建实例
static EagerSingleton instance; // 静态成员变量,程序启动时即创建
};
// 在类外初始化静态成员变量
EagerSingleton EagerSingleton::instance;
2. 工厂模式 (Factory Pattern)
介绍: 工厂模式用于创建对象,但将对象的创建过程与使用者隔离开来。
应用场景: 当需要根据输入参数来创建不同类型的对象时,工厂模式是很有用的,比如图形库中的形状工厂。
cpp
class Product {
public:
virtual void operation() = 0;
virtual ~Product() {}
};
class ConcreteProduct : public Product {
public:
void operation() override {
// 具体产品的操作
}
};
class ConcreteFactory {
public:
Product* createProduct(){
return new ConcreteProduct();
}
};
这个模式的结构和管理产品对象的方式十分简单, 但是它的扩展性非常差,当我们需要新增产品的时 候,就需要去修改工厂类新增⼀个类型的产品创建逻辑,违背了开闭原则。
抽象工厂模式
- 提供一个抽象的工厂接口: 它定义了一系列工厂方法,用于创建产品对象。
- 具体工厂实现: 每个具体工厂实现了抽象工厂接口,并负责创建特定系列的产品对象。
- 一组相关的产品: 每个具体工厂负责创建一组相关的产品,这些产品通常属于同一家族或具有相关性。
- 解耦产品的创建和使用: 客户端代码通过抽象工厂接口创建产品对象,无需关心具体的产品类。
抽象工厂模式的示例:
假设我们要开发一个跨平台的GUI库,其中包含按钮和文本框两种控件,我们可以使用抽象工厂模式来实现。
cpp
// 抽象产品:按钮
class Button {
public:
virtual void render() = 0;
};
// 具体产品:Windows按钮
class WindowsButton : public Button {
public:
void render() override {
// 渲染Windows风格按钮
}
};
// 具体产品:Linux按钮
class LinuxButton : public Button {
public:
void render() override {
// 渲染Linux风格按钮
}
};
// 抽象产品:文本框
class TextBox {
public:
virtual void render() = 0;
};
// 具体产品:Windows文本框
class WindowsTextBox : public TextBox {
public:
void render() override {
// 渲染Windows风格文本框
}
};
// 具体产品:Linux文本框
class LinuxTextBox : public TextBox {
public:
void render() override {
// 渲染Linux风格文本框
}
};
// 抽象工厂接口
class GUIFactory {
public:
virtual Button* createButton() = 0;
virtual TextBox* createTextBox() = 0;
};
// 具体工厂:Windows GUI工厂
class WindowsGUIFactory : public GUIFactory {
public:
Button* createButton() override {
return new WindowsButton();
}
TextBox* createTextBox() override {
return new WindowsTextBox();
}
};
// 具体工厂:Linux GUI工厂
class LinuxGUIFactory : public GUIFactory {
public:
Button* createButton() override {
return new LinuxButton();
}
TextBox* createTextBox() override {
return new LinuxTextBox();
}
};
在示例中,GUIFactory 是抽象工厂接口,定义了创建按钮和文本框的方法。WindowsGUIFactory 和 LinuxGUIFactory 是具体工厂类,分别负责创建Windows风格和Linux风格的按钮和文本框。客户端代码可以根据需要选择使用哪种具体工厂,从而创建相应的产品。这样,产品的创建和使用被有效地解耦。
3. 代理模式 (Proxy Pattern)
介绍: 代理模式用于控制对其他对象的访问,提供了一种间接访问对象的方法。
应用场景: 当需要在访问对象前或访问对象后执行额外操作时,代理模式是很有用的,比如远程代理、保护代理等
(代理模式分为静态代理和动态代理,这里是静态)
cpp
class Subject {
public:
virtual void request() = 0;
virtual ~Subject() {}
};
class RealSubject : public Subject {
public:
void request() override {
// 真实主题的请求
}
};
class Proxy : public Subject {
public:
void request() override {
// 执行额外操作
if (!realSubject)
realSubject = new RealSubject();
realSubject->request();
// 执行额外操作
}
~Proxy() {
delete realSubject;
}
private:
RealSubject* realSubject = nullptr;
};
4. 建造者模式 (Builder Pattern)
介绍: 建造者模式用于将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
应用场景: 当需要创建的对象具有复杂的内部结构或者创建过程中涉及多个步骤时,建造者模式是很有用的。
cpp
class Product {
public:
void setPartA(const std::string& partA) {
partA_ = partA;
}
void setPartB(const std::string& partB) {
partB_ = partB;
}
void setPartC(const std::string& partC) {
partC_ = partC;
}
std::string getState() const {
return partA_ + " " + partB_ + " " + partC_;
}
private:
std::string partA_;
std::string partB_;
std::string partC_;
};
class Builder {
public:
virtual void buildPartA() = 0;
virtual void buildPartB() = 0;
virtual void buildPartC() = 0;
virtual Product getResult() = 0;
virtual ~Builder() {}
};
class ConcreteBuilder : public Builder {
public:
void buildPartA() override {
product_.setPartA("PartA");
}
void buildPartB() override {
product_.setPartB("PartB");
}
void buildPartC() override {
product_.setPartC("PartC");
}
Product getResult() override {
return product_;
}
private:
Product product_;
};