Design Patterns

补充一点跟设计模式不相关的cpp多态知识。

  • overload是函数重载,表示一个函数的对于输入不同参数时的不同表现。

  • override是函数重写,多用于重写父类的虚函数。

  • overwrite是函数的覆盖,直接使用与父类同名的函数名,在调用函数时就会直接使用子类的函数(子类对父类的覆盖)。

  • virtual void add();虚函数,在子类中实现,子类声明该函数时为void add() override;实现时可以不加override。void Derived::add(){}

  • virtual void add() = 0;纯虚函数是一种特殊的虚函数。在类的定义中,纯虚函数没有函数体,它的声明结尾处使用= 0来标识。含有纯虚函数的类是抽象类。抽象类不能被实例化,它的主要作用是为派生类提供一个接口规范,派生类必须重写(override)纯虚函数来实现具体的功能。纯虚函数,子类实现时同理。(现在我才明白cpp老师当时问的那个问题”抽象类不能实例化,能不能有非虚函数和变量?”,我当时回答是不可以,答案是可以,今天才知道这个问题是什么意思。)
    虚类要有一个虚析构函数。

设计原则

  1. 依赖倒置原则:高层次的代码(稳定)不应该依赖低层次的代码(变化)、抽象的代码不应该依赖具体的代码。
  2. 开放封闭原则:类模块应该开放扩展的,而其原先的代码尽量封闭不可改变。
  3. 单一职责原则:一个类应该仅有一个变化的原因,该变化隐含了它的职责,职责太多时会导致扩展时对代码东拉西扯,造成混乱。
  4. 替换原则:子类必须能够替换它的基类(IS-A),继承可以表达类型抽象。
  5. 接口隔离原则:接口应该小而完备,不该强迫用户使用多余的方法。
  6. 优先使用组合而不是继承:继承通常会让子类和父类的耦合度增加、组合的方式只要求组件具备良好定义的接口。
  7. 封装变化点
  8. 针对接口编程,而不是针对实现编程。

策略模式

策略模式定义了一个算法族,分别分装起来,是的他们之间可以互相变换。策略让算法的变化独立于使用它的客户。

一般情况下,比如我们的A类有好几个计算a这一量的策略,如果要在A类中实现这么多不同的行为,选择算法就需要很多if-else,不满足复用性,当需要增加策略时需要修改源代码,违背(开放封闭原则)。

使用策略模式就可以将策略抽象出来S类抽象类,然后A类中放一个S类的指针,之后通过向该类传入不同的S类的子类(即具体的实现),实现不同的行为,同时增加策略时不需要修改A类(通过使用另外的文件实现S的子类策略也可以做到不修改已有的策略和S类增加策略)。

观察者模式

观察者模式定义对象之间的一对多依赖,这样,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

Subject类三个vitual 函数添加删除和通知(RegisterObserver(Observer o)MoveObserver(Observer o)NotifyObserver(Observer o)),函数。

Subject子类有一个Observer类型的动态数组,存储观察者,构造函数会初始化该数组,实现父类的三个虚函数,有一个获取到更新(MeasurementChanged())就调用通知函数,然后通知函数调用各个Observe类的Update()函数。

Observe类一个vitual Update(//args)函数。

Observer子类实现Update函数。

DisplayElement类,为了 方便不同Observe实现不同显示方法,可以设置一个DisplayElement类(里面只有一个virtual Display()函数),继承Observer和DisplayElement的子类需要实现自己的Update和Display函数。

因为不同组件所需数据不同,所以可以将Observer类的Update函数设置为不传参的虚函数,同时子类创建时属性要添加一个Subject子类类型的变量,当Subject子类调用Observer子类Updata函数时,因为子类实现Update函数不同而可以根据需要来使用Subject子类的数据(因为有传入的Subject类)。

观察者模式解决了什么问题?
解决了与依赖倒置原则冲突,例如A类的变化需要通知B类,一般的处理是将A类中添加一个B类的类成员,在A类的变化时调用该类的函数,但是这样会导致A类和B类之间的耦合度增加。(即A类编译必须依赖于B类的存在)。

而观察则模式通过将通知这一变化抽象出来一个Subject类,A类继承S类,S类有一个动态数组来存放需要通知的类,(添加、移除被通知者的函数),还有一个Notify函数,同时有一个B类抽象出一个被通知的类Observer,Observer有一个Update函数,每个B类都可以自主实现不同的Update函数。
以上完成后,Notify调用各个Observer的Update函数,实现了观察者模式。

装饰者模式

装饰者模式动态地将额外责任附加到对象上,对于扩展功能,装饰者提供子类化之外的弹性替代方案。

简单来说就是一个类A,之后针对类A退出了新品BC(继承自A),如何对A扩展呢?可以另外使用a(继承自A),a是从A中抽象出来扩展A的类,a中有一个属性类是A,之后对A的扩展就可以继承自a,减少对A代码的修改。如图

工厂模式

工厂方法模式

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法让类把实例化推迟到子类。

商店类

抽象类是PizzaStoreOrderPizza(string type);//该函数会创建pizza,然后调用CreatPizza(type) ) 、( vitual CreatPizza();
继承该类的两个子类位NYPizzaStoreChicagoPizzaStore会实现CreatPizza()函数。两个子类的CreatPizza函数会通过typenew一个对应的pizza(NY_cheese_pizza

产品类

抽象类是Pizza(有属性:name, dough,sauce,vector toppings行为:Prepare();Bake();Cut();Box();GetName()
继承该类的多个子类NYStyleCheesePizza、NYStylePepperonPizza、ChicagoStyleCheesePizza等,子类的构造函数会对属性修改,以及对父类行为的覆盖。

总的流程线为,先创建商店(两个子类的实例),然后使用子类的OrderPizza()函数传入type,两个子类商店的OrderPizza()函数会调用CreatPizza()函数,通过该函数来实现动态生成不同Pizza。

也就是说PizzaStore()这个商店类的OrderPizza()方法并不能实现自动根据你的地区来决定你做出的是哪一种Pizza,而是你选择了不同的不同的地区的商店,Order就会根据你选择的不同来生成不同的Pizza()。

用代码来看就是,我们创建Pizza对象时,直接就选择了地区Pizza pizza = NYPizzaStore.OrderPizza("Cheese");

抽象工厂模式

例如一个原料生产抽象工厂A,A有各种原料的生产行为,但是A的行为都是抽象的,然后根据不同地区创建不同的B、C继承自A,BC实现具体的生产行为。

对于本题,如何将原料工厂应用到Pizza?可以给Pizza类一个B/C的属性类,之后修改Prepare()函数,将该函数使用的原料都使用B/C方法生产的原料。

工厂方法和抽象工厂的区别在于,工厂方法是一个类一个工厂,而抽象工厂是一个工厂生产多个产品。
 比如,Pizza类有一个属性类是原料工厂,之后在Prepare()函数中使用该工厂生产的原料。

单件模式

简单来说,单间模式就是一个只能实例化一次的类。

具体实现方法就是将类的构造函数声明为private,然后实现一个静态的公开方法,在这个方法中实现这个类的实例,这样还不够,还需要这个方法中避免创建多个实例,例如

Class Singleton{
private:
static Singleton unique_instance_;
Singleton();
public:
static Singleton GetInstance(){
if(unique_instance_ == nullptr){
unique_instance == new Singleton();
}
return unique_instance_;
}
}

比较难的点就是处理多线程情况,例如,在多线程中,可能会有多个线程同时调用GetInstance()函数,那么就会创建多个实例。(此处按下不表,请听下回分解)

命令模式

命令模式把请求封装为对象,以便使用不同的请求、队列或者日志请求来参数化其他对象,并支持可撤销的操作。

就是每个对应的类的不同行为创建为一个命令类。例如Light的on()和off()行为创建一个LightOnCommand类和LightOffCommand类。

适配器模式

适配器模式将一个类的接口转换为客户期望的另一个接口。适配器让原本接口不兼容的类可以合作。
比如缺鸭子对象,用火鸡冒充,那么就要继承于鸭子,类中有一个火鸡对象

外观模式

外观模式为子系统中的一组接口提供了一个统一的接口。外观定义了一个更为高级的接口,使得子系统更容易使用。

对于一组类,可以创建一个外观类,在该外观类中实现一个函数,该函数会调用其一组属性类的函数。

例如一个遥控器,遥控器可以控制电灯、风扇、音响等设备,那么可以创建一个遥控器类(该类由电灯、风扇、音响这几个类作为属性),在该类中实现一个影音模式的函数,该函数会调用其一组属性类的函数。

模板方法模式

我感觉这个模式就是在父类中定义一个函数,然后子类继承父类,子类实现父类的函数。类图和策略模式一样,但是策略模式是让父类的函数可以实现不同的功能,而模板方法模式是让父类的函数实现相同的功能。

迭代器模式

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

组合模式

组合模式允许你将对象组合成树形结构来表现“整体-部分”的层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

  • Component组件: 组合模式的“根节点”,定义组合中所有对象的通用接口,可以是抽象类或接口。该类中定义了子类的共性内容。
  • Leaf叶子:实现了Component接口的叶子节点,表示组合中的叶子对象,叶子节点没有子节点。
  • Composite合成: 作用是存储子部件,并且在Composite中实现了对子部件的相关操作,比如添加、删除、获取子组件等。

其实Leaf和Composite层次相同,都是一个总的Composite的子节点,不过因为Leaf和Composite重载函数的实现不同,从而实现不同的功能,却不更改其接口。

状态模式

状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
描述状态模式的图有点像数字逻辑中触发器。

代理模式(等到学完网络通信再说)

模型-视图-控制器

这是一个复合模式。
具体实现是在一个窗口中,有一个视图,一个模型,一个控制器。但是,视图和模型之间是没有直接联系的,而是通过控制器来联系的。而控制器可以和模型和视图联系起来。

graph TD
A[模型] --> B[控制器]
B --> C[视图]
C --> B
B --> A