C++设计模式 20种
要读懂C++的设计模式,精髓是看懂类之间的关系,各种设计模式主要就是虚基类之间的包含关系
学习设计模式,重点就是看懂不同类之间的包含继承等关系
一些基本的理解:
虚基类的用处就是可以通过虚基类的指针调用实现类的函数,如果一个类变为虚基类加多个实现类,说明这个类可以有很多种
分为三种设计模式,包括 创建型,结构型,策略型,接下来就依次讲解,主要用于自学和记录,较粗糙,如果可以帮助你理解那也很荣幸,代码主要用于理解设计模式
创建型设计模式
创建型设计模式主要关注对象的创建过程。这些模式提供了一种在创建对象时隐藏创建逻辑的方式,而不是直接使用 new 运算符来创建对象,这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
创建型设计模式主要包括以下几种:
-
单例模式:确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。
-
工厂方法模式:定义一个创建对象的接口,但让子类决定实例化哪一个类。工厂方法让类的实例化推迟到子类。
-
抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
-
建造者模式:使用简单对象和使用步骤来构建复杂对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
-
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
这些模式都有各自的优点和适用场景
单例模式
单例模式(Singleton Pattern)是一种创建型设计模式,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。
以下是一个简单的单例模式的示例:
class Singleton {
private:
static Singleton* instance;
Singleton() {}
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
void someBusinessLogic() {
// ...
}
};
// 初始化指针
Singleton* Singleton::instance = nullptr;
// 客户端代码
int main() {
Singleton* s = Singleton::getInstance();
s->someBusinessLogic();
// ...
return 0;
}
在这个示例中,Singleton
类有一个私有的静态成员 instance
,用于保存单例对象。Singleton
类的构造函数是私有的,所以不能直接创建 Singleton
对象。Singleton
类提供了一个静态的 getInstance
方法,用于获取单例对象。当第一次调用 getInstance
方法时,会创建一个新的 Singleton
对象;当再次调用 getInstance
方法时,会返回已经创建的 Singleton
对象。这样,可以确保 Singleton
类只有一个实例。
精髓就是 把类的实例放在类内部,并且作为私有成员,构造函数也作为私有成员,这样类外部不能之间创建它的实例,避免有多个实例,另外提供一个 get_insence()接口获取对象,获取的时候检查对象有没有创建,如果创建了就返回对象,否则创建对象,这样可以保证只会有一个对象被创建
在多线程情况下,假如有多个线程同时执行 if (instance == nullptr) 就会同时进行创建,所以多线程情况要加锁,先加锁,再判断一下是否是空指针,如果为空再创建
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Singleton();
}
}
另外上面的是懒汉式,顾名思义就是很懒,等待需要的时候再实例化,而饿汉式如下
private:
static Singleton instance;
public:
static Singleton& getInstance() {
return instance;
}
用static变量,在程序启动时就会实例化,并且不用考虑多线程的问题,至少创建时不需要
工厂方法模式
工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它提供了一种方式来封装对象的创建过程。在工厂方法模式中,客户端不直接创建对象,而是调用一个工厂方法来创建对象。这个工厂方法可以被子类重写,以创建不同类型的对象。
以下是一个简单的工厂方法模式的示例:
// 抽象产品
class Product {
public:
virtual void use() = 0;
};
// 具体产品
class ConcreteProduct : public Product {
public:
void use() override {
std::cout << "Use ConcreteProduct\n";
}
};
// 抽象工厂
class Factory {
public:
virtual Product* createProduct() = 0;
};
// 具体工厂
class ConcreteFactory : public Factory {
public:
Product* createProduct() override {
return new ConcreteProduct();
}
};
// 客户端代码
int main() {
Factory* factory = new ConcreteFactory();
Product* product = factory->createProduct();
product->use(); // 输出 "Use ConcreteProduct"
delete product;
delete factory;
return 0;
}
在这个示例中,Factory
类是一个抽象工厂,它定义了一个 createProduct
方法来创建产品。ConcreteFactory
类是一个具体工厂,它重写了 createProduct
方法来创建 ConcreteProduct
对象。
这样,客户端可以通过工厂来创建产品,而不需要直接创建产品。如果需要创建不同类型的产品,只需要创建不同的工厂即可。这使得客户端在不修改代码的情况下就可以创建不同类型的产品,
看类的关系,抽象工厂是个虚基类,它定义了一个函数返回的指针是抽象产品的虚基类,也就是可以返回不同的具体产品,而抽象工厂是虚基类,就可以有不同的具体工厂来创建不同的产品,返回不同的产品指针
而在使用的时候就只需要调用具体工厂类,而不用调用具体产品类,并且可以通过虚基类指针调用统一的产品的方法
抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,它提供了一种方式来封装一组具有共同主题的单个工厂,而无需指定它们的具体类。
以下是一个简单的抽象工厂模式的示例:
#include <iostream>
// 抽象产品:Button
class Button {
public:
virtual void click() = 0;
};
// 抽象产品:Checkbox
class Checkbox {
public:
virtual void check() = 0;
};
// 具体产品:WindowsButton
class WindowsButton : public Button {
public:
void click() override {
std::cout << "WindowsButton clicked\n";
}
};
// 具体产品:WindowsCheckbox
class WindowsCheckbox : public Checkbox {
public:
void check() override {
std::cout << "WindowsCheckbox checked\n";
}
};
// 具体产品:LinuxButton
class LinuxButton : public Button {
public:
void click() override {
std::cout << "LinuxButton clicked\n";
}
};
// 具体产品:LinuxCheckbox
class LinuxCheckbox : public Checkbox {
public:
void check() override {
std::cout << "LinuxCheckbox checked\n";
}
};
// 抽象工厂:GUIFactory
class GUIFactory {
public:
virtual Button* createButton() = 0;
virtual Checkbox* createCheckbox() = 0;
};
// 具体工厂:WindowsFactory
class WindowsFactory : public GUIFactory {
public:
Button* createButton() override {
return new WindowsButton();
}
Checkbox* createCheckbox() override {
return new WindowsCheckbox();
}
};
// 具体工厂:LinuxFactory
class LinuxFactory : public GUIFactory {
public:
Button* createButton() override {
return new LinuxButton();
}
Checkbox* createCheckbox() override {
return new LinuxCheckbox();
}
};
// 客户端代码
int main() {
GUIFactory* factory = new WindowsFactory();
Button* button = factory->createButton();
button->click(); // 输出 "WindowsButton clicked"
Checkbox* checkbox = factory->createCheckbox();
checkbox->check(); // 输出 "WindowsCheckbox checked"
delete checkbox;
delete button;
delete factory;
factory = new LinuxFactory();
button = factory->createButton();
button->click(); // 输出 "LinuxButton clicked"
checkbox = factory->createCheckbox();
checkbox->check(); // 输出 "LinuxCheckbox checked"
delete checkbox;
delete button;
delete factory;
return 0;
}
看上面的代码,感觉和工厂模式是不是没什么区别,确实没什么区别,唯一的区别就是抽象工厂 创建的是产品簇,就是可以在一个工厂创建多个不同类型的产品,工厂和产品类的组成方式是一样的
简单工厂模式
看完上面两个工厂,再看简单工厂,其实就是简化版,只有产品有虚基类,也就是产品可以有不同的产品,但是工厂不是虚基类,只有一个工厂,在这个工厂里创建不同的产品
简单工厂模式的精髓就是 可以用一个统一的接口来调用不同的产品的函数,新增产品只需要修改工厂和新增具体产品以及客户端代码,但是都是新增,易于扩展
以下是一个简单的简单工厂模式的示例:
// 抽象产品
class Product {
public:
virtual void use() = 0;
};
// 具体产品A
class ConcreteProductA : public Product {
public:
void use() override {
std::cout << "Use ConcreteProductA\n";
}
};
// 具体产品B
class ConcreteProductB : public Product {
public:
void use() override {
std::cout << "Use ConcreteProductB\n";
}
};
// 简单工厂
class SimpleFactory {
public:
static Product* createProduct(const std::string& type) {
if (type == "A") {
return new ConcreteProductA();
} else if (type == "B") {
return new ConcreteProductB();
} else {
return nullptr;
}
}
};
// 客户端代码
int main() {
Product* productA = SimpleFactory::createProduct("A");
productA->use(); // 输出 "Use ConcreteProductA"
delete productA;
Product* productB = SimpleFactory::createProduct("B");
productB->use(); // 输出 "Use ConcreteProductB"
delete productB;
return 0;
}
在这个示例中,SimpleFactory
类有一个静态的 createProduct
方法,用于创建产品。这个方法根据传入的类型参数来创建不同的产品。这样,客户端可以通过工厂来创建产品,而不需要直接创建产品。这使得客户端在不修改代码的情况下就可以创建不同类型的产品,提高了代码的可扩展性和可维护性。
然而,简单工厂模式也有其缺点。当产品种类非常多时,工厂方法的业务逻辑会变得非常复杂。此外,每当我们需要添加一个新的产品时,都需要修改工厂方法,这违反了 “开放封闭原则”(即软件实体应该对扩展开放,对修改封闭)。因此,简单工厂模式更适合产品种类较少的情况。
建造者模式
精髓就是 建造者里可以用不同的方法建造不同的产品部分,而导演里调用用各种方式或顺序调用建造者里的建造方法,可以建造不同的产品
建造者模式(Builder Pattern)是一种创建型设计模式,它提供了一种构建复杂对象的有效方式。在建造者模式中,复杂对象的创建过程被分解为多个简单的步骤,这些步骤可以在运行时改变,以创建不同类型的对象。
以下是一个简单的建造者模式的示例:
// 产品
class Product {
public:
std::vector<std::string> parts_;
void ListParts() const {
std::cout << "Product parts: ";
for (size_t i = 0; i < parts_.size(); i++) {
if (parts_[i] == parts_.back()) {
std::cout << parts_[i];
} else {
std::cout << parts_[i] << ", ";
}
}
std::cout << "\n\n";
}
};
// 抽象建造者
class Builder {
public:
virtual ~Builder() {}
virtual void ProducePartA() const = 0;
virtual void ProducePartB() const = 0;
virtual void ProducePartC() const = 0;
};
// 具体建造者
class ConcreteBuilder : public Builder {
private:
Product* product;
public:
ConcreteBuilder() {
this->Reset();
}
~ConcreteBuilder() {
delete product;
}
void Reset() {
product = new Product();
}
void ProducePartA() const override {
this->product->parts_.push_back("PartA");
}
void ProducePartB() const override {
this->product->parts_.push_back("PartB");
}
void ProducePartC() const override {
this->product->parts_.push_back("PartC");
}
Product* GetProduct() {
Product* result = this->product;
this->Reset();
return result;
}
};
// 指挥者
class Director {
private:
Builder* builder;
public:
void set_builder(Builder* builder) {
this->builder = builder;
}
void BuildMinimalViableProduct() {
this->builder->ProducePartA();
}
void BuildFullFeaturedProduct() {
this->builder->ProducePartA();
this->builder->ProducePartB();
this->builder->ProducePartC();
}
};
// 客户端代码
int main() {
Director* director = new Director();
ConcreteBuilder* builder = new ConcreteBuilder();
director->set_builder(builder);
std::cout << "Standard basic product:\n";
director->BuildMinimalViableProduct();
Product* p = builder->GetProduct();
p->ListParts();
delete p;
std::cout << "Standard full featured product:\n";
director->BuildFullFeaturedProduct();
p = builder->GetProduct();
p->ListParts();
delete p;
// Remember, the Builder pattern can be used without a Director class.
std::cout << "Custom product:\n";
builder->ProducePartA();
builder->ProducePartC();
p = builder->GetProduct();
p->ListParts();
delete p;
delete builder;
delete director;
return 0;
}
在这个示例中,Builder
类是一个抽象建造者,它定义了创建产品的各个步骤。ConcreteBuilder
类是一个具体建造者,它实现了 Builder
类的各个步骤
原型模式
其实原型模式可以理解为是对复制构造函数的优化,复制构造函数是创建一个完全不同的副本,但是原型模式中你可以控制复制哪些内容已经怎么复制,可以更加高效
原型模式(Prototype Pattern)是一种创建型设计模式,它通过复制或克隆已存在的实例来创建新的对象,而不是通过新建实例的方式。这种模式在某些情况下可以提高程序的性能,例如,对象的创建成本很高,或者类的实例之间的差异很小,可以通过复制已存在的实例并稍作修改的方式来创建新的实例。
以下是一个简单的原型模式的示例:
#include <iostream>
#include <memory>
// 抽象原型
class Prototype {
public:
virtual ~Prototype() {}
virtual std::unique_ptr<Prototype> Clone() const = 0;
};
// 具体原型
class ConcretePrototype : public Prototype {
private:
std::string data_;
public:
ConcretePrototype(const std::string& data) : data_(data) {}
std::unique_ptr<Prototype> Clone() const override {
return std::make_unique<ConcretePrototype>(*this);
}
void Display() const {
std::cout << "Data: " << data_ << "\n";
}
};
// 客户端代码
int main() {
std::unique_ptr<Prototype> prototype = std::make_unique<ConcretePrototype>("Hello");
std::unique_ptr<Prototype> clone = prototype->Clone();
static_cast<ConcretePrototype*>(prototype.get())->Display(); // 输出 "Data: Hello"
static_cast<ConcretePrototype*>(clone.get())->Display(); // 输出 "Data: Hello"
return 0;
}
在这个示例中,Prototype
类是一个抽象原型,它定义了一个 Clone
方法来创建原型的克隆。ConcretePrototype
类是一个具体原型,它实现了 Clone
方法来创建 ConcretePrototype
的克隆,不过这个也只是调用了复制构造函数进行复制,实际你可能使用其他方式复制然后返回一个对象
这样,客户端可以通过原型来创建对象,而不需要直接创建对象。这使得客户端在不修改代码的情况下就可以创建不同类型的对象,提高了代码的可扩展性和可维护性。
结构型设计模式
适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
桥接模式(Bridge):将抽象部分与它的实现部分分离,使它们都可以独立地变化。
组合模式(Composite):将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
装饰模式(Decorator):动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活。
外观模式(Facade):为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
享元模式(Flyweight):运用共享技术有效地支持大量细粒度的对象。
代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问。
适配器模式
先看示例代码
// 目标接口(新的接口)
class Target {
public:
virtual void request() = 0;
};
// 要适配的类(旧的接口)
class Adaptee {
public:
void specificRequest() {
// ...
}
};
// 适配器类
class Adapter : public Target {
private:
Adaptee* adaptee;
public:
Adapter(Adaptee* adaptee) : adaptee(adaptee) {}
void request() override {
// 将目标接口转换为要适配的类的接口
adaptee->specificRequest();
}
};
// 客户端代码
int main() {
Adaptee* adaptee = new Adaptee();
Target* target = new Adapter(adaptee);
target->request(); // 实际上调用的是 Adaptee 的 specificRequest 方法
delete target;
delete adaptee;
return 0;
}
非常简单,就是传递了一下参数,你甚至都怀疑是不是没学到精髓
但是它确是非常常用的,比如stl里的栈,队列,都属于一种适配器,实际是对 vector容器的封装,还有比如跨平台开发,Windows和linux提供的底层方法不一样,就封装一下,变成一个一致的接口,也怪不得单独列成一种设计模式
装饰模式
装饰模式(Decorator Pattern)是一种结构型设计模式,它的主要目的是动态地给一个对象添加一些额外的职责,而不需要通过继承来实现。这主要通过创建一个新的装饰类来实现,这个装饰类包含了要装饰的对象,并实现了相同的接口。
以下是一个简单的装饰模式的示例:
// 抽象组件
class Component {
public:
virtual void operation() = 0;
};
// 具体组件
class ConcreteComponent : public Component {
public:
void operation() override {
std::cout << "ConcreteComponent operation\n";
}
};
// 抽象装饰类
class Decorator : public Component {
protected:
Component* component;
public:
Decorator(Component* component) : component(component) {}
virtual void operation() override {
component->operation();
}
};
// 具体装饰类
class ConcreteDecorator : public Decorator {
public:
ConcreteDecorator(Component* component) : Decorator(component) {}
void operation() override {
std::cout << "ConcreteDecorator operation\n";
Decorator::operation();
}
};
// 客户端代码
int main() {
Component* component = new ConcreteComponent();
Component* decorator = new ConcreteDecorator(component);
decorator->operation(); // 输出 "ConcreteDecorator operation" 和 "ConcreteComponent operation"
delete decorator;
delete component;
return 0;
}
在这个示例中,ConcreteDecorator
类是一个装饰类,它包含了一个 Component
对象,并实现了 Component
接口。当调用 ConcreteDecorator
的 operation
方法时,它会先执行一些额外的操作,然后调用 Component
的 operation
方法。这样,你可以动态地给一个对象添加一些额外的职责,而无需通过继承来实现。
装饰模式和适配器模式都是结构型设计模式,它们都涉及到类和对象的组合。然而,它们的主要区别在于它们的目标和如何实现这些目标。
-
装饰模式:装饰模式的主要目标是动态地给一个对象添加一些额外的职责,而不需要通过继承来实现。这主要通过创建一个新的装饰类来实现,这个装饰类包含了要装饰的对象,并实现了相同的接口。装饰模式通常用于扩展一个对象的功能,而不改变其接口。
-
适配器模式:适配器模式的主要目标是使两个已有的不兼容接口能够一起工作。这主要通过创建一个新的类(适配器)来实现,这个新的类包含了要适配的类的对象,并实现了目标接口。适配器模式通常用于处理那些无法修改的类,例如旧的代码或第三方库。
简单的看,看它们继承关系,
装饰模式里面,都继承自同一个虚基类,修饰的是出自同一个基类的对象,可以在调用方法的前后添加各种操作,只不过装饰器也是虚类,继承自虚基类,可以实现多种不同的装饰器
适配器模式则不是同一个基类,适配器有一个虚基类,然后不同的实现类里包含不同的要适配的接口,比如一个读文件的虚基类,两个实现类分别包含Windows和linux的读文件接口,封装后,使用的人只需要调用虚基类的读文件接口
代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,它的主要目的是为其他对象提供一个代理或占位符,以控制对这个对象的访问。代理模式通常用于延迟对象的创建,保护对象的访问,或者在访问对象时添加一些额外的操作。
以下是一个简单的代理模式的示例:
// 抽象主题
class Subject {
public:
virtual void request() = 0;
};
// 真实主题
class RealSubject : public Subject {
public:
void request() override {
std::cout << "RealSubject request\n";
}
};
// 代理
class Proxy : public Subject {
private:
RealSubject* realSubject;
public:
Proxy() : realSubject(new RealSubject()) {}
~Proxy() {
delete realSubject;
}
void request() override {
std::cout << "Proxy request\n";
realSubject->request();
}
};
// 客户端代码
int main() {
Subject* proxy = new Proxy();
proxy->request(); // 输出 "Proxy request" 和 "RealSubject request"
delete proxy;
return 0;
}
在这个示例中,Proxy
类是一个代理类,它包含了一个 RealSubject
对象,并实现了 Subject
接口。当调用 Proxy
的 request
方法时,它会先执行一些额外的操作,然后调用 RealSubject
的 request
方法。这样,你可以控制对 RealSubject
的访问,添加一些额外的操作,或者延迟 RealSubject
的创建。
仔细看,是不是似曾相识,没错,它怎么和上面的装饰器模式这么像啊,但是有区别,代理模式的里直接创建了要代理的类,而装饰器模式需要你传递你要装饰的类给装饰器,这在使用的时候有区别,代理模式也没有再弄一个虚类,而是直接代理的,外部使用的时候就直接使用这个代理,而装饰器使用的时候是先创建一个组件类,然后你可以 “装饰” 上不同的装饰类,封装性就不一样
代理模式主要可以解决以下几类问题:
-
远程代理:当对象位于远程服务器上时,可以创建一个在本地运行的代理对象,这个代理对象代表远程对象,所有对远程对象的操作都通过代理对象进行。
-
虚拟代理:当对象的创建开销很大时,可以创建一个代理对象来代表这个对象。代理对象在真正需要时才创建真实对象。
-
保护代理:当对象有一些访问限制时,可以创建一个代理对象来控制对真实对象的访问。代理对象可以检查调用者是否有足够的权限来访问真实对象。
-
智能引用代理:当对象被多个客户端共享时,可以创建一个代理对象来跟踪对象的引用计数。代理对象可以在对象不再被使用时自动销毁对象。
例如,一个图片浏览器可能需要加载大量的高清图片。由于高清图片的大小通常很大,直接加载所有的图片可能会消耗大量的内存和网络带宽。为了解决这个问题,你可以为每个图片创建一个代理对象。代理对象在图片被真正需要时才加载图片,否则只显示一个占位符。这就是虚拟代理的一个典型应用。
桥接模式
先看代码示例
// 颜色接口
class Color {
public:
virtual void applyColor() = 0;
};
// 红色
class Red : public Color {
public:
void applyColor() override {
std::cout << "Applying red color\n";
}
};
// 蓝色
class Blue : public Color {
public:
void applyColor() override {
std::cout << "Applying blue color\n";
}
};
// 形状抽象
class Shape {
protected:
Color* color;
public:
Shape(Color* color) : color(color) {}
virtual void draw() = 0;
};
// 圆形
class Circle : public Shape {
public:
Circle(Color* color) : Shape(color) {}
void draw() override {
std::cout << "Drawing Circle. ";
color->applyColor();
}
};
// 矩形
class Rectangle : public Shape {
public:
Rectangle(Color* color) : Shape(color) {}
void draw() override {
std::cout << "Drawing Rectangle. ";
color->applyColor();
}
};
// 客户端代码
int main() {
Color* red = new Red();
Shape* circle = new Circle(red);
circle->draw(); // 输出 "Drawing Circle. Applying red color"
Color* blue = new Blue();
Shape* rectangle = new Rectangle(blue);
rectangle->draw(); // 输出 "Drawing Rectangle. Applying blue color"
delete circle;
delete red;
delete rectangle;
delete blue;
return 0;
}
桥接模式的精髓是抽象和实现解耦,重点理解这句话
在上面这段示例代码中,颜色抽象类作为形状抽象类的成员,形状的实现类就可以调用颜色抽象类的方法,当你想新增一个颜色时,你不需要改变形状的实现类,因为颜色是 “桥接” 在形状类里的,名词听起来吓人,实际就是一个虚基类里包含另一个虚基类,然后可以调用另一个虚基类的函数而已
抽象和实现解耦"是一种设计原则,它的主要目的是使抽象(通常是一个接口或抽象类)与其实现(具体类)能够独立地变化,而不会相互影响。
在传统的设计中,一个抽象类通常会有一些具体的子类,这些子类提供了抽象类定义的接口的实现。这种设计有一个问题,那就是抽象类与其子类紧密耦合在一起,如果你想改变抽象类的接口或者子类的实现,可能会影响到其他的代码。
为了解决这个问题,你可以使用"抽象和实现解耦"的原则。具体来说,你可以将实现定义为一个接口,并将该接口的对象作为抽象类的成员。这样,抽象类只依赖于实现接口,而不依赖于具体的子类。如果你想改变抽象类的接口或者子类的实现,只需要修改相应的部分,而不会影响到其他的代码;
这种设计可以使你的代码更加灵活,更易于扩展和维护。例如,你可以很容易地添加新的实现,而无需修改抽象类的代码。你也可以在运行时动态地改变抽象类的实现,这在很多情况下都是非常有用的。
组合模式
组合模式(Composite Pattern)是一种结构型设计模式,它的主要目的是将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
让我们考虑一个文件系统的例子,其中有文件和文件夹。文件是叶子节点,文件夹是复合节点,可以包含其他文件或文件夹。这是一个典型的组合模式的应用场景。
以下是一个简单的示例:
#include <vector>
#include <iostream>
// 抽象组件
class FileSystemComponent {
public:
virtual void list() = 0;
};
// 叶子组件:文件
class File : public FileSystemComponent {
private:
std::string name;
public:
File(std::string name) : name(name) {}
void list() override {
std::cout << name << "\n";
}
};
// 复合组件:文件夹
class Directory : public FileSystemComponent {
private:
std::string name;
std::vector<FileSystemComponent*> children;
public:
Directory(std::string name) : name(name) {}
void list() override {
std::cout << name << ":\n";
for (FileSystemComponent* child : children) {
child->list();
}
}
void add(FileSystemComponent* component) {
children.push_back(component);
}
};
// 客户端代码
int main() {
Directory* root = new Directory("root");
root->add(new File("file1"));
root->add(new File("file2"));
Directory* dir = new Directory("dir");
dir->add(new File("file3"));
root->add(dir);
root->list(); // 输出 "root: file1 file2 dir: file3"
delete root;
return 0;
}
在这个示例中,Directory
类和 File
类都实现了 FileSystemComponent
接口。Directory
类包含了一组 FileSystemComponent
对象,当调用 Directory
的 list
方法时,它会调用其所有子组件的 list
方法。这样,你可以将多个对象组合成一个复杂的树形结构,而对于客户端代码来说,它只需要处理 FileSystemComponent
接口,无需关心具体的实现。
又来看虚类的构成,精髓就是不同的组件继承自同一个虚基类,然后组合类里有一个列表,列表是虚基类的指针,所以组合类可以包含任意的继承自虚基类的 实现类,并且可以无限套娃
组合模式主要解决以下两类问题:
-
表示部分-整体层次结构:在许多情况下,一个对象由多个部分组成,这些部分又可以由其他部分组成,形成一个树形结构。例如,一个公司由多个部门组成,每个部门又由多个员工组成;一个文件系统由多个文件和文件夹组成,每个文件夹又可以包含其他文件和文件夹。组合模式可以很好地表示这种部分-整体的层次结构。
-
统一对待单个对象和组合对象:在许多情况下,你希望对单个对象和组合对象进行相同的操作。例如,你可能希望对一个文件和一个文件夹进行相同的操作,如打开、关闭、读取等。组合模式可以使你对单个对象和组合对象进行相同的操作,而无需关心它们是单个对象还是组合对象。
总的来说,组合模式是一种非常实用的设计模式,它可以帮助你更好地组织和管理复杂的对象结构。
外观模式
外观模式(Facade Pattern)是一种结构型设计模式,它的主要目的是提供一个统一的接口来访问一组接口,从而使得系统更易于使用。外观模式隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口。
以下是一个简单的外观模式的示例:
// 子系统类
class Subsystem1 {
public:
void operation1() {
std::cout << "Subsystem1 operation1\n";
}
};
class Subsystem2 {
public:
void operation2() {
std::cout << "Subsystem2 operation2\n";
}
};
// 外观类
class Facade {
private:
Subsystem1* subsystem1;
Subsystem2* subsystem2;
public:
Facade() {
subsystem1 = new Subsystem1();
subsystem2 = new Subsystem2();
}
~Facade() {
delete subsystem1;
delete subsystem2;
}
void operation() {
subsystem1->operation1();
subsystem2->operation2();
}
};
// 客户端代码
int main() {
Facade* facade = new Facade();
facade->operation(); // 输出 "Subsystem1 operation1" 和 "Subsystem2 operation2"
delete facade;
return 0;
}
在这个示例中,Facade
类提供了一个统一的接口来访问 Subsystem1
和 Subsystem2
的接口。当调用 Facade
的 operation
方法时,它会调用 Subsystem1
的 operation1
方法和 Subsystem2
的 operation2
方法。这样,客户端代码只需要与 Facade
类交互,无需关心 Subsystem1
和 Subsystem2
的具体实现。
这个模式听起来一头雾水,其实非常简单,就是一个类的 包含了你想隐藏的两个类,然后提供一个接口,这个接口来调用你想隐藏的类的接口,有个很直观的例子就是我们打开文件时,系统内部实际涉及到打开文件和读取文件,但是我们以为只有一步操作
外观模式的优点主要包括:
-
简化接口:外观模式提供了一个简单的接口,隐藏了系统的复杂性,使得客户端更容易使用系统。
-
解耦:外观模式解耦了客户端和子系统的依赖关系,使得子系统可以独立地变化和演化,而不会影响到客户端。
-
提高灵活性和可扩展性:通过引入外观类,可以将子系统与客户端解耦,使得子系统的修改和扩展不会影响到客户端,提高了系统的灵活性和可扩展性。
假设你正在开发一个电影播放器,这个播放器包含了许多子系统,如解码器、音频系统、视频系统等。每个子系统都有一组复杂的接口,如播放、暂停、停止等。如果客户端需要直接与这些子系统交互,那么代码会变得非常复杂。
为了简化客户端的代码,你可以创建一个外观类,这个外观类提供了一组简单的接口,如播放电影、停止电影等。当客户端调用这些接口时,外观类会调用相应的子系统的接口。这样,客户端只需要与外观类交互,无需关心子系统的具体实现。
享元模式
享元模式(Flyweight Pattern)是一种结构型设计模式,它的主要目的是通过共享对象来减少内存使用和提高性能。享元模式通常用于那些大量使用的小型对象,这些对象的大部分状态都可以外部化。
精髓就是对于一个要频繁使用,重复使用的对象,我们给它包装一个工厂类,工厂类里用map来存这个对象,要使用使直接调用工厂类的方法获取对象,对于可以重用的对象可以减少构建和销毁,减少内存使用等
当让我们考虑一个直观的例子:一个文本编辑器。在文本编辑器中,我们可能需要创建大量的字符对象。每个字符对象都有一个字符值和一个字体样式。字符值是内部状态,它在字符对象创建时就确定了,不会改变。字体样式是外部状态,它在运行时可以改变。我们可以使用享元模式来共享字符对象,从而减少内存使用和提高性能。
以下是一个简单的示例:
#include <map>
#include <string>
// 享元类
class Character {
private:
char symbol; // 内部状态
public:
Character(char symbol) : symbol(symbol) {}
void render(const std::string& font) { // 外部状态
std::cout << "Rendering character " << symbol << " with font " << font << "\n";
}
};
// 享元工厂
class CharacterFactory {
private:
std::map<char, Character*> characters;
public:
~CharacterFactory() {
for (auto pair : characters) {
delete pair.second;
}
}
Character* getCharacter(char symbol) {
if (characters.find(symbol) == characters.end()) {
characters[symbol] = new Character(symbol);
}
return characters[symbol];
}
};
// 客户端代码
int main() {
CharacterFactory* factory = new CharacterFactory();
Character* characterA = factory->getCharacter('A');
characterA->render("Arial"); // 输出 "Rendering character A with font Arial"
Character* characterB = factory->getCharacter('B');
characterB->render("Times New Roman"); // 输出 "Rendering character B with font Times New Roman"
delete factory;
return 0;
}
在这个示例中,Character
类是一个享元类,它包含了一个内部状态(symbol
),这个状态是在享元对象创建时确定的,不会改变。Character
类的 render
方法接受一个外部状态(font
),这个状态是在运行时确定的,可以改变。
CharacterFactory
类是一个享元工厂,它维护了一个享元对象的池。当客户端请求一个享元对象时,享元工厂会检查池中是否已经有这个对象,如果有,就返回这个对象;如果没有,就创建一个新的对象,添加到池中,然后返回这个对象。这样,相同的享元对象可以被多个客户端共享,从而减少内存使用和提高性能。
行为型模式
行为型模式涉及到对象之间的通信,关注的是对象之间的责任分配。以下是一些常见的行为型设计模式:
策略模式:定义了一系列的算法,并将每一个算法封装起来,使它们可以互相替换,让算法独立于使用它的客户。
模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
观察者模式:定义了对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖者都会收到通知并自动更新。
迭代器模式:提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。
中介者模式:定义了一个封装一组对象如何交互的对象。通过使这些对象明确地相互引用来促进松散耦合,并允许独立地改变它们的交互。
备忘录模式:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
访问者模式:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
状态模式:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
责任链模式:为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
每种设计模式都解决了在特定的软件设计情况下的特定问题。选择哪种设计模式通常取决于问题本身,以及问题在软件系统中的上下文。
策略模式
精髓就是 一个类包含了另一个虚基类的指针,并且提供了修改这个指针的方法,以及调用虚基类的函数的方法,所以使用的时候可以灵活的切换使用不同的虚基类的具体实现类的方法(策略),其实类的结构和简单工厂模式很像有没有
策略模式(Strategy Pattern)是一种行为设计模式,它定义了一系列的算法,并将每一个算法封装起来,使它们可以互相替换,让算法独立于使用它的客户。这样可以使得算法可以独立于使用它的客户端变化。
以下是一个简单的策略模式的示例:
#include <iostream>
#include <memory>
// 抽象策略
class Strategy {
public:
virtual ~Strategy() {}
virtual void AlgorithmInterface() const = 0;
};
// 具体策略A
class ConcreteStrategyA : public Strategy {
public:
void AlgorithmInterface() const override {
std::cout << "ConcreteStrategyA AlgorithmInterface\n";
}
};
// 具体策略B
class ConcreteStrategyB : public Strategy {
public:
void AlgorithmInterface() const override {
std::cout << "ConcreteStrategyB AlgorithmInterface\n";
}
};
// 上下文
class Context {
private:
std::unique_ptr<Strategy> strategy_;
public:
Context(std::unique_ptr<Strategy> strategy) : strategy_(std::move(strategy)) {}
void set_strategy(std::unique_ptr<Strategy> strategy) {
strategy_ = std::move(strategy);
}
void ContextInterface() const {
strategy_->AlgorithmInterface();
}
};
// 客户端代码
int main() {
Context context(std::make_unique<ConcreteStrategyA>());
context.ContextInterface(); // 输出 "ConcreteStrategyA AlgorithmInterface"
context.set_strategy(std::make_unique<ConcreteStrategyB>());
context.ContextInterface(); // 输出 "ConcreteStrategyB AlgorithmInterface"
return 0;
}
在这个示例中,Strategy
类是一个抽象策略,它定义了一个 AlgorithmInterface
方法,这个方法是所有具体策略必须实现的接口。ConcreteStrategyA
和 ConcreteStrategyB
类是两个具体策略,它们实现了 AlgorithmInterface
方法。Context
类是一个上下文,它维护一个策略对象的引用,可以动态地更改策略。
这样,客户端可以通过上下文来使用策略,而不需要直接创建策略。这使得客户端在不修改代码的情况下就可以使用不同的策略,提高了代码的可扩展性和可维护性。
模板方法模式
模板方法模式(Template Method Pattern)是一种行为设计模式,它在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
精髓就是 使用了类继承重载的特性,在虚基类中定义了系列的算法,以及一个以某种顺序或方式调用算法的函数(骨架\模板),然后 客户端 可以直接使用虚基类的指针调用这个函数,从而调用不同具体类的不同方法
总之就是 要调用相同顺序的方法,而不同实例的具体方法有差异
以下是一个简单的模板方法模式的示例:
#include <iostream>
// 抽象类
class AbstractClass {
public:
void TemplateMethod() const {
this->BaseOperation1();
this->RequiredOperations1();
this->BaseOperation2();
this->hook1();
this->RequiredOperation2();
this->BaseOperation3();
this->hook2();
}
protected:
void BaseOperation1() const {
std::cout << "BaseOperation1\n";
}
void BaseOperation2() const {
std::cout << "BaseOperation2\n";
}
void BaseOperation3() const {
std::cout << "BaseOperation3\n";
}
virtual void RequiredOperations1() const = 0;
virtual void RequiredOperation2() const = 0;
virtual void hook1() const {}
virtual void hook2() const {}
};
// 具体类
class ConcreteClass1 : public AbstractClass {
protected:
void RequiredOperations1() const override {
std::cout << "ConcreteClass1 says: Implemented Operation1\n";
}
void RequiredOperation2() const override {
std::cout << "ConcreteClass1 says: Implemented Operation2\n";
}
};
// 具体类
class ConcreteClass2 : public AbstractClass {
protected:
void RequiredOperations1() const override {
std::cout << "ConcreteClass2 says: Implemented Operation1\n";
}
void RequiredOperation2() const override {
std::cout << "ConcreteClass2 says: Implemented Operation2\n";
}
void hook1() const override {
std::cout << "ConcreteClass2 says: Overridden Hook1\n";
}
};
// 客户端代码
void ClientCode(AbstractClass* class_) {
class_->TemplateMethod();
}
int main() {
std::cout << "Same client code can work with different subclasses:\n";
ConcreteClass1* concreteClass1 = new ConcreteClass1;
ClientCode(concreteClass1);
std::cout << "\n";
std::cout << "Same client code can work with different subclasses:\n";
ConcreteClass2* concreteClass2 = new ConcreteClass2;
ClientCode(concreteClass2);
delete concreteClass1;
delete concreteClass2;
return 0;
}
在这个示例中,AbstractClass
类是一个抽象类,它定义了一个 TemplateMethod
方法,这个方法是算法的骨架。ConcreteClass1
和 ConcreteClass2
类是两个具体类,它们实现了 AbstractClass
类中的抽象方法。
这样,客户端可以通过 AbstractClass
的接口来使用算法,而不需要知道具体类的存在。这使得客户端在不修改代码的情况下就可以使用不同的算法,提高了代码的可扩展性和可维护性。
观察者模式
观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖者都会收到通知并自动更新。
仔细理解一下下面的代码类结构,
观察者定义了一个可以被主题调用的update函数,然后主题里维护了一个观察者列表,当主题有更新时通过遍历观察者列表,调用update函数,就是通知了
就这么简单,当然了,改成虚基类 使得具体实现类可以有多种,都是一样的
观察者模式特别适用于以下几种场景:
-
当一个对象的改变需要同时改变其他对象,而且它不知道具体有多少对象需要改变时。观察者模式允许一个对象通知其他依赖对象,而无需知道这些对象的具体信息。
-
当一个对象必须通知其他对象,而你又希望这两个对象是松耦合的。观察者模式提供了一种松耦合的设计,其中主题和观察者可以独立地改变和复用。
以下是一个简单的观察者模式的示例:
#include <iostream>
#include <list>
#include <string>
class IObserver {
public:
virtual ~IObserver() {}
virtual void Update(const std::string &message_from_subject) = 0;
};
class ISubject {
public:
virtual ~ISubject() {}
virtual void Attach(IObserver *observer) = 0;
virtual void Detach(IObserver *observer) = 0;
virtual void Notify() = 0;
};
class Subject : public ISubject {
private:
std::list<IObserver *> list_observer_;
std::string message_;
public:
virtual ~Subject() {
std::cout << "Goodbye, I was the Subject.\n";
}
void Attach(IObserver *observer) override {
list_observer_.push_back(observer);
}
void Detach(IObserver *observer) override {
list_observer_.remove(observer);
}
void Notify() override {
std::list<IObserver *>::iterator iterator = list_observer_.begin();
HowManyObserver();
while (iterator != list_observer_.end()) {
(*iterator)->Update(message_);
++iterator;
}
}
void CreateMessage(std::string message = "Empty") {
this->message_ = message;
Notify();
}
void HowManyObserver() {
std::cout << "There are " << list_observer_.size() << " observers in the list.\n";
}
};
class Observer : public IObserver {
private:
std::string message_from_subject_;
Subject &subject_;
static int static_number_;
int number_;
public:
Observer(Subject &subject) : subject_(subject) {
this->subject_.Attach(this);
std::cout << "Hi, I'm the Observer \"" << ++Observer::static_number_ << "\".\n";
this->number_ = Observer::static_number_;
}
virtual ~Observer() {
std::cout << "Goodbye, I was the Observer \"" << this->number_ << "\".\n";
}
void Update(const std::string &message_from_subject) override {
message_from_subject_ = message_from_subject;
PrintInfo();
}
void RemoveMeFromTheList() {
subject_.Detach(this);
std::cout << "Observer \"" << number_ << "\" removed from the list.\n";
}
void PrintInfo() {
std::cout << "Observer \"" << this->number_ << "\": a new message is available --> " << this->message_from_subject_ << "\n";
}
};
int Observer::static_number_ = 0;
void ClientCode() {
Subject *subject = new Subject;
Observer *observer1 = new Observer(*subject);
Observer *observer2 = new Observer(*subject);
Observer *observer3 = new Observer(*subject);
Observer *observer4;
Observer *observer5;
subject->CreateMessage("Hello World! :D");
observer3->RemoveMeFromTheList();
subject->CreateMessage("The weather is hot today! :p");
observer4 = new Observer(*subject);
observer2->RemoveMeFromTheList();
observer5 = new Observer(*subject);
subject->CreateMessage("My new car is great! ;)");
observer5->RemoveMeFromTheList();
observer4->RemoveMeFromTheList();
observer1->RemoveMeFromTheList();
delete observer5;
delete observer4;
delete observer3;
delete observer2;
delete observer1;
delete subject;
}
int main() {
ClientCode();
return 0;
}
在这个示例中,ISubject
是主题接口,它定义了添加、删除和通知观察者的方法。Subject
是具体的主题,它维护了一个观察者列表,并实现了 ISubject
的方法。IObserver
是观察者接口,它定义了更新方法。Observer
是具体的观察者,它实现了 IObserver
的方法。
这样,当 Subject
的状态发生改变时,所有的 Observer
都会收到通知,并自动更新。这使得 Observer
在不直接与 Subject
交互的情况下就可以获取 Subject
的状态,提高了代码的可扩展性和可维护性。
迭代器模式
迭代器模式(Iterator Pattern)是一种行为设计模式,它提供了一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。
这就很常见了,比如迭代器访问 vector和list,底层分别是数组和链表,但是迭代器隐藏了访问的细节,每次都贴心的返回给你具体的元素
以下是一个简单的迭代器模式的示例:
#include <iostream>
#include <vector>
// 抽象迭代器
class Iterator {
public:
virtual ~Iterator() {}
virtual bool HasNext() = 0;
virtual int Next() = 0;
};
// 抽象聚合
class Aggregate {
public:
virtual ~Aggregate() {}
virtual Iterator* CreateIterator() = 0;
};
// 具体聚合
class ConcreteAggregate : public Aggregate {
private:
std::vector<int> data_;
public:
ConcreteAggregate(const std::vector<int>& data) : data_(data) {}
Iterator* CreateIterator() override;
int GetItem(int index) const {
return data_[index];
}
int GetSize() const {
return data_.size();
}
};
// 具体迭代器
class ConcreteIterator : public Iterator {
private:
const ConcreteAggregate& aggregate_;
int index_;
public:
ConcreteIterator(const ConcreteAggregate& aggregate) : aggregate_(aggregate), index_(0) {}
bool HasNext() override {
return index_ < aggregate_.GetSize();
}
int Next() override {
return aggregate_.GetItem(index_++);
}
};
Iterator* ConcreteAggregate::CreateIterator() {
return new ConcreteIterator(*this);
}
// 客户端代码
int main() {
std::vector<int> data = {1, 2, 3, 4, 5};
ConcreteAggregate aggregate(data);
Iterator* iterator = aggregate.CreateIterator();
while (iterator->HasNext()) {
std::cout << iterator->Next() << "\n";
}
delete iterator;
return 0;
}
在这个示例中,Iterator
是一个抽象迭代器,它定义了 HasNext
和 Next
方法。Aggregate
是一个抽象聚合,它定义了 CreateIterator
方法。ConcreteAggregate
是一个具体聚合,它实现了 Aggregate
的方法。ConcreteIterator
是一个具体迭代器,它实现了 Iterator
的方法。
这样,客户端可以通过 Iterator
的接口来遍历 Aggregate
,而不需要知道 Aggregate
的内部结构。这使得客户端在不修改代码的情况下就可以遍历不同的聚合,提高了代码的可扩展性和可维护性。
中介者模式
这名字听起来就迷糊,但是耐心的看完代码就发现,就这,就这
总的就是在两个类之间间接调用的作用(传话)
同事可以调用send传话,也可以被中介调用 notify 被传话
同事调用的send实际调用了中介的send,中介根据是谁调用的,调用另外一个同事的notify方法,就通知(传递)到位了
中介者模式(Mediator Pattern)是一种行为设计模式,它定义了一个封装一组对象如何交互的对象。通过使这些对象明确地相互引用来促进松散耦合,并允许独立地改变它们的交互。
中介者模式的优点包括:
-
减少类之间的依赖关系:通过引入中介者对象,类之间不再互相引用,只与中介者对象打交道,从而减少了类之间的依赖关系。
-
提高对象的重用性:由于类之间的依赖关系减少,每个类都可以独立地改变和复用。
-
提高系统的灵活性:通过设置和更改中介者对象,可以动态地改变对象之间的交互。
中介者模式适用于以下情况:
-
一组对象以定义良好但复杂的方式进行通信:产生的相互依赖关系结构混乱且难以理解。
-
想定制一个分布在多个类中的行为,而又不想生成太多的子类。
假设你正在开发一个聊天室应用。每个用户都可以向聊天室发送消息,聊天室会将消息转发给所有的用户。你可以使用中介者模式来实现这个功能
以下是一个简单的中介者模式的示例
#include <iostream>
#include <string>
// 抽象中介者
class Mediator {
public:
virtual ~Mediator() {}
// 定义了发送消息的方法
virtual void Send(const std::string &message, Colleague *colleague) = 0;
};
// 抽象同事
class Colleague {
protected:
Mediator *mediator_; // 同事类中保存了中介者的引用
public:
Colleague(Mediator *mediator) : mediator_(mediator) {}
// 定义了发送消息和接收消息的方法
virtual void Send(const std::string &message) = 0;
virtual void Notify(const std::string &message) = 0;
};
// 具体同事1
class ConcreteColleague1 : public Colleague {
public:
ConcreteColleague1(Mediator *mediator) : Colleague(mediator) {}
// 实现了发送消息和接收消息的方法
void Send(const std::string &message) override {
mediator_->Send(message, this);
}
void Notify(const std::string &message) override {
std::cout << "Colleague1 gets message: " << message << "\n";
}
};
// 具体同事2
class ConcreteColleague2 : public Colleague {
public:
ConcreteColleague2(Mediator *mediator) : Colleague(mediator) {}
// 实现了发送消息和接收消息的方法
void Send(const std::string &message) override {
mediator_->Send(message, this);
}
void Notify(const std::string &message) override {
std::cout << "Colleague2 gets message: " << message << "\n";
}
};
// 具体中介者
class ConcreteMediator : public Mediator {
private:
ConcreteColleague1 *colleague1_;
ConcreteColleague2 *colleague2_;
public:
// 设置具体同事类
void SetColleague1(ConcreteColleague1 *colleague) {
colleague1_ = colleague;
}
void SetColleague2(ConcreteColleague2 *colleague) {
colleague2_ = colleague;
}
// 实现了发送消息的方法
void Send(const std::string &message, Colleague *colleague) override {
if (colleague == colleague1_) {
colleague2_->Notify(message);
} else {
colleague1_->Notify(message);
}
}
};
// 客户端代码
int main() {
ConcreteMediator mediator;
ConcreteColleague1 colleague1(&mediator);
ConcreteColleague2 colleague2(&mediator);
mediator.SetColleague1(&colleague1);
mediator.SetColleague2(&colleague2);
colleague1.Send("How are you?");
colleague2.Send("Fine, thanks");
return 0;
}
在这个示例中,Mediator
是一个抽象中介者,它定义了 Send
方法。Colleague
是一个抽象同事,它定义了 Send
和 Notify
方法。ConcreteMediator
是一个具体中介者,它实现了 Mediator
的方法。ConcreteColleague1
和 ConcreteColleague2
是两个具体同事,它们实现了 Colleague
的方法。
这样,ConcreteColleague1
和 ConcreteColleague2
可以通过 ConcreteMediator
来交互,而不需要直接相互引用。这使得 ConcreteColleague1
和 ConcreteColleague2
在不修改代码的情况下就可以独立地改变它们的交互,提高了代码的可扩展性和可维护性。
备忘录模式
备忘录模式(Memento Pattern)是一种行为设计模式,它提供了一种保存对象内部状态的方法,并在需要时可以恢复到这个状态。
精髓就是当前类,把类里的一个变量(备忘录),专门用一个类存储起来,备份到这个类里面,并定义了创建,更新,访问这个类的方法
备忘录模式的优点包括:
-
保存和加载内部状态:备忘录模式提供了一种保存和加载对象内部状态的机制,而不破坏对象的封装性。
-
简化了原发器类:原发器类可以将复杂的状态保存工作委托给备忘录对象。
备忘录模式适用于以下情况:
-
需要保存和恢复数据的相关状态场景:如用户当前的系统配置、编辑器的状态等。
-
提供一个可回滚(undo)的操作:备忘录模式通过保存和恢复数据的状态,可以实现撤销操作。
以下是一个具体的例子:
假设你正在开发一个文本编辑器。用户可以在编辑器中输入文本,也可以撤销他们的操作。你可以使用备忘录模式来实现这个功能:
class Editor {
private:
std::string text_;
public:
void Type(const std::string &text) {
text_ += text;
}
std::string GetText() const {
return text_;
}
std::unique_ptr<Memento> Save() {
return std::make_unique<Memento>(text_);
}
void Restore(const Memento &memento) {
text_ = memento.GetText();
}
};
class Memento {
private:
std::string text_;
public:
Memento(const std::string &text) : text_(text) {}
std::string GetText() const {
return text_;
}
};
// 客户端代码
int main() {
Editor editor;
editor.Type("Hello, ");
std::unique_ptr<Memento> memento = editor.Save();
editor.Type("world!");
std::cout << "Text: " << editor.GetText() << "\n";
editor.Restore(*memento);
std::cout << "Text: " << editor.GetText() << "\n";
return 0;
}
在这个例子中,Editor
是原发器,它可以创建备忘录来保存当前状态,也可以通过备忘录来恢复状态。Memento
是备忘录,它保存了 Editor
的内部状态。
这样,你就可以在不破坏 Editor
封装性的情况下保存和恢复 Editor
的状态,提高了代码的可扩展性和可维护性。
命令模式
命令模式(Command Pattern)是一种行为设计模式,它将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
精髓就是 命令类里包含执行函数和撤销函数,还包含要操作的对象的指针或引用,所以可以更改要操作的对象
要操作的对象就是包含了状态,可以被更改
而控制类,就是执行命令的类,包含了命令对象,这个命令对象可以通过接口修改,以及可以被执行
命令模式的优点包括:
-
降低系统的耦合度:命令模式将调用操作的对象和知道如何实施这个操作的对象解耦。
-
新的命令很容易添加:因为增加新的具体命令类不会影响到其他的类,所以增加新的具体命令类很容易。
-
可以很容易地设计一个命令队列:只需要增加一个队列即可实现命令列表的功能。
-
可以方便地实现对请求的Undo和Redo。
命令模式适用于以下情况:
-
系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
-
系统需要在不同的时间指定请求、将请求排队和执行请求。
-
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
以下是一个具体的例子:
假设你正在开发一个智能家居系统。用户可以通过遥控器来控制家电,如打开或关闭电灯、调高或调低空调的温度等。你可以使用命令模式来实现这个功能:
class Command {
public:
virtual ~Command() {}
virtual void Execute() = 0;
virtual void Undo() = 0;
};
class Light {
private:
bool isOn_ = false;
public:
void TurnOn() {
isOn_ = true;
std::cout << "Light is on.\n";
}
void TurnOff() {
isOn_ = false;
std::cout << "Light is off.\n";
}
bool IsOn() const {
return isOn_;
}
};
class LightOnCommand : public Command {
private:
Light &light_;
bool wasOn_;
public:
LightOnCommand(Light &light) : light_(light), wasOn_(light.IsOn()) {}
void Execute() override {
wasOn_ = light_.IsOn();
light_.TurnOn();
}
void Undo() override {
if (wasOn_) {
light_.TurnOn();
} else {
light_.TurnOff();
}
}
};
class RemoteControl {
private:
std::unique_ptr<Command> command_;
std::unique_ptr<Command> lastCommand_;
public:
void SetCommand(std::unique_ptr<Command> command) {
command_ = std::move(command);
}
void PressButton() {
if (command_) {
lastCommand_ = std::move(command_);
lastCommand_->Execute();
}
}
void PressUndo() {
if (lastCommand_) {
lastCommand_->Undo();
}
}
};
// 客户端代码
int main() {
Light light;
RemoteControl remote;
// 打开电灯
remote.SetCommand(std::make_unique<LightOnCommand>(light));
remote.PressButton();
// 撤销操作
remote.PressUndo();
return 0;
}
在这个例子中,Light
是接收者,它知道如何执行请求。LightOnCommand
和 LightOffCommand
是具体命令,它们实现了 Command
的 Execute
方法。RemoteControl
是调用者,它保存了一个命令,并提供了一个 PressButton
方法来执行命令。
这样,你就可以通过 RemoteControl
来控制 Light
,而不需要直接操作 Light
。这使得你可以在不修改 RemoteControl
的代码的情况下添加新的 Command
,提高了代码的可扩展性和可维护性。
访问者模式
访问者模式(Visitor Pattern)是一种行为设计模式,它允许你在不改变类的情况下增加新的操作。这是通过在访问者类中添加一个新的操作,然后在被访问的类中添加一个接受访问者的方法来实现的。
简单的看 就是 访问者 去访问元素(元素accept 访问者),元素调用访问者 对应元素的的create方法,假如有多个访问者,各个访问者对元素的操作不同,不同访问者访问元素时的效果也就不同
访问者里包含的是被执行的操作,不同访问者里都有相同函数名的操作,并且有多个,每个函数又对应于一个元素, 每个元素里又会调用它对应的函数,从而实现 相同的元素,对不同的访问者调用不同的函数
访问者模式适用于以下情况:
-
如果你需要在一个类的结构中执行复杂的操作,而这些操作需要对这个类的结构有深入的了解。访问者模式可以将这些操作的相关代码移动到一个单独的类中。
在这种情况下,访问者模式可以帮助你将复杂的操作逻辑从数据结构中分离出来。例如,你有一个表示复杂数学表达式的类结构,你需要在这个结构上执行一些复杂的操作,如求导、积分等。这些操作需要对表达式的结构有深入的了解,如果将这些操作的代码放在表达式的类中,会使得类变得复杂且难以维护。通过使用访问者模式,你可以将这些操作的代码移动到一个单独的访问者类中,使得表达式的类结构保持简单且易于维护。
-
如果你需要执行一些与类结构相关的操作,而这些操作可能会改变这个类的结构。访问者模式可以保护这个类的结构不被改变。
在这种情况下,访问者模式可以帮助你保护数据结构的完整性。例如,你有一个表示图形对象的类结构,你需要在这个结构上执行一些操作,如添加、删除或移动图形对象。这些操作可能会改变图形的结构。如果将这些操作的代码放在图形的类中,可能会导致图形的结构在操作过程中被破坏。通过使用访问者模式,你可以将这些操作的代码移动到一个单独的访问者类中,访问者类可以在执行操作时保护图形的结构不被改变。
-
如果你需要在不同的类上执行一些相关的操作,而这些类可能没有共同的接口。访问者模式可以为这些类提供一个统一的接口。
访问者模式的优点包括:
-
增加新的操作很容易:只需要添加一个新的访问者类即可。
-
将相关的操作集中在一个类中:访问者模式可以将一组相关的操作集中在一个类中,而不是分散在多个类中。
以下是一个简单的访问者模式的示例,我已经为代码添加了详细的注释:
#include <iostream>
// 访问者接口
class Visitor {
public:
virtual ~Visitor() {}
virtual void VisitConcreteElementA() = 0;
virtual void VisitConcreteElementB() = 0;
};
// 具体访问者
class ConcreteVisitor : public Visitor {
public:
void VisitConcreteElementA() override {
std::cout << "Visiting Concrete Element A.\n";
}
void VisitConcreteElementB() override {
std::cout << "Visiting Concrete Element B.\n";
}
};
// 元素接口
class Element {
public:
virtual ~Element() {}
virtual void Accept(Visitor &visitor) = 0;
};
// 具体元素A
class ConcreteElementA : public Element {
public:
void Accept(Visitor &visitor) override {
visitor.VisitConcreteElementA();
}
};
// 具体元素B
class ConcreteElementB : public Element {
public:
void Accept(Visitor &visitor) override {
visitor.VisitConcreteElementB();
}
};
// 客户端代码
int main() {
ConcreteVisitor visitor;
ConcreteElementA elementA;
ConcreteElementB elementB;
elementA.Accept(visitor);
elementB.Accept(visitor);
return 0;
}
在这个示例中,Visitor
是一个抽象访问者,它定义了访问各种元素的方法。ConcreteVisitor
是一个具体访问者,它实现了 Visitor
的方法。Element
是一个抽象元素,它定义了接受访问者的方法。ConcreteElementA
和 ConcreteElementB
是两个具体元素,它们实现了 Element
的方法。
这样,你就可以通过 Visitor
来访问 Element
,而不需要知道 Element
的具体实现。这使得你可以在不修改 Element
的代码的情况下添加新的 Visitor
,提高了代码的可扩展性和可维护性。