设计模式本质上是对于开发中的常见问题的封装。 *** 请注意:设计模式不是代码** 而是类似于如何解读一个问题,然后如何解决一个问题的解释方法。
在工作中我经常碰到的场景是这样的,很多在大学中即便是计算机科学专业,或者其他与IT领域相关的专业毕业的人们。即便他们在学校期间学习过设计模式,但从未受到足够的训练来使用它们。在开始工作之后,大多数人都会忘记设计模式,或者感觉在实际的开发中并无用处。
然而,大多数初级开发者无法认识到的是,理解设计模式的确是开发者必须具备的核心能力。对于开发人员来说,如果希望开发水平不断提升,那么一定会在更高级的开发任务中与设计模式不期而遇。如果在构建完整的架构 / 系统 / 解决方案时,无法识别或者应用设计模式来解决问题,且不说失去了提升自己的机会,项目本身在投入生产之后也可能会迅速变得岌岌可危。
设计模式为什么会于你有益的几点原因
长话短说:设计模式会让任务变得简单。
举个例子
有一个需求是要求一个类的所有属性都必须是私有的,并且保证不能被其他类获取。面对这样的需求时,是否要为每一个属性都加上private关键字?能不能使用创建者模式来保证实例变量无法被外界获取?
另一个原因
设计模式本身其实是一种对于解决方案的共识,通过所有人都认可的词汇来表示。这样你和其他同事之间进行沟通就会更加有效率。你可以说:“这种情况下实现一个单例模式就行了”。然后其他人就都会明白你建议背后的含义。如果没有这种共识,你将需要向大家解释所谓单例模式背后的含义,以及好处和使用场景。
在本文中,会通过应用我个人认为最常用的三种设计模式,向读者们展示设计模式是如何让具体代码变的更好的。
1. 策略模式
这是我最喜爱的一种模式。
使用设计模式允许你在运行时切换具体实现,而且不会影响客户端。对于需要处理很多条件的处理方式的场景来说这句有两个“处理”,大部分人的第一反应是if-elif-elif-else的处理方式。但是相比于直接实现一个单一方法,使用策略模式可以在代码运行时选择具体的处理逻辑。也就是说对于不同的处理逻辑,我们可以建立不同的类,从而将分发逻辑和处理逻辑解耦。
从开放-关闭原则中我们知道,代码应该对扩展开放,而对修改关闭。这一原则是我在刚刚成为编程新手时就学到的一个原则,我相信大多数人都至少听说过它。
但在具体的开发工作中应用开闭原则,最常见的场景就是在需要应用策略模式的时候。
具体来说,当多种处理逻辑需要统一对外接口的场景时,这种模式就非常趁手。实现策略模式大致涉及4个元素,包括客户端:
— 客户端 -> 发起上下文的地方
— 上下文 -> 客户端希望处理的场景
— 接口 -> 分发策略的模块
— 算法 -> 真实的处理逻辑
一起来看个例子
想象你有一个神级创业产品,最开始的MVP版本先从附近的邻居开始。因为客户都在周边,所以应用的第一版就只有使用自行车送货这一种选项,发布当日即获得100万的日活,很明显你的业务要起飞了。但不久后你的业务拓展到周边的城市,这时候还仅有自行车一种选项,听起来很难雇到合适的快递小哥。
于是,事情发生了变化:业务要求至少来个通过汽车配送的选项吧。作为首席架构师你应该知道:这只是一个开始。
在这之后各种交通工具你方唱罢我登场,铁路、飞机轮流排期。PackageDelivery这个类随着运输逻辑的增加变得越来越臃肿,越来越难以维护。任何一个小的缺陷都可能威胁整个类,于是即便只是修改了关于自行车运输的处理逻辑,也不得不引入全量回归,来测试整个运输流程。
作为一个有追求的架构师,你终于引入策略模式来解救这场灾难。于是你对所有开发者声明:每一个具体运输逻辑应该有自己的类,这些类我们称之为“策略”。这样一来,下次再需要引入“通过海运传输”这一新选项时,就不用再去修改主流程的代码了。
下面的伪代码展示了主类是如何应用这些策略,以及根据包裹类型分发给不同的策略
interface process PackageStrategy has
method processPackage(package);
//These strategies implement the algorithm by implementing the interface above.
class SendByRail implements process PackageStrategy has
method processPackage(package) {
//process the package that will be sent by Rail and return something
}
class SendByBike implements process PackageStrategy has
method processPackage(package) {
// process and return something
}
//strategies used by the context class
class Context {
private strategy: processPackageStrategy
method setStrategy(processPackageStrategy Strategy) does
this.strategy= strategy;
method executeStrategy(Package package) does
return strategy.processPackage();
}
// read that strategy from user (UI or Api)
class App {
create a new context instance;
get package info;
read the desired user choice
if (choice is rail) then
context.setStrategy(new SendByRail())
if (choice is bike) then
context.setStrategy(new SendByBike())
response = context.executeStrategy(package)
//do something with response.
2. 单例模式
一个类只有一个实例
单例模式适用于对于一个类仅仅需要其唯一实例的场景。限制类的实例化的主要动机在于希望能够统一维护和控制共享资源,比如数据库、存储或者文件。凭借单例模式的技巧,我们可以构建唯一的类实例,然后让其可在全局访问。
比如现代前端框架中对于统一状态管理的工具,通常都会使用单例模式生成全局唯一的实例来管理当前应用的状态。这句好像和上面的英文原文不一致
下面的JavaScript片段可以当作一个小小的实例,展示如何实现一个单例
class Store {
static instance;
constructor() {
if (!Store.instance) {
this._state = [];
Store.instance = this;
}
return Store.instance;
}
add(stuff) {
this._state.push(stuff);
}
}
const instance = new Store();
Object.freeze(instance);
export default instance;
// In other files
const a = new Store();
a.add("phone");
const b = new Store();
console.log(b._state) // outputs ["phone"] - shared state
很多人应该在工作中多多少少都会碰到类似场景:我们只想要全局唯一的一个实例。也许甚至直到看到这篇文章是才知道这是一种设计模式。
这再次回应了前文所说的,设计模式是开发人员在实践中总结出来的对于特定类型的问题专用解决方案、最佳实践。抛开那些看起来很高大上的名字,设计模式本身解决的问题都是很接地气的。
3. 观察者模式
观察者模式存在的土壤就是需要解决大量对象之间一对多关系的问题。观察者模式可以用于设置订阅机制,以便在一个实体上发生某种你感兴趣的行为时,将每次这种行为发生的事件通知给你。
Kafka, RabbitMQ都是在真实世界中通过实现pub/sub(一种观察者模式的变体)模式创造出的发布订阅系统。
下面是一些示例
— 在web开发领域中,尤其是React生态中,你一定听说过使用Redux来管理应用的状态。Redux就是一种观察者模式的实现。当你通过action更新了store中的状态,监听这一改变的组件会根据变更的状态调整自身的显示
*— *一旦代码被推送到远程Git仓库,CI环境会监视到这一事件,并执行构建
*— *事件驱动的过程式编程用于模拟观察者模式。与其他模式类似,这一模式的应用让整个系统的各个组件通过松散的关系各自链接。由此特性我们便可以开发并维护模块化的系统,以及在事件驱动系统中实现不同actor之间的明显分割
设计模式总是好的吗?
虽然当我们谈到设计模式的时候,总是讨论它广为人知的好的一面。然而当设计模式被滥用时,也会产生不好的副作用。
因此,开发者应当思考在项目中使用设计模式是否是有效并且恰当的,以及查找其他替代方案,并有能力比较替代方案和设计模式。
I’m not sure what’s wrong with people: they don’t learn by comprehending; they learn by rote or something else.” Their understanding is so weak! — Feynman, Richard P.
我在工作中以及技术交流中见过很多工程师,我经常会谈起关于设计模式的话题。我发现有一些工程师很擅长记住这些设计模式,然后就很有信心的认为自己已经掌握了设计模式的咒语可以随便施展。理解设计模式可不是仅仅记住那些炫酷的名字。
这就是为什么在理解每个设计模式之前,要了解它的固有限制以及应用它的时候需要进行权衡,是至关重要的。