内功心法之设计模式(带开源框架案例分析)
目录
- 什么是设计模式?
1.1. 概念
1.2. 三大方向
1.3. 平台无关性 - 为什么要用设计模式?
2.1. 面向对象基本原则
2.2. 要达到的效果 - 如何使用设计模式?
3.1. 抽象公共,封装变化
3.2. 依赖转移
3.3. 功能分离
3.4. 迭代细化
3.5. 思考变化
3.6. 灵活运用 - 有哪些设计模式?
4.1. 创建型模式
4.2. 结构型模式
4.3. 行为型模式
1. 什么是设计模式?
1.1. 概念
模式是在特定环境下人们解决某类重复出现问题的一套成功或有效的解决方案
设计模式的公式如下:
设计模式 = 模式名称 + 问题 + 解决方案 + 效果。
长期编程实践中,踩坑踩出来的,可以用来提高软件的可维护性和可复用性。
各行各业都有自己的模板和套路,可以认为设计模式就是软件设计的一些套路。
我的观点设计模式像一个个已经存在的公式,是对一些软件设计规律的总结。然后被一系列的软件开发实践中发现的。
牛顿的苹果1.2. 三大方向
设计模式被分成了三大类:
- 创建型(Creational),5 种
- 结构型(Structural),7 种
- 行为型(Behavioral),11 种
从对象本身特点和对象与对象之间的关系去思考:
- 创建型:关注点在如何 创建对象。
- 结构型:关注点在于 对象的组合。
- 行为型:关注点在于 对象的交互。
因为这些不同的设计模式关注的领域不一样,所以平时往往会组合使用,很少会出现某个很单纯地使用某个模式。
1.3. 平台无关性
广泛应用于面向对象编程。支持面向对象的语言都可以应用,比如 C++,Python,PHP 等等。
通用套路,和平台无关。
2. 为什么要用设计模式?
2.1. 面向对象基本原则
传送门:一文读懂面向对象基本原则
要去符合面向对象的基本原则。设计模式是实现面向对象基本原则的重要方式。
几大基本原则如下:
- 单一职责,一个类只负责一个功能领域中的相应职责。
- 开闭原则,软件实体应对扩展开放,而对修改关闭。
- 里氏替换原则,所有引用基类对象的地方能够透明地使用其子类的对象。大意为如果你用了基类,那么我可以使用它的多个子类,而不用关心子类是怎么实现的。
- 接口隔离原则,使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。如果有接口,要对接口作下分类,避免大杂烩。
- 依赖倒置原则,抽象不依赖于细节,细节应该依赖于抽象。面向抽象编程。
- 迪米特法则,一个软件实体应当尽可能少地与其他实体发生相互作用。最小知识原则,不要和陌生人说话,只与直接朋友通信。
2.2. 要达到的效果
包括面向对象基本原则在内,如果放到工作中会有什么体现?
能很好地应对需求变化,用尽可能少的改动新增需求,扩展性好,可以提高开发的效率。稳定的结构,可维护性好,高内聚,低耦合,避免代码膨胀而发生牵一发而动全身的情况。
能很好地进行代码复用,避免重复造轮子,避免了不必要的体力活。
阅读上的改善,写代码容易读代码难,但有有应用设计模式的话,并且理解其中的设计思路,可以帮助我们理解代码。设计模式在一些开源库和源码中应用很多,所以也是一把打开开源世界代码的钥匙。
3. 如何使用设计模式?
总结几个关键词:抽象、解耦、分治、迭代。
3.1. 抽象公共,封装变化
对于整体业务,思考不变和变的部分,进行区分。
抽象和封装,也是一个建模的过程,建模就像素描一样,能够用简单的结构和交互,把整个业务模型给设计出来。
基于抽象和接口编程,多用组合,少用继承。
- 可以是 模板方法模式,对整体算法进行抽象,让子类用继承实现变化的方法。
- 可以是 策略模式,把算法抽象出基本接口,单独变化,让调用者可以动态地选择需要的算法。
- 可以是 桥接模式,让抽象和实现单独变化,然后使用的时候动态组合,让多维度场景更加灵活。
建立模型要注意的是,必定会损失精度,这个需要对业务详细分析。早起因为思考不全面,设计变化的频率会比较多。但一旦设计成熟后,后续的开发会变得简单。
房屋建模3.2. 依赖转移
依赖反转是 Ioc 或者 DI 。把依赖关系的注入转移到类外去维护。比如 Spring 框架做到了,建了一个 Spring 实例工厂,管理了对象的生命周期、实例化、依赖注入。现在 Spring 已经是很多开源框架的灵魂。
除了把依赖注入交给统一的工厂来处理,还有其他方式来实现依赖的转移。
如果业务中的对象有交互的地方,也是变化最大的部分,是考虑抽象出一个单独的层来对这层依赖进行连接。
- 比如 命令模式,使用了命令抽象层,对接收者和发送者解耦。
- 比如 门面模式,使用门面抽象层,把子系统的复杂交互对调用者屏蔽,只留最简单清晰的接口。
- 比如 中介者模式,使用了中介者抽象层,把复杂的依赖中心化在中介者中,调用者和使用者通过中介者进行解耦。
3.3. 功能分离
功能分离即分治。
一个整体,必然是由部分组成的。设计模式要做的,就是处理好这些部分直接的结构和行为。
通过分治,让每个对象负责的事情尽量独立且单一,更加符合单一职责问题。
- 比如 策略模式,把算法单独拆分出来,并且可替换。
- 比如 装饰模式,把增强功能单独拎出一个修饰者来实现,多个功能对应多个修饰者。
- 比如 责任链模式,把每一个请求处理者的工作内聚到一个个节点中。
- 比如 状态模式,把每个状态对应的数据结构和操作行为内聚到一个个状态类中。
这里需要找到粒度控制的平衡点。如果很执着地在很小的粒度上应用设计模式,反而会导致结构复杂,理解困难,修改繁琐。所以最重要的是合适,而不是生搬硬套。
三省六部制3.4. 迭代细化
有没有框架的设计是一步到位的?答案是没有。阿里巴巴的淘宝也是经过多次技术迭代才能够像今天那样如此强大,能够抗住双 11 的巨大流量。
架构要跟着业务发展走的,重要是要适合。
- 拥抱变化。随着业务的变化,设计也要跟着调整,进一步地细化。
- 不断完善。可以一开始定一个初步的模型,用简单的用例描述整体的设计。然后在去细化每一个点。如果开发中发现有设计不合理的地方,及时调整模型的结构,不断增强和完善。可以使用 UML 图进行辅助设计。
- 风险控制。在实践开发过程中,有一个很重要点,要做好风险控制。不能因为想到一个更好的方式,就急着进行颠覆式地调整。但也不能放任系统野蛮生产。
- 平衡。最好的方式,就是平衡的方式。在风险可控的情况下,对设计进行分步调整。或者确定一个大版本,进行某个功能点的集中改造。为了追求更美好的设计,却给开发带来一些未知的系统风险,是不可取的。
3.5. 思考变化
我们不是先知,无法预料到几十年后是怎样的变化的。
但我们可以根据当前的信息,确定接下来几个版本或者需求的发展动向。
然后进行思考和推测,在接下来的视野可见的发展趋势里,我们的设计是否是修改方便、扩展容易的?如果不是的话,要做出怎样的改变。
和迭代细化一样,思考变化也是需要找到一个平衡点,确保不因为过度思考,过度思考会把原本可以很简单的模型做得很复杂。
赛博朋克3.6. 灵活运用
设计模式是套路,但见过生搬硬套的武林高手吗?
李小龙的截拳道,就是集个家之所长,然后又融入了自己的格斗思考衍生出来的一种新武术体系。
截拳道这些模式是我们的基础,我们要做的是,反复思考、记忆、训练。理解每一种模式的定义、模型、应用场景以及优缺点,熟稔于心,记忆深刻。
然后像截拳道一样,有了各种设计模式的思想和理念,然而又不是经典模型,而是为了实战的变型,在业务模型中收到奇效。
到实战场景中,业务模型复杂,没有什么套路是可以。需要我们去变通,取长补短。
- 比如 责任链模式,在 Android 事件分发中的应用了使用了该模式,严格责任链要求 View 消费事件就不转发了,我们可以调整为既消费事件又对事件转发,实现多个 View 对同一个事件的联动。
- 比如 建造者模式,我们只需要生成一个对象的情况下,可以把模型简化再简化,抽象建造者和指挥者合二为一,结构变得轻巧而且调用简单,同时不失构建者模式的优点。
- 比如 访问者模式,如果对访问者没有扩展的需求,可以省略掉抽象访问者,甚至可以不需要双重委派。
设计模式不是一成不变的,我们借助这些理论和思想,多思考,然后从整体理论出发,适当做出一些设计上的调整。或者同时应用多种模式的思想,帮助复杂的业务去繁就简。
尽可能地朝基本原则靠拢,去符合 开闭原则。
4. 有哪些设计模式?
24 种设计模式,以及用一句话描述它们的特点(点击可进入相应的分析文章):