模式:在特定环境下的一套用来解决某一类重复出现的问题的解决方案。
0x00 为什么需要设计模式
避免做一些重复的设计、编写一些重复的代码。
0x01 设计模式的分类
设计模式可分为创建型(Creational)6、结构型(Structural)、行为型(Behavioral)三种。
设计模式一览表
模式类型 | 模式名称 | 使用频率 |
---|---|---|
创建型 | 单例Singleton | 常用 |
创建型 | 建造者Builder | 常用 |
创建型 | 工厂方法Factory Method | 常用 |
创建型 | 抽象工厂Abstract Factory | 常用 |
创建型 | 简单工厂Simple Factory | |
创建型 | 原型Prototype | |
结构型 | 组合模式Composite | 常用 |
结构型 | 装饰者Decorator | 常用 |
结构型 | 代理Proxy | 常用 |
结构型 | 外观Facade | 常用 |
结构型 | 桥接Bridge | |
结构型 | 享元Flyweight | |
结构型 | 适配器Adapter Pattern | |
行为型 | 迭代器Iterator | 常用 |
行为型 | 观察者Obverser | 常用 |
行为型 | 策略Strategy | 常用 |
行为型 | 命令Command | 常用 |
行为型 | 模板方法Template Method | 常用 |
行为型 | 职责链Chain of Responsibility | |
行为型 | 解释器Interpreter | |
行为型 | 中介者Mediator | |
行为型 | 备忘录Momento | |
行为型 | 状态State | |
行为型 | 访问者Visitor |
0x10 学习设计模式要点
模式是为了解决问题而产生的,不是用来创造问题的。
- 要清楚具体模式能解决什么问题,有哪些前提`条件。
- 不可滥用模式,不能为了使用模式而使用,只有当遇到了问题,并且经过分析发现在该情况下使用某某模式可以解决问题才去使用它。
0x11 面对对象设计原则SOLID
- 单一职责:Simple Responsibility一个类只负责一个领域的职责。
- 开闭:Open-Close对扩展开放,对修改关闭。软件实体应尽量在不修改原有代码的情况下进行扩展。
- 里式替换:Liskov Substitution任何父类出现的地方,都能使用子类替代。
- 接口隔离:Interface Segregation使用多个单一的接口,而不是单一的集成接口。
- 依赖倒置:Dependence Inversion具体依赖抽象,上层依赖下层。
另外还有组合/聚合复用Composite Reuse :尽量使用对象组合,而不是继承;以及迪米特软件实体应当尽可能少地与其他实体发生相互作用。
单一职责原则
设计一个客户信息图形统计模块,主要包含获取数据库连接信息,查询客户信息,图表的创建和展示;初始设计方案如下。
显然,该方式违背了单一职责原则,CustomerDataChart承担了太多职责,一旦其他类需要使用连接数据库或者查询客户信息等方法,都需要对CustomerDataChart进行修改。尝试将其分解为3个类。
- DBUtil类,只负责连接数据库。
- CustomerDAO类,只负责客户信息的CRUD。
- CustomerDataChart,只负责图表的创建和显示。
开闭原则
开闭原则是面向对象编程的目标。
以下代码违背了开闭原则,一旦新增新的图表,需要改动原有代码。1
2
3
4
5
6
7
8
9
10public void display(String type){
if (type.equals("pie")) {
PieChart chart = new PieChart();
chart.display();
}
else if (type.equals("bar")) {
BarChart chart = new BarChart();
chart.display();
}
}
下图则为一种符合开闭原则的架构,如遇到新增图表,只需要增加AbstractChart的实现类即可。
里式替换原则
下图展示了这一过程。p1的模式不利于扩展,p2中传递的参数是父类,扩展只需要继承即可,无需改动sender。
接口隔离原则
这里的“接口是指一个类型所具有的方法特征的集合,仅仅是一种逻辑上的抽象。
当一个“接口”承担太多功能时,由如下缺点。
- 很多时候用户只需要其中的一个功能,但必须实现接口的所有方法,产生了很多空方法。
- 使得client端见到了不应该看到的方法。
依赖倒置原则
依赖倒转原则是面向对象设计的主要实现机制之一。抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
- 在程序代码中传递参数时,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。
- 程序中尽量使用抽象层进行编程,而将具体类写在配置文件中,这样一来,如果系统行为发生变化,只需要对抽象层进行扩展。
- 在实现依赖倒转原则时,我们需要针对抽象层编程,将具体类的对象通过依赖注入DI的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。
在编写程序过程中,在大数情况下,这三个设计原则会同时出现,开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段,它们相辅相成,相互补充。
合成复用原则
通过继承来复用的主要问题在于继承复用会
破坏系统的封装性
,因为继承会将基类的实现细节暴露给子类,由于基类的内部细节通常对子类来说是可见的
,所以这种复用又称“白箱”复用,如果基类发生改变,那么子类的实现也不得不发生改变;从基类继承而来的实现是静态的,不能在运行时发生改变
,没有足够的灵活性;
- p1中使用了继承复用,当需要把数据库从MySQL升级为Oracle数据库时,需要修改CustomerDAO中的代码,或者修改DBUtil中的代码,违背了开闭原则。
- p2中使用了合成复用,当需要把数据库从MySQL升级为Oracle数据库时,根据里式替换原则,只需要新增DBUtil的实现类OracleDBUtil并注入到CustomerDAO即可。
迪米勒法则
不要和“陌生人”说话、只与你的直接朋友通信,直接朋友包含以下几种。
- 对象本身this。
- 以参数形式传入到当前对象方法中的对象。
- 当前对象的成员对象;
- 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
- 当前对象所创建的对象。