1. 基础
1. 什么是设计模式?
设计模式是指在软件开发中,经过验证的,用于解决在特定环境下重复出现的特定问题的解决方案。
简单的说,设计模式是解决问题的固定套路。
慎用设计模式。
2. 设计模式是怎么来的?
满足设计原则后,慢慢迭代出来的。
3. 设计模式解决了什么问题?
前提:具体需求既有稳定点,又有变化点。
目的:期望修改少量的代码,就可以适应需求的变化。
比喻:整洁的房间,有一只好动的猫,怎么保持房间的整洁?把猫关在笼子里。
4. 设计模式基础
1. 面向对象的特性
- 封装:隐藏实现细节,实现模块化
- 继承:无需修改原有类的情况下,实现对功能的扩展
- 多态:静态多态:函数重载、模板。动态多态:虚函数动态绑定。
2. 面向对象设计的原则
- 依赖倒置 。高层模块不应该依赖底层模块,两者都应该依赖抽象。抽象不应该依赖具体实现,具体实现应该依赖于抽象。
- 开放封闭。一个类应该对扩展(组合和继承)开放,对修改关闭。
- 面向接口。
- 不将变量类型声明为某个特定的具体类,而是声明为一个接口
- 这样客户程序就无需知道对象的具体类型,只需要知道对象所具有的接口
- 从而减少系统中各部分的依赖关系,实现"高内聚、松耦合"的类型设计方案
- 封装变化点。将需求中的稳定点和变化点分离,使用扩展来封装变化点。
- 单一职责。一个类应该仅有一个引起它变化的原因,即一个类应该只负责一个职责或功能。
- 里氏替换。子类型必须能够替换它的父类型。要求子类覆盖父类实现时,必须保证职责和功能的一致性。
- 接口隔离。不应强迫客户端依赖它们不需要的接口,即应该将庞大、臃肿的接口拆分成多个更小、更专一的接口,使得每个接口只包含客户端所需要的方法。
- 组合优于继承。继承耦合度高,组合耦合度低。
- 最小知道原则。
5. 如何学习设计模式?
1. 目的
在尽量少的修改原有代码基础上,来适应未来需求的变化。
2. 学习步骤
-
该设计模式解决了什么问题?--> 稳定点、变化点
-
该设计模式的代码结构?
-
符合那些设计原则?--> 写符合设计原则的代码
-
如何在上面扩展代码?
-
该设计模式有哪些典型应用场景?
2. 模板方法------类行为型模式
1. 定义
在父类中定义处理流程的框架,在子类中实现具体处理的模式。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特性。
2. 解决的问题
稳定点:算法骨架
变化点:子流程需要变化
当一个操作大体流程是稳定的,而其中某些步骤的实现会发生变化时,可以使用模板方法。
3. 代码结构
- 基类中有骨架流程接口
- 所有子流程对子类开放并且是虚函数
- 多态的使用方式
4. 符合哪些设计原则
- 单一职责
- 开闭原则
- 依赖倒置
- 封装变化点
- 接口隔离
- 最小知道原则
5. 如何扩展
- 实现子类继承基类,覆写子流程
- 通过多态调用方式调用
6. 实例
上图中,AbstractDisplay类中定义了display方法,在该方法中依次调用了open、print、close这3个方法,虽然这3个方法都已经在AbstractDisplay中被声明了,但是都没有实体的抽象方法。这里,调用抽象方法的display方法就是模板方法,它定义了算法的处理流程框架,而具体的步骤实现由子类完成。
cpp
class AbstractDisplay
{
public:
// 模板方法
void display()
{
// 算法流程框架
open();
for (int i = 0; i < 5; ++i)
{
print();
}
close();
}
protected:
// 保护子类要覆写的子流程,这样子类的子流程只能由父类调用
virtual void open() = 0;
virtual void print() = 0;
virtual void close() = 0;
};
class CharDisplay : public AbstractDisplay
{
protected:
void open() override;
void print() override;
void close() override;
};
class StringDisplay : public AbstraceDisplay
{
protected:
void open() override;
void print() override;
void close() override;
};
3. 观察者模式------对象行为型模式
1. 定义
定义了对象间的一种一对多的依赖关系,当一个对象(subject)的状态发生变化时,所有依赖于他的对象(Observers)都得到通知,并自动更新。本质是"触发联动"。
2. 解决的问题
稳定点:一对多的依赖关系,一变化,多要跟着变化
变化点:"多"增加,"多"减少
3. 代码结构
- 一个Subject抽象类,作为观察对象,定义了注册观察者和删除观察者的方法。
- 多个ConcreteSubject类,具体的观察对象,当自身状态发生变化后,他会通知所有已经注册的Observer角色。
- 一个Observer抽象类,负责接受来自Subject角色的状态变化的通知。
- 多个ConcreteObserver类,是具体的观察者。
4. 符合哪些设计原则
- 面向接口编程
- 接口隔离:类与类的依赖建立在接口上面;
- 封装变化点:attach, detach
5. 如何扩展
- 继承实现接口
- 调用attach, detach
6. 实例
cpp
class Observer;
// Subject抽象类
class NumberGenerator
{
public:
void addObserver(Observer *);
void deleteObserver(Observer *);
virtual int getNumber() = 0;
virtual void execute() = 0;
private:
std::set<Observer *> observers;
};
// Observer接口
class Observer
{
public:
virtual void update(NumberGenerator *) = 0;
};
// 具体Subject类
class RandomNumberGenerator : public NumberGenerator
{
public:
int getNumber() override;
void execute() override;
private:
int number;
};
// 具体Observer类
class DigitObserver : public Observer
{
public:
void update(NumberGenerator *generator) override;
};
class GraphObserver : public Observer
{
public:
void update(NumberGenerator *generator) override;
}
int main(int argc, const char *argv[])
{
NumberGenerator generator = new RandomNumberGenerator;
Observer observer1 = new DigitObserver;
Observer observer2 = new GraphObserver;
generator.addObserver(observer1);
generator.addObserver(observer2);
generator.execute();
delete generator1;
delete observer1;
delete observer2;
}
4. 策略模式------对象行为模式
1. 定义
**定义一系列算法,把他们一个个封装起来,并使其可相互替换。**该模式使得算法可独立与客户程序进行变化。
消除if_else。
稳定点:抽象去解决它
变化点:扩展去解决它
2. 解决了什么问题
稳定点:客户程序与算法的调用关系
变化点:新加算法;算法内容改变
3. 代码结构
- Strategy接口
- ConcreteStrategy类
4. 符合哪些设计原则
- 接口隔离
- 依赖注入,通过一个接口解决两个类的依赖。
5. 如何扩展
- 定义新的具体类实现Strategy接口
6. 实例
5. 责任链------对象行为模式
1. 定义
**将多个对象组成一条职责链,然后按照它们在职责链上的顺序一个一个地找出到底应该谁来负责处理。**使用Chain of Responsibility模式可以弱化"请求方"和"处理方"之间的关联关系,让双方各自都成为可独立复用的组件。
2. 解决了什么问题
稳定点:处理请求的对象被组织为一个职责链;请求的处理方式。
变化点:对象的数量、顺序、具体每个对象的处理方式。
3. 代码结构
- Handler(处理者),定义了处理请求的接口,如果不能处理,则传给下一个处理者。
- ConcreteHandler(具体的处理者),实现处理请求的接口。
- Client(请求者),向第一个ConcreteHandler角色发送请求的角色。
4. 符合哪些设计原则
- 依赖倒置
- 接口隔离
- 开放封闭
- 单一职责
5. 如何扩展
- 定义新的类实现Handler接口
- 通过指定具体Handler的next属性指定下一个处理者。