赣州市住房和城乡建设局网站,wordpress开源博客系统,邯郸网站建设在哪里,代理服务器地址怎么填五、设计模式 文章目录 五、设计模式1.设计模式三大类型概述一、创建型设计模式二、结构型设计模式三、行为型设计模式 2.设计模式三大原则3.单例模式1.饿汉单例模式2.懒汉单例模式 4.线程安全的懒汉单例模式1.锁双重判断2.简洁的线程安全懒汉单例模式 5.简单工厂(Simple Facto…五、设计模式 文章目录 五、设计模式1.设计模式三大类型概述一、创建型设计模式二、结构型设计模式三、行为型设计模式 2.设计模式三大原则3.单例模式1.饿汉单例模式2.懒汉单例模式 4.线程安全的懒汉单例模式1.锁双重判断2.简洁的线程安全懒汉单例模式 5.简单工厂(Simple Factor)、工厂方法(Factory Method)1.简单工厂2.工厂方法 6.抽象工厂(Abstract Factory)7.代理模式Proxy优点缺点 8.装饰器模式优点缺点 9.代理和装饰的区别一、目的二、功能扩展方式三、结构修改四、关注点五、应用场景 10.适配器模式优点缺点 11.观察者模式优点缺点 这里贴出常用的23中设计模式。视频课程仅包含部分剩余部分需要找其他课程或者资料进行自学。
1.设计模式三大类型概述
C设计模式是一套被广泛认可的用于解决常见软件设计问题的最佳实践它们可以帮助开发者编写更加清晰、可维护和可扩展的代码。根据解决的问题类型设计模式通常被分为三大类创建型、结构型和行为型。以下是对每一大类的概述及其特点
一、创建型设计模式
创建型设计模式主要关注于对象的创建机制帮助使系统独立于如何创建、组合和表示对象。
特点 将对象的创建和使用分离增加代码的灵活性和可维护性。通过定义创建对象的接口或方法使得子类或具体实现类可以决定实例化哪个类。 常见模式 单例模式Singleton确保一个类只有一个实例并提供一个全局访问点。工厂方法模式Factory Method定义一个用于创建对象的接口让子类决定实例化哪一个类。抽象工厂模式Abstract Factory提供一个创建一系列相关或相互依赖对象的接口而无需指定它们具体的类。建造者模式Builder将一个复杂对象的构建与它的表示分离使得同样的构建过程可以创建不同的表示。原型模式Prototype通过复制现有的实例来创建新的实例而不是通过新建类。
二、结构型设计模式
结构型设计模式关注于类和对象的组合用于形成更大的结构以解决如何将对象和类组合成较大的结构同时保持结构的灵活和高效。
特点 通过组合和继承等方式将对象或类组合成更大的结构。强调对象之间的静态关系以及如何通过不同的组合方式获得更加灵活的程序结构。 常见模式 适配器模式Adapter将一个类的接口转换成客户期望的另一个接口。桥接模式Bridge将抽象部分与实现部分分离使它们可以独立变化。组合模式Composite将对象组合成树形结构以表示“部分-整体”的层次结构。装饰器模式Decorator动态地给一个对象添加一些额外的职责。外观模式Facade提供一个统一的接口用来访问子系统中的一群接口。享元模式Flyweight运用共享技术有效地支持大量细粒度的对象。代理模式Proxy为其他对象提供一种代理以控制对这个对象的访问。
三、行为型设计模式
行为型设计模式特别关注对象之间的通信以及如何通过对象之间的协作来实现特定的功能。
特点 强调对象之间的动态关系以及如何通过对象之间的交互来实现特定的行为。通过定义对象之间的交互规则和通信方式使得系统更加灵活和可扩展。 常见模式 责任链模式Chain of Responsibility为请求创建一个接收者对象的链。命令模式Command将一个请求封装为一个对象从而使你可用不同的请求对客户进行参数化。解释器模式Interpreter给定一个语言定义它的文法的一种表示并定义一个解释器这个解释器使用该表示来解释语言中的句子。迭代器模式Iterator提供一种方法顺序访问一个聚合对象中各个元素而又不暴露其内部的表示。中介者模式Mediator用一个中介对象来封装一系列的对象交互。备忘录模式Memento在不破坏封装的前提下捕获并保存一个对象的内部状态以便在将来的时间点上恢复对象到这个状态。观察者模式Observer定义对象间的一种一对多的依赖关系当一个对象的状态发生改变时所有依赖于它的对象都得到通知并被自动更新。状态模式State允许一个对象在其内部状态改变时改变它的行为。策略模式Strategy定义一系列的算法把它们一个个封装起来并使它们可相互替换。模板方法模式Template Method定义一个操作中的算法的骨架而将一些步骤延迟到子类中。访问者模式Visitor表示一个作用于某对象结构中的各元素的操作它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
综上所述C中的创建型、结构型和行为型设计模式各具特点分别关注于对象的创建、组合以及对象之间的通信和协作。这些设计模式在软件开发中具有重要的应用价值可以帮助开发者编写更加清晰、可维护和可扩展的代码。
2.设计模式三大原则
设计模式的三大原则通常指的是开闭原则Open/Closed Principle、里氏替换原则Liskov Substitution Principle和依赖倒置原则Dependency Inversion Principle它们是面向对象设计的基本原则旨在提高代码的灵活性、可维护性和可扩展性。以下是这三个原则的清晰简洁解释 开闭原则Open/Closed Principle 解释软件实体类、模块、函数等应该对扩展开放对修改关闭。这意味着当需要添加新功能时应该通过扩展现有代码例如添加新类、新接口等来实现而不是修改已有代码。目的提高代码的灵活性和可维护性减少因修改已有代码而引入的潜在错误。 里氏替换原则Liskov Substitution Principle 解释子类必须能够替换它们的基类而不会导致程序出错。这要求子类必须完全遵守基类所定义的接口契约即子类在替换基类时其行为应该与基类保持一致。目的确保系统的稳定性和可靠性避免子类破坏基类的行为预期。 依赖倒置原则Dependency Inversion Principle 解释高层模块不应该依赖于低层模块二者都应该依赖于抽象。抽象不应该依赖于细节细节应该依赖于抽象。这意味着在设计中应该通过接口或抽象类来定义高层模块和低层模块之间的交互而不是直接依赖于具体的实现类。目的降低模块之间的耦合度提高系统的可扩展性和可维护性。通过依赖抽象而不是具体实现可以更容易地在不改变高层模块的情况下替换低层模块的实现。
这三大原则共同构成了面向对象设计的基础它们指导我们如何设计更加灵活、可维护和可扩展的软件系统。遵循这些原则可以帮助我们避免常见的设计问题提高代码的质量和可维护性。
3.单例模式
单例模式一个类不管创建多少次对象永远只能得到该类型的一个对象的实例
常用到的比如日志模块数据库模块
需要注意的五个点
1、需要将构造函数私有化这样保证使用者无法通过构造函数创建新的单例对象 2、需要定义一个唯一的static实例对象 3、需要提供对外的接口返回这个唯一的实例对象 4、需要删除拷贝构造函数和赋值运算符重载函数保证使用者不能通过者二者构造新的对象 5、在类内声明了static对象还需要在类外进行定义
分为两类
饿汉式单例模式还没有获取实例对象实例对象就已经产生了 懒汉式单例模式唯一的实例对象直到第一次获取它的时候才产生初始化)
1.饿汉单例模式
饿汉单例模式 一定是线程安全的
**饿汉式单例模式在类加载时就创建实例。**这种方式的特点是线程安全因为实例在类加载时就已经被初始化而类加载是线程安全的由类加载器保证。此外饿汉式单例模式的实现相对简单。然而它的缺点是即使实例没有被使用它也会在类加载时被创建这可能会导致内存浪费。
创建步骤
1.构造函数私有化 使得用户不能随意调用构造函数没有那么轻易的创建对象的实例
2.定义一个唯一的类的实例对象既然已经让用户难以调用构造函数那么类应该提供这个唯一的实例化对象
3.定义接口让用户有办法获取类的唯一实例化对象的方法通常返回的都是指针类型
#includeiostream
#includethread
#includelist
#includemutex
#includeatomic
#includequeue
#includecondition_variable
using namespace std;class Singleton
{
public:static Singleton* getInstance()//3.定义接口让用户有办法获取类的唯一实例化对象的方法{return instance;}
private:static Singleton instance;//2.定义一个唯一的类的实例对象既然已经让用户难以调用构造函数那么类应该提供这个唯一的实例化对象Singleton(){}// 1.构造函数私有化 使得用户不能随意调用构造函数没有那么轻易的创建对象的实例Singleton(const Singleton) delete;Singleton operator (const Singleton) delete;
};Singleton Singleton::instance;int main()
{//打印出来的p1 p2 p3 都是同一块地址Singleton* p1 Singleton::getInstance();Singleton* p2 Singleton::getInstance();Singleton* p3 Singleton::getInstance();cout p1 p2 p3 endl;return 0;
}
2.懒汉单例模式
**懒汉式单例模式在首次使用时才创建实例。**这种方式的特点是实现了延迟加载即只有在需要实例时才创建它从而节省了内存。
把静态变量设置为指针通过初始化为空的方式不去分配内存直到使用时调用get才去分配内存。
创建步骤
1.构造函数私有化 使得用户不能随意调用构造函数没有那么轻易的创建对象的实例
2.定义一个唯一的类的实例对象既然已经让用户难以调用构造函数那么类应该提供这个唯一的实例化对象
3.定义接口让用户有办法获取类的唯一实例化对象的方法通常返回的都是指针类型
#includeiostream
#includethread
#includelist
#includemutex
#includeatomic
#includequeue
#includecondition_variable
using namespace std;class Singleton
{
public:static Singleton* getInstance()//3.定义接口让用户有办法获取类的唯一实例化对象的方法{if (instance nullptr){instance new Singleton();}return instance;}
private:static Singleton *instance;//2.定义一个唯一的类的实例对象既然已经让用户难以调用构造函数那么类应该提供这个唯一的实例化对象Singleton(){}// 1.构造函数私有化 使得用户不能随意调用构造函数没有那么轻易的创建对象的实例Singleton(const Singleton) delete;Singleton operator (const Singleton) delete;
};Singleton* Singleton::instance nullptr;int main()
{Singleton* p1 Singleton::getInstance();Singleton* p2 Singleton::getInstance();Singleton* p3 Singleton::getInstance();cout p1 p2 p3 endl;return 0;
}
然而懒汉式单例模式在多线程环境下可能会出现线程安全问题即多个线程可能会同时创建实例导致违反单例原则。为了解决这个问题可以在创建实例的方法上加上同步关键字synchronized但这会降低性能。
为了解决懒汉式单例模式在多线程环境下的线程安全问题和性能问题可以采用双重检查锁定Double-Checked Locking和volatile关键字。双重检查锁定可以确保在创建实例时只进行一次同步操作而volatile关键字可以确保变量的可见性和禁止指令重排序从而避免在创建实例时出现线程安全问题。
4.线程安全的懒汉单例模式
**可重入函数**这个函数还没执行完可不可以再被调用一次
在单线程中不可能发生除了递归在多线程中可能线程1还没运行完线程2就来运行了
如果这个函数可以在多线程环境下直接运行而且不发生竞态条件那就是可重入函数
而懒汉单例模式中getIntance并不是线程安全的
线程1进去了还没给instance赋值时间片到了给了线程2那线程2就给instance赋值了所以不是可重入函数所以懒汉单例模式并不是线程安全的
1.锁双重判断
#includeiostream
#includethread
#includelist
#includemutex
#includeatomic
#includequeue
#includecondition_variable
using namespace std;mutex mtx;
//锁双重判断
class Singleton
{
public:static Singleton* getInstance(){//锁的粒度太大了 在单线程环境中也要不停地加锁解锁//lock_guardmutex guard(mtx);if (instance nullptr){//放到if里面只要创建过后面就不会进来iflock_guardmutex guard(mtx);if (instance nullptr){instance new Singleton();/*1.开辟内存2.构造对象3.给instance赋值*/}}return instance;}
private://不加volatile的默认情况下线程会对代码数据段拷贝一份副本自己看自己的副本加上以后不拷贝副本只要instance发生改变所有线程都能立马看到它改变了//注意在下面初始化的时候也要加上volatile关键字static Singleton * volatile instance;Singleton(){}Singleton(const Singleton) delete;Singleton operator (const Singleton) delete;
};Singleton* volatile Singleton::instance nullptr;int main()
{Singleton* p1 Singleton::getInstance();Singleton* p2 Singleton::getInstance();Singleton* p3 Singleton::getInstance();cout p1 p2 p3 endl;return 0;
}
注意1.不加volatile的默认情况下线程会对代码数据段拷贝一份副本自己看自己的副本加上以后不拷贝副本只要instance发生改变所有线程都能立马看到它改变了
2.在下面初始化的时候也要加上volatile关键字
3.new具体步骤补充
开辟内存new 操作符首先为对象分配足够的内存空间。这是通过调用底层的内存分配函数如 malloc尽管在 C 中更常见的是使用 operator new来完成的。这个步骤确保了对象有足够的空间来存储其数据成员。构造对象一旦内存被分配new 操作符就会在该内存位置上调用类的构造函数来初始化对象。这是对象实际被“创建”或“构造”的时刻它的数据成员被赋予初始值如果有的话。给 instance 赋值最后new 操作符返回指向新构造对象的指针这个指针随后被赋值给静态成员变量 instance。这一步是将新创建的对象与类的静态成员变量关联起来的关键。
2.简洁的线程安全懒汉单例模式
class Singleton
{
public:static Singleton* getInstance(){static Singleton instance;return instance;}
private:Singleton(){}Singleton(const Singleton) delete;Singleton operator (const Singleton) delete;
};在C中类的静态局部变量的内存确实在程序启动时就已经为其预留但是变量的初始化会延迟到第一次执行到它所在的代码块所以这也是一种懒汉单例模式
而函数静态局部变量的初始化在汇编指令上已经自动添加线程互斥指令了因此不用担心线程安全的问题
#includeiostream
#includethread
#includelist
#includemutex
#includeatomic
#includequeue
#includecondition_variable
using namespace std;class Singleton
{
public:static Singleton* getInstance(){static Singleton instance;return instance;}
private:Singleton(){}Singleton(const Singleton) delete;Singleton operator (const Singleton) delete;
};int main()
{Singleton* p1 Singleton::getInstance();Singleton* p2 Singleton::getInstance();Singleton* p3 Singleton::getInstance();cout p1 p2 p3 endl;return 0;
}
5.简单工厂(Simple Factor)、工厂方法(Factory Method)
工厂模式主要是封装了对象的创建操作
1.简单工厂
#includeiostream
#includethread
#includelist
#includemutex
#includeatomic
#includequeue
#includecondition_variable
#includememory
using namespace std;class Car
{
public:Car(string name):_name(name){}virtual void show() 0;string _name;
};class BMW :public Car
{
public:BMW(string name) :Car(name) {}void show(){cout 获得了一辆宝马汽车 _name endl;}
};class Audi :public Car
{
public:Audi(string name) :Car(name) {}void show(){cout 获得了一辆奥迪汽车 _name endl;}
};enum CarType
{Bmw, AUDI
};class SimpleFactory
{
public:Car* createCar(CarType ct){switch (ct){case Bmw:return new BMW(X1);case AUDI:return new Audi(A6);default:cerr 传入工厂的参数不正确: ct endl;break;}return nullptr;}
};int main()
{/*1.原来是这样的但是对于用户来说根本不需要知道什么X1X6什么的Car* p1 new BMW(X1);Car* p2 new Audi(A6);*//*2.SimpleFactory* factory new SimpleFactory();Car* p1 factory-createCar(Bmw);Car* p2 factory-createCar(AUDI);p1-show();p2-show();*///3.使用智能指针管理资源unique_ptrSimpleFactory factory(new SimpleFactory());unique_ptrCar p1(factory-createCar(Bmw));unique_ptrCar p2(factory-createCar(AUDI));p1-show();p2-show();return 0;
}
该例子中使用SimpleFactory类封装两个汽车类的创建操作
一共2种使用方法即代码中的2种直接用或者通过智能指针间接用
简单工厂模式Simple Factory的缺点主要包括以下几个方面
违反开闭原则 开闭原则要求软件实体类、模块、函数等应该是可扩展的但不可修改的。然而在简单工厂模式中每当需要增加新的产品时都需要修改工厂类中的判断逻辑从而违反了开闭原则。 高内聚问题 简单工厂模式中的工厂类通常负责所有产品的创建这导致工厂类的职责过重不符合高内聚的原则。高内聚要求一个模块或类应该只负责一个功能或一个紧密相关的功能集合。 不利于扩展和维护 由于简单工厂模式中的工厂类集中了所有产品的创建逻辑随着产品种类的增加工厂类的逻辑将变得越来越复杂不利于系统的扩展和维护。当需要添加新产品时需要修改工厂类的代码这增加了代码的维护成本。 测试困难 在简单工厂模式中由于工厂类与具体产品类之间存在紧密的耦合关系这增加了单元测试的难度。为了测试某个具体产品类可能需要先实例化工厂类并调用其创建方法这可能会引入不必要的依赖和复杂性。 缺乏灵活性 简单工厂模式通常使用静态方法或全局方法来创建对象这限制了对象的创建方式和灵活性。例如在某些情况下可能需要使用不同的创建策略或根据不同的上下文创建不同的对象实例但简单工厂模式无法提供这种灵活性。
所以有了工厂方法和抽象工厂
2.工厂方法
#includeiostream
#includethread
#includelist
#includemutex
#includeatomic
#includequeue
#includecondition_variable
#includememory
using namespace std;class Car
{
public:Car(string name):_name(name){}virtual void show() 0;string _name;
};class BMW :public Car
{
public:BMW(string name) :Car(name) {}void show(){cout 获得了一辆宝马汽车 _name endl;}
};class Audi :public Car
{
public:Audi(string name) :Car(name) {}void show(){cout 获得了一辆奥迪汽车 _name endl;}
};class Factory
{
public:virtual Car* createCar(string name) 0;//这个就是所谓的工厂方法
};class BMWFactory :public Factory
{
public:Car* createCar(string name){return new BMW(name);}
};class AudiFactory :public Factory
{
public:Car* createCar(string name){return new Audi(name);}
};int main()
{unique_ptrFactory bmwfty(new BMWFactory());unique_ptrFactory audifty(new AudiFactory());unique_ptrCar p1(bmwfty-createCar(X6));unique_ptrCar p2(audifty-createCar(A8));p1-show();p2-show();return 0;
}
Factory的纯虚函数就是工厂方法
其实就是对每个类有又单独创建了一个创建它的对象的类就相当于封装了
1.完成了对对象的封装操作
2.贴合了软件的开闭原则对原来已有的功能封闭对扩展新功能开放
一个工厂对应了一个类的创建如果类很多的话会导致工厂也很多
缺点灵活性受限
工厂方法模式通常用于创建单个产品对象如果需要创建多个相关或依赖的产品对象可能需要使用其他模式如抽象工厂模式来替代。
6.抽象工厂(Abstract Factory)
对有一组关联关系的产品簇提供产品对象的统一创建
#includeiostream
#includethread
#includelist
#includemutex
#includeatomic
#includequeue
#includecondition_variable
#includememory
using namespace std;class Car
{
public:Car(string name):_name(name){}virtual void show() 0;string _name;
};class BMW :public Car
{
public:BMW(string name) :Car(name) {}void show(){cout 获得了一辆宝马汽车 _name endl;}
};class Audi :public Car
{
public:Audi(string name) :Car(name) {}void show(){cout 获得了一辆奥迪汽车 _name endl;}
};//车的相关系列产品 车灯
class Light
{
public:virtual void show() 0;
};class BmwLight :public Light
{
public:void show() { cout BMW Light endl; }
};class AudiLight :public Light
{
public:void show() { cout Audi Light endl; }
};//抽象工厂 对有一组关联关系的产品簇提供产品对象的统一创建
class AbstractFactory
{
public://这个就是所谓的工厂方法virtual Car* createCar(string name) 0;//工厂方法 创建车virtual Light* createCarLight() 0;//工厂方法 创建汽车关联的产品车灯
};class BMWFactory :public AbstractFactory
{
public:Car* createCar(string name){return new BMW(name);}Light* createCarLight(){return new BmwLight();}
};class AudiFactory :public AbstractFactory
{
public:Car* createCar(string name){return new Audi(name);}Light* createCarLight(){return new AudiLight();}
};int main()
{unique_ptrAbstractFactory bmwfty(new BMWFactory());unique_ptrAbstractFactory audifty(new AudiFactory());unique_ptrCar p1(bmwfty-createCar(X6));unique_ptrCar p2(audifty-createCar(A8));unique_ptrLight l1(bmwfty-createCarLight());unique_ptrLight l2(audifty-createCarLight());p1-show();p2-show();l1-show();l2-show();return 0;
}
缺点不支持单一产品的变化
抽象工厂模式适用于一组相关产品的创建但如果只有一个产品发生变化那么整个工厂都需要进行修改可能不够灵活。
其他的类甚至也要重写AbstractFactory里面新加的这个产品不然自己的类会变成虚函数但是实际上其他类本身也不提供这个产品比如宝马课程生产一个螺丝奥迪可能就没有这个时候就挺尴尬
小结
简单工厂 Simple Factory :
**优点**把对象的创建封装在一个接口函数里面,通过传入不同的标识,返回创建的对象
客户不用自己负责new对象,不用了解对象创建的详细过程
**缺点**提供创建对象实例的接口函数不闭合,不能对修改关闭
工厂方法 Factory Method
**优点**Factory基类,提供了一个纯虚函数(创建产品)定义派生类(具体产品的工厂)负责创建对应的
产品,可以做到不同的产品,在不同的工厂里面创建,能够对现有工厂,以及产品的修改关闭
**缺点**实际上,很多产品是有关联关系的,属于一个产品簇,不应该放在不同的工厂里面去创建,这样
一是不符合实际的产品对象创建逻辑,二是工厂类太多了,不好维护
抽象工厂 Abstract Factory
**优点**把有关联关系的,属于一个产品簇的所有产品创建的接口函数,放在一个抽象工厂里面AbstractFactroy派生类(具体产品的工厂)应该负责创建该产品簇里面所有的产品
**缺点**抽象工厂模式适用于一组相关产品的创建但如果只有一个产品发生变化那么整个工厂都需要进行修改可能不够灵活。
7.代理模式Proxy
通过代理类来控制实际对象的访问权限
代理模式Proxy Pattern 是一种结构型设计模式它提供一个对象的代理以控制对这个对象的访问。代理对象作为客户端和目标对象之间的中介客户端通过代理对象间接地访问目标对象。代理模式常用于延迟加载、访问控制、缓存等功能。
优点
隐藏实现细节客户端通过代理对象访问目标对象不需要知道目标对象的具体实现。增强目标对象可以在不修改目标对象代码的情况下为目标对象添加额外的功能。控制访问可以对目标对象的访问进行权限控制。减少系统开销例如通过代理实现延迟加载减少系统资源的消耗。
缺点
性能损耗代理对象会增加一层调用开销虽然这个开销通常很小但在高性能要求的场景下可能会成为瓶颈。代码复杂度增加引入代理模式后系统的代码复杂度会增加。
步骤
1.抽象公共类
2.委托类继承自公共类
3.代理类继承自公共类
4.以组合的方式使用代理对象
5.客户直接访问代理对象 相当于客户只能访问助理不能直接访问老板
#includeiostream
#includethread
#includelist
#includemutex
#includeatomic
#includequeue
#includecondition_variable
#includememory
using namespace std;//客户 助理proxy 老板 委托类class VideoSite//1.抽象类
{
public:virtual void freeMovie() 0;//免费看电影virtual void vipMovie() 0;//vip看virtual void ticketMovie() 0;//用券才能看
};class FixBugVideoSite:public VideoSite//2.委托类
{
public:virtual void freeMovie(){cout 观看免费电影 endl;}virtual void vipMovie(){cout 观看vip电影 endl;}virtual void ticketMovie(){cout 用券观看电影 endl;}
};//3.代理类
class FreeVideoSiteProxy :public VideoSite
{
public:FreeVideoSiteProxy() { pVideo new FixBugVideoSite(); }~FreeVideoSiteProxy() { delete pVideo; }virtual void freeMovie(){pVideo-freeMovie();//通过代理对象的freemovie访问委托类真正的freemovie}virtual void vipMovie(){cout 您目前只是普通用户需要升级VIP才能观看VIP电影 endl;}virtual void ticketMovie(){cout 您目前没有券需要购买电影券才能观看该电影 endl;}
private:VideoSite* pVideo;//4.以组合的方式使用代理对象//或者去掉构造和析构直接调用委托类也行 //FixBugVideoSite Video
};class VipVideoSiteProxy :public VideoSite
{
public:VipVideoSiteProxy() { pVideo new FixBugVideoSite(); }~VipVideoSiteProxy() { delete pVideo; }virtual void freeMovie(){pVideo-freeMovie();//通过代理对象的freemovie访问委托类真正的freemovie}virtual void vipMovie(){pVideo-vipMovie();}virtual void ticketMovie(){cout 您目前没有券需要购买电影券才能观看该电影 endl;}
private:VideoSite* pVideo;
};//这些都是通用的API接口使用的都是基类的指针或者引用 通过多态访问虚函数就是了
void watchMovice(unique_ptrVideoSite ptr)
{ptr-freeMovie();ptr-vipMovie();ptr-ticketMovie();
}int main()
{/*1.只有委托类没有代理类同一个用户p1看的时候可能就还得对这些调用加if else判断来判断身份从而控制访问权限什么电影能看什么不能看很麻烦不灵活*/VideoSite* p1 new FixBugVideoSite();p1-freeMovie();p1-vipMovie();p1-ticketMovie();/*2.通过代理不同身份的用户可以对不同类型的电影具有不同的访问权限*///第五步客户直接访问代理对象 相当于客户只能访问助理不能直接访问老板//游客unique_ptrVideoSite p2(new FreeVideoSiteProxy());watchMovice(p2);//VIPunique_ptrVideoSite p3(new VipVideoSiteProxy());watchMovice(p3);return 0;
}
类和接口的说明
VideoSite这是一个抽象基类定义了三个纯虚函数freeMovie、vipMovie和ticketMovie分别代表观看免费电影、VIP电影和用券观看电影的功能。这个类作为所有视频站点包括代理和委托的接口。FixBugVideoSite这是VideoSite的一个具体实现即委托类。它实现了所有三个虚函数分别输出相应的观看信息。这个类代表了一个实际的视频站点提供了观看电影的具体功能。FreeVideoSiteProxy和VipVideoSiteProxy这两个类都是VideoSite的代理类。它们各自持有一个指向VideoSite实际上是FixBugVideoSite的指针用于在需要时调用委托类的功能。代理类通过重写虚函数来控制对委托类功能的访问例如普通用户FreeVideoSiteProxy不能观看VIP电影或用券观看电影而VIP用户VipVideoSiteProxy则可以观看VIP电影但仍然不能用券观看在这个例子中VIP用户是否能用券观看取决于代理类的实现这里简单地限制了。 委托类和代理类的虚函数都是一样的都是抽象类里面的函数
代理类经过检查发现不合法没有权限就不会调用委托类对象
8.装饰器模式
装饰器模式主要是增加现有类的功能
为了增强现有类的功能通过实现子类的方式重写接口是可以完成功能扩展的但是代码中有太多的子类添加进来了
**装饰器模式Decorator Pattern**是一种结构型设计模式它允许你向一个现有的对象添加新的功能同时又不改变其结构。装饰器模式通过创建一个包装对象即装饰器来包裹原始对象从而可以在运行时动态地给对象添加职责。
优点
灵活性可以在不修改原有类的情况下增加新的功能。扩展性通过组合而非继承来扩展功能避免了继承带来的高耦合和代码膨胀问题。复用性装饰器和具体组件可以独立变化互不干扰。
缺点
装饰链复杂如果装饰链太长调试和维护会变得复杂。性能因为每次调用都会通过多个装饰器可能会有一定的性能开销。
#includeiostream
#includethread
#includelist
#includemutex
#includeatomic
#includequeue
#includecondition_variable
#includememory
using namespace std;class Car
{
public:virtual void show() 0;
};class Bmw :public Car
{
public:void show(){cout 这是一辆宝马汽车配置有基类配置;}
};class Audi :public Car
{
public:void show(){cout 这是一辆奥迪汽车配置有基类配置;}
};class Benz :public Car
{
public:void show(){cout 这是一辆奔驰汽车配置有基类配置;}
};//装饰器的基类 装饰器可以让增加的功能互相组合
class CarDecorator :public Car
{
public:CarDecorator(Car* p) :pCar(p) {}private:Car* pCar;
};//装饰器1 定速巡航
class ConcreteDecorator01 :public Car
{
public:ConcreteDecorator01(Car *p):pCar(p){}void show(){pCar-show();cout ,定速巡航;}
private:Car* pCar;
};//装饰器2 定速巡航
class ConcreteDecorator02 :public Car
{
public:ConcreteDecorator02(Car* p) :pCar(p) {}void show(){pCar-show();cout ,自动刹车;}
private:Car* pCar;
};//装饰器3 车道偏离
class ConcreteDecorator03 :public Car
{
public:ConcreteDecorator03(Car* p) :pCar(p) {}void show(){pCar-show();cout ,车道偏离;}
private:Car* pCar;
};int main()
{Car* p1 new ConcreteDecorator01(new Bmw());//功能组合p1 new ConcreteDecorator02(p1);p1 new ConcreteDecorator03(p1);p1-show();cout endl;Car* p2 new ConcreteDecorator02(new Audi());p2-show();cout endl;Car* p3 new ConcreteDecorator03(new Benz());p3-show();cout endl;return 0;
}9.代理和装饰的区别
C中的装饰器模式Decorator Pattern和代理模式Proxy Pattern都是结构型设计模式但它们在目的、功能扩展方式、结构修改以及关注点等方面存在显著的区别。
一、目的
装饰器模式主要用于动态地为对象添加额外的职责而不改变其结构。它允许在不改变现有对象代码的情况下通过创建一系列的装饰器类来增加、扩展或修改对象的功能。代理模式主要用于控制对其他对象的访问。它在客户端和实际对象之间引入了一个代理对象客户端通过代理对象访问实际对象。代理对象可以用于控制访问权限、延迟加载、远程访问等。
二、功能扩展方式
装饰器模式通过组合多个装饰器类来实现功能扩展。每个装饰器类都实现了与被装饰对象相同的接口并可以在调用接口方法之前或之后添加额外的行为。代理模式主要通过代理对象来控制访问实际功能一般是由被代理对象提供的。代理对象可以在访问实际对象之前或之后添加额外的逻辑如权限检查、日志记录等。
三、结构修改
装饰器模式通常不改变对象的结构只是在其上添加装饰器。装饰器与被装饰对象具有相同的接口因此可以替换或组合使用。代理模式虽然也引入了新的代理对象但代理对象通常包含了额外的逻辑这些逻辑在访问实际对象之前或之后执行。此外代理模式可能会改变客户端与实际对象之间的交互方式。
四、关注点
装饰器模式关注于对象的功能增强。它允许在不修改现有代码的情况下动态地为对象添加新的行为或功能。代理模式关注于对象的访问控制和管理。它提供了对实际对象访问的间接层以便在访问过程中添加额外的逻辑或控制。
五、应用场景
装饰器模式 组件扩展在大型项目中随着业务的增加需要添加新的功能时装饰器可以避免修改原有的基础组件。API增强当提供API给第三方调用时装饰器可以用于添加额外的功能如日志记录、安全校验等。权限管理装饰器可以用来控制对原有特定接口的访问权限。缓存机制在网络请求或数据库查询等操作中装饰器可以用来添加额外的缓存、重试、超时处理等功能。 代理模式 延迟加载可以在需要时才创建实际对象节省资源。远程代理用于控制对远程对象的访问通常用于网络编程中。保护代理用于控制对对象的访问权限增强安全性。缓存/缓冲代理用于缓存频繁访问的数据以减少计算或网络请求的开销。智能引用代理用于管理对象的生命周期确保对象在不再需要时被正确释放。
10.适配器模式
适配器模式让不兼容的接口可以在一起工作
**适配器模式Adapter Pattern**是一种结构型设计模式它允许接口不兼容的类一起工作。适配器模式将类的接口转换成客户端所期望的另一种接口形式使得原本不兼容的类可以合作无间。
优点
提高灵活性通过适配器客户端可以透明地访问不兼容的接口提高了系统的灵活性。复用性适配器使得已有的类可以被复用而无需修改它们的源代码。解耦适配器模式有助于将接口和实现解耦使得系统更加模块化。
缺点
代码复杂度增加引入适配器会增加系统的代码量和复杂度。性能损耗在某些情况下适配器可能会导致性能上的损耗因为它需要在客户端和适配对象之间进行额外的转换。
#includeiostream
#includethread
#includelist
#includemutex
#includeatomic
#includequeue
#includecondition_variable
#includememory
using namespace std;//VGA接口的电脑 TV投影仪也是VGA接口
class VGA
{
public:virtual void play() 0;
};//TV01表示支持VGA接口的投影仪
class TV01 :public VGA
{
public:void play(){cout 通过VGA接口连接投影仪进行视频播放 endl;}
};//实现一个电脑类只支持VGA接口
class Computer
{
public://由于电脑只支持VGA接口所以该方法的参数也只能支持VGA接口的指针和引用void playVideo(VGA* pVGA){pVGA-play();}
};//进了一批新的投影仪都只支持HDMT接口根本都插不到电脑上
class HDMI
{
public:virtual void play() 0;
};class TV02 :public HDMI
{
public:void play(){cout 通过HDMI接口连接投影仪进行视频播放 endl;}
};//由于电脑(VGA接口)和投影仪(HDMI接口)无法直接相连所以需要添加适配器类
class VGAToHDMTAdapter :public VGA
{
public:VGAToHDMTAdapter(HDMI *p):pHdmi(p){}//该方法相当于就是转换头做不同接口的信号转换的void play(){pHdmi-play();}
private:HDMI* pHdmi;
};int main()
{Computer computer;//电脑本身就支持VGA通过VGA投影到投影仪上computer.playVideo(new TV01());/*TV02只支持HDMI不支持AGVcomputer.playVideo(new TV02());表现为VGA*不接受一个TV02指针类型的参数方法1换一个支持HDMI接口的电脑这个就叫代码重构方法2买一个转换头适配器能够把VGA信号转成HDMI信号这是添加适配器类*///通过转换头可以通过HDMI接口投影仪播放视频computer.playVideo(new VGAToHDMTAdapter(new TV02()));return 0;
}
下面是对代码中各个部分的详细讲解
抽象接口定义
VGA 和 HDMI 是两个抽象基类分别定义了具有 play() 方法的接口。这两个接口代表两种不同的视频输出标准。
具体实现类
TV01 继承自 VGA表示一个支持VGA接口的投影仪其 play() 方法实现了通过VGA接口播放视频的功能。TV02 继承自 HDMI表示一个支持HDMI接口的投影仪其 play() 方法实现了通过HDMI接口播放视频的功能。
电脑类
Computer 类有一个方法 playVideo(VGA* pVGA)这个方法接受一个 VGA 接口的指针作为参数并调用该指针的 play() 方法。这表示电脑只能通过VGA接口播放视频。
适配器类
VGAToHDMTAdapter 类继承自 VGA但它内部持有一个 HDMI 接口的指针。这个适配器类实现了 VGA 接口的 play() 方法但在这个方法内部它调用的是内部 HDMI 接口指针的 play() 方法。这样VGAToHDMTAdapter 就起到了将HDMI接口转换为VGA接口的作用。
11.观察者模式
也称为监听者模式或发布-订阅模式
它属于行为型模式而行为型主要关注的是对象之间的通信
观察者模式主要关注的是对象的一对多的关系也就是多个对象都依赖一个对象当该对象的状态发生改变时其他对象都能接收到相应的通知
观察者模式Observer Pattern是一种行为设计模式它定义了一种一对多的依赖关系让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时会通知所有观察者对象使它们能够自动更新自己。
优点
松耦合观察者和被观察者之间通过抽象接口进行交互降低了它们之间的耦合度。灵活性观察者可以在任何时候增加或删除而不会影响被观察者的行为。扩展性强可以在不修改被观察者代码的情况下增加新的观察者。
缺点
性能开销如果被观察者状态频繁变化并且有很多观察者那么通知所有观察者可能会带来较大的性能开销。内存泄漏风险如果没有正确管理观察者的生命周期可能会导致内存泄漏。循环依赖观察者之间可能相互依赖导致复杂的依赖关系网。
例如
一组数据数据对象通过这一组数据生成 曲线图对象1/ 柱状图对象2/ 圆饼图对象3
当数据对象改变时对象1,2,3应该及时收到相应的通知
Subject主题有更改的时候应该及时通知相应的观察者去处理相应的事件
#includeiostream
#includethread
#includelist
#includemutex
#includeatomic
#includequeue
#includecondition_variable
#includememory
#includeunordered_map
#includelist
using namespace std;//观察者抽象类
class Observer
{
public:virtual void handle(int msgid) 0;
};//第一个观察者实例
class Observer1 :public Observer
{
public:void handle(int msgid){switch (msgid){case 1:cout Observer1 recv 1 msg endl;break;case 2:cout Observer1 recv 2 msg endl;break;default:cout Observer1 recv unkown msg endl;break;}}
};//第二个观察者实例
class Observer2 :public Observer
{
public:void handle(int msgid){switch (msgid){case 2:cout Observer2 recv 2 msg endl;break;default:cout Observer2 recv unkown msg endl;break;}}
};//第三个观察者实例
class Observer3 :public Observer
{
public:void handle(int msgid){switch (msgid){case 1:cout Observer3 recv 1 msg endl;break;case 3:cout Observer3 recv 3 msg endl;break;default:cout Observer3 recv unkown msg endl;break;}}
};//主题类
class Subject
{
public://给主题增加观察者对象void adObserver(Observer* obser, int msgid){_subMap[msgid].push_back(obser);}//主题检测发生改变通知相应的观察者对象处理事件void dispatch(int msgid){auto it _subMap.find(msgid);//没找着说明没人对这件事情感兴趣if (it ! _subMap.end()){for (Observer* pObser : it-second){pObser-handle(msgid);}}}
private:unordered_mapint, listObserver* _subMap;
};int main()
{Subject subject;Observer* p1 new Observer1();Observer* p2 new Observer2();Observer* p3 new Observer3();subject.adObserver(p1, 1);subject.adObserver(p1, 2);subject.adObserver(p2, 2);subject.adObserver(p3, 1);subject.adObserver(p3, 3);int msgid 0;for (;;){cout 输入消息id: ;cin msgid;if (msgid -1)break;subject.dispatch(msgid);}return 0;
}当主题改变的时候对消息关注的对象会收到通知