引言-
(节省时间可跳过引言)
最近真是多事之秋,中秋辞别待了六年的公司,被迫重新投入到人才市场的面试洗牌大军中。
这两天整理了简历,恰逢好友结婚又匆匆拿起相机去当了朋友婚礼的花絮摄影和剪辑。
这当中插空去面试了一家比较大的电商公司。
六年后的第一次面试,从面试官变成了被面试者,感慨颇多。
面试时面试官提到了设计模式,因为我简历也写过熟练运用设计模式
是的,但那是曾经,快4年前吧。写框架的时候基本把设计模式常用的那几种用了个遍
说句实话,现在让我看代码我能可圈可点评判它的所有实现细节和扩展意义
但问及他们的分类的数量以及名字,已经忘得七七八八
“ Talk is cheap, show me the code ”?
No!
这是在招聘
每个人手头上的活一大把
在仅有一面的情况下,往往Talk才是重点
所以趁着辞职空闲下来
我决定坐下来安安静静的重新复习一下所有的设计模式
更新下自从工作后就再没怎么更新过的博客
前言-
知名数学博主 3Blue1Brown 在 《线性代数的本质》教学视频中提及:
“普适的代价是抽象”
“Abstractness is the price of generality”当时在学线性代数时这句话给我很深刻的印象
我认为这句话本身也具有普适性
用在设计模式上也同样适用
丑话说在前面
以我个人粗浅的经验的纯粹个人观点来看
我不认为像我这样的普通人在无工作经验的情况下能真正搞懂设计模式
我认为学习设计模式自身一定需要已经具备至少1-2年的实打实的开发经验才行
(当然,理解能力很强的人不在此列)
因为设计模式本身就是在工作经验中提取总结出的共性的具有普适性的代码写法,能在或扩展性,或易读性,或多人协作性方面提供至少一项帮助。
所以工作年限越长的人,越容易与之产生共鸣,也越容易读懂和运用设计模式
所以如果你是初学者,看不懂也没关系。工作几年以后再看就行。
我不打算给任何代码示例
因为网上太多了
我的总结写给我自己,只是复习所以不会细讲。
正文:(复习用,非科普)
设计模式的类型
根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。另一类设计模式:J2EE 设计模式是由 Sun Java Center 鉴定的,不在经典23种之内本次不打算复习。
创建型模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
注意:不是不用 new,而是不直接用(藏在层层嵌套封装方法里偷偷用- -),懂吧
- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
简单工厂和抽象工厂一起说吧,凡是需要后期扩展方便的(少改代码)的,且行为一致的代码功能,都能这么搞。打个比方,java,php中访问数据库的库是不是可以选择数据源,且行为一致。mysql 或者 Oracle ,都是sql查询语法。
再比如在一些框架中,可以选择缓存数据存储的容器,也可以自行扩展容器。比如存文本,存mysql数据库,或存redis等 且行为都是 存 ,取 和 过期,
只要是实现同等行为的继承产品接口的类都能被工厂生产出来。
- 单例模式(Singleton Pattern)
一个类只用那一个new出来的对象,打个比方,写过接口的都知道request对象,这个对象从依赖注入进来的始终是全局唯一的那一个request对象不能变(孤本,独一份),因为head和params都在那个request对象里,换对象不就丢了
- 建造者模式(Builder Pattern)
这个跟工厂模式容易混淆,但其实区别挺大,我举个我写过的例子
写小票打印时我需要对接三个品牌的打印机,但每个品牌打印的都是来自相同订单的模板变量
所以我写了构造器,传入不同的打印机id,不同的模板id和相同的模板变量来组合和构建不同的打印模板对不同的设备发送远程打印指令
可以看出,和工厂模式的区别在于:
工厂模式是直接传入要获取的单个指定产品进行构建
而建造者模式则是设计一个流水线,每个环节可替换不同实现来影响最终产出的产品
- 原型模式(Prototype Pattern)
说白了就是复制出一个一摸一样的对象,如果你要复用一摸一样的对象(内部属性都一样),但是又必须独立成两个完全的个体,而非像单例模式一样一直只操作那一个孤本。那就写一个复制的方法专门用来复制用,注意区分深浅拷贝!浅拷贝引用不变且可能无原型链的东西(说的就是你js)复制vue对象用浅拷贝会被坑惨。而深拷贝需要实现递归深度遍历确保对象拷贝无遗漏。实现类似于递归遍历文件树
结构型模式
这些模式关注对象之间的组合和关系,旨在解决如何构建灵活且可复用的类和对象结构。
- 适配器模式(Adapter Pattern)
适配,适配 ,何为适配?要在windows上运行linux系统(适配者),目标接口是windows系统,而适配器就是虚拟机 windows可运行虚拟机,虚拟机能适配不同操作系统
- 桥接模式(Bridge Pattern)
还是我那个写小票打印的代码,我接收的参数其中一个是一个抽象的打印机接口,而我执行的是这个抽象类的打印方法,他必须有打印方法,也就是我这个方法可以桥接各种打印机,但从这个视角去看他,就是桥接模式
- 过滤器模式(Filter、Criteria Pattern)
在js里用拉姆达表达式链式操作过数组过滤器那就是过滤器模式的典型应用,其过滤后返回的this本身指代过滤后的数组对象,过滤器之间亦能组合调用,用来过滤筛选原数据类型的数据,但一定注意不能改变原数据类型
- 组合模式(Composite Pattern)
就是在一个对象中包含其他对象,这些被包含的对象可能是终点对象(不再包含别的对象),也有可能是非终点对象(其内部还包含其他对象,或叫组对象),我们将对象称为节点,即一个根节点包含许多子节点,这些子节点有的不再包含子节点,而有的仍然包含子节点,以此类推。
最简单的比方,比如双向链表节点类,有上节点引言和下节点类引用
- 装饰器模式(Decorator Pattern)
装饰器模式可以替代继承实现类似功能,这里单指扩展当前对象的属性和方法,其实就是装饰器的对象属性有个指向被装饰对象的属性,所以访问这个装饰器及可以访问被装饰对象,也可以访问装饰器自定义的额外属性和方法。
比如一个自定义的迭代器既可以得到原数组,也能使用迭代器自带的方法方便的操作原数组数据
- 外观模式(Facade Pattern)
比如php框架的门面模式,将一些原本需要通过构造函数 new对象才能使用的某类的对象方法。用门面类可以直接通过类调用无需手写new对象再调用。屏蔽了new对象的细节,提供更简洁交互方式。
- 享元模式(Flyweight Pattern)
如果单例模式是孤品,那享元就是限量n的精品,或者用熟悉的池更有代入感。比如数据库连接池,又比如fast-cgi协议复用的进程池。共享(分配)有限个资源,用完回收等待分配给下个人。
- 代理模式(Proxy Pattern)
接受参数与被代理者一致,代理处理后的返回结构与无代理时返回结构应一致,方便与实际被代理者无感调用。代码编写通常与装饰器模式相似,但目的不同,一个是为了附加功能属性,一个通常是为了控制访问
行为型模式
这些模式关注对象之间的通信和交互,旨在解决对象之间的责任分配和算法的封装。
- 责任链模式(Chain of Responsibility Pattern)
在JavaScript中,事件从最具体的元素开始,逐级向上传播的 事件冒泡。thinkphp框架中,事件event触发时在处理者链条中传递
- 命令模式(Command Pattern)
解耦请求者和执行者,事件的触发与事件触发时执行的动作是一样的,但触发时每一个监听者的触发执行方法都是自定义的回调钩子。每个接收者都是接收者接口的实现。
- 解释器模式(Interpreter Pattern)
对接收到的文本按既定规则字符串文本去解析,词法分析,语法分析,构建语法树等等,实现每一个词法和语法的解析器,按下文执行相应指令。
没错,就是在写高级语言。各种脚本语言,sql,nginx的配置文件,皆是实践
- 迭代器模式(Iterator Pattern)
如果想循环一个数据这个数据需要哪些必要方法才能被放在foreach里循环 ;要能get到当前遍历的对象,要能退到上一个,要能判断是否读完,要能重置到第一个,要能访问下标第N个,要能得到长度.. 如果一个数据的包装类实现了 循环规定的这些必要方法,那他就是迭代器,可以被循环遍历,哪怕这个数据可能不是数组而是个类对象或者树形数据
- 中介者模式(Mediator Pattern)
网络协议就是这个模式的最佳实践
我不知听谁说过,“ 一切复杂的程序设计逻辑多可以通过加中间层的方式去降低难度和解耦 ”,我觉得说的很有道理,在学网络协议时协议的分级让各自只专注各自的独立功能点,个阶层协议只和自己的下层对接和打交道,最终层层叠加完成整个复杂,庞大,精密的互联网运作。
再比如MVC框架:控制器作为模型和视图的中介者。将http响应处理部分分级,来简化各层的实现难度
- 备忘录模式(Memento Pattern)
PS软件的历史纪录,浏览器的后退按钮,游戏的保存,数据存储程序的快照,皆是在用一个结构方法去记录当时那一刻的相对于当前程序的空间信息(不同程序空间信息也不同,比如一个简单的定时器程序,空间信息可能仅仅是当时的时间变量值),以便于在某一刻可对其进行回溯和还原,我突然想到了git
- 观察者模式(Observer Pattern)
还是上面提到的事件订阅机制,事件的触发与事件的订阅是完全解耦且分离的。事件触发只是信号,只是水的涟漪。可以没有观察者,也可以有观察者,观察者观察时,就能看到谁的变化(事件订阅)
再想想进程间通信的信号量变化监听也是观察者模式
- 状态模式(State Pattern)
想想看,当你判断某个状态变量变换写执行逻辑,如果状态多,而你用switch去写的时候,状态多的时候就必须强耦合的去改switch的代码块,不断扩展,使这个方法越写越臃肿,不利于维护和阅读。如果每个状态是个独立的类,那执行的方法也是各自类的对象方法,那后期要和扩展状态增加一个对应实现类和方法就行。是不是更利于维护和扩展。因为状态改变对应行为也变了,根本不用switch 或者 if else 去判断
- 空对象模式(Null Object Pattern)
突然想到哨兵,这个在学数据结构和算法时会被经常使用。用来简化需要判断null的逻辑,从而简化了代码的复杂度,哨兵用来在开始或结束越界处根据需要设立空对象,避免null判断
- 策略模式(Strategy Pattern)
代码实现和状态模式神似,但目的不同,状态模式是被动监听状态变化执行相应方法,策略是主动决定变更策略。嗯,就这些
能想到的例子就是在写商城订单下单的折扣策略,根据后台配置策略会变更,调用方法也会不同,这就是主动了,嗯,可用策略模式
- 模板模式(Template Pattern)
这个感觉没什么可说的,面向对象的抽象类,接口类,本质上都能用来约束行为,规范模板。很多设计模式也是这么拿来用的。比如工厂模式的抽象产品也是约束产品实现的模板,建造者模式每个可替换实现策略的流程点都是其必须遵守的抽象模板,很多设计模式实现离不开抽象类,模板模式早就根深蒂固
- 访问者模式(Visitor Pattern)
代码实现与状态模式类似
在编译器的设计中,语法树是一个复杂的对象结构,需要对树中的每个节点执行不同的操作(如类型检查、代码生成等)。使用访问者模式可以将这些操作封装在访问者类中,使得编译器的设计更加灵活和可扩展。
在图形处理软件中,图形对象(如圆形、矩形、多边形等)可能需要执行多种不同的操作(如旋转、缩放、平移等)。使用访问者模式可以将这些操作封装在访问者类中,使得图形处理软件能够轻松地扩展新的图形操作。
J2EE 模式
这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。这里只提名字日后有缘再学习更新吧
- MVC 模式(MVC Pattern)
- 业务代表模式(Business Delegate Pattern)
- 组合实体模式(Composite Entity Pattern)
- 数据访问对象模式(Data Access Object Pattern)
- 前端控制器模式(Front Controller Pattern)
- 拦截过滤器模式(Intercepting Filter Pattern)
- 服务定位器模式(Service Locator Pattern)
- 传输对象模式(Transfer Object Pattern)