设计模式是软件设计中常见问题的典型解决方案。设计模式与方法或库的使用方式不同,你很难直接在自己的程序中套用某个设计模式。
模式并不是一段特定的代码,而是解决特定问题的一般性概念。
你可以根据模式来实现符合自己程序实际所需的解决方案。
模式的历史
设计模式并不是晦涩的、复杂的概念,事实恰恰相反。模式是面向对象设计中常见问题的典型解决方案。同样的解决方案在各种项目中得到了反复使用,所以最终有人给它们起了名字,并对其进行了详细描述。
1994 年,出版的《设计模式: 可复用面向对象软件的基础》一书,将设计模式的概念应用到程序开发领域中。该书提供了 23 个模式来解决面向对象程序设计中的各种问题。由于书名太长,人们将其简称为 GoF。
为什么以及如何学习设计模式
- 设计模式是针对软件设计中常见问题的工具箱,其中的工具就是各种经过实践验证的解决方案。即使你从未遇到过这些问题,了解模式仍然非常有用,因为它能指导你如何使用面向对象的设计原则来解决各种问题
- 设计模式定义了一种让你和团队成员能够更高效沟通的通用语言。
关于模式的争议
设计模式自其诞生之初似乎就饱受争议。
- 一种针对不完善编程语言的蹩脚解决方案。通常当所选编程语言或技术缺少必要的抽象功能时,人们才需要设计模式。例如,策略模式在绝大部分现代编程语言中可以简单地使用 lambda 来实现
- 低效的解决方案。许多人会将这样的统一化认为是某种教条,而不会根据项目的实际情况对其进行调整,要注意,设计模式是问题的解决方案,而不是用解决方案来发现问题
- 不当使用。如果你只有一把铁锤,那么任何东西看上去都像是钉子。
设计模式分类
模式可以根据其意图或目的可以分为三类:
- 创建型模式(Creational Design Patterns): 提供创建对象的机制,增加已有代码的灵活性和可复用性
- 结构型模式(Structural Design Patterns): 介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效
- 行为型模式(Behavioral Design Patterns): 负责对象间的高效沟通和职责委派
创建型模式
创建型模式有:
- 工厂方法 Factory Method
- 抽象工厂 Abstract Factory
- 生成器 Builder
- 原型 Prototype
- 单例 Singleton
结构型模式
- 适配器 Adapter
- 桥接 Bridge
- 组合 Composite
- 装饰器 Decorator
- 外观 Facade
- 享元 Flyweight
- 代理 Proxy
行为型模式
- 责任链 Chain of Responsibility
- 命令 Command
- 迭代器 Iterator
- 中介者 Mediator
- 备忘录 Memento
- 观察者 Observer
- 状态 State
- 策略 Strategy
- 模板方法 Template Method
- 访问者 Visitor
面向对象设计原则
设计原则要比设计模式更重要。我们之后要讲的模式其中有的已经不重要了,实际应用非常少,有些已经被替代了。而设计原则在我们设计过程中是贯穿始终的。所有的设计模式都是根据设计原则进行抽象。设计原则是尺子,用于指导面向对象的设计。
- 依赖倒置原则: 高层模块(稳定)不应该依赖于低层模块(变化),二者都应依赖于抽象(稳定)
- 开闭原则: 对扩展开放,对修改封闭
- 单一职责原则: 一个类应该仅有一个引起他变化的原因
- 里氏代换原则: 子类必须能够替换他们的基类
- 接口隔离原则: 接口应该小而完备
- 优先使用对象组合而不是类继承: 类继承为白箱复用,对象组合为黑箱复用,继承在某种程度破坏了封装,子类父类耦合度高,而组合只要求被组合对象具有良好定义的接口
- 封装变化点: 使用封装来创建对象之间的分界层
- 针对接口编程而不是针对实现编程: 不要将变量声明为具体类,而应声明为某个接口
接口标准化是产业强盛的标志(秦的统一、活字印刷、汽车制造)。
各个原则(SOLID)之间也有着千丝万缕的联系,下面我们依次利用代码说明。
单一职责原则(S)
|
一开始,我们有一个订单类,内部有支付方法,当我们新增一个订阅类、会员类时,如果也需要支付方法,那么我们就需要拷贝相同的代码。
|
那么我们应抽离单独的支付类,保证类职责单一。
|
开闭原则(O)
|
如果这种写法,每次新增加存储类型,那么就需要重新测试全部代码。
我们可以利用继承扩展或构造扩展来遵守开闭原则。
|
里氏代换原则(L)
父类可以完全替换子类,也就是继承来的方法不允许重写,只允许在子类新增方法。
|
以上情况违反里氏代换原则,我们使用如下方式遵循。
|
接口隔离原则(I)
接口隔离原则要求单个接口的方法数尽可能少。
|
以上情况违反里氏代换原则,因为接口力度不够小,我们使用如下方式遵循。
|
依赖倒置原则(D)
依赖倒置是指:
- 不要在一个类中使用 new 创建一个对象
- 注入时应使用接口而不是具体类
|
以上方式,邮件与订单耦合在一起,无法测试,我们使用如下方式注入:
|
以上方式还是有问题,邮件与订单耦合在一起,无法替换,我们使用接口方式注入:
|