DesignPattern
设计模式
- strategy 策略模式
- observer 观察者模式
- decorator 装饰者模式
- factory 工厂模式(简单工厂/抽象工厂)
- singleton 单例模式
- command 命令模式
- adapter 适配器模式
- facade 外观模式
- template 模板模式
- iterator 迭代器模式
- composite 组合模式
- state 状态模式
- proxy 代理模式
- bridge 桥接模式
- builder 建造者模式
- chain_of_responsibility 责任链模式
- flyweight 蝇量模式
- interpreter 解释器模式
- mediator 中介者模式
- memento 备忘录模式
- prototype 原型模式
- visitor 访问者模式
根据设计模式所针对的问题,将设计模式分为三类:
- 创建型,创建型模式针对对象的创建。包括:工厂模式、单例模式、生成器模式、原型模式;
- 行为型,行为型模式针对对象的行为,如对象之间的通信、职责分配。包括:策略模式、观察者模式、状态模式、模板模式、命令模式、迭代器模式、责任链模式、中介者模式、解释器模式、备忘录模式、访问者模式。
- 结构型,结构型模式针对如何实现对象的结构。包括:装饰者模式、适配器模式、外观模式、桥接模式、组合模式、代理者模式、蝇量模式。
策略模式(Strategy)
介绍
策略模式: 将算法或操作抽象成实现共同接口、可以被替换
的类,实现逻辑和具体算法的解耦。
- 将各种行为
抽象成算法
,封装算法为对象; - 算法实现
共同接口
,调用者调用时不考虑算法具体实现,调用接口方法即可; - 调用者可随时替换此算法对象;
场景
- 多个方法择一使用,且他们会被随时替换;
- 方法没有共性,使用继承会有大量重写,使用接口会有大量重复使用;
实现
- 两个算法: 冒泡排序和快速排序;
- 抽象冒泡排序和快速排序为算法对象,实现算法接口,拥有 used() 被使用方法;
- 计算器计算时不用理会是什么算法,调用 used() 即可。
观察者模式(Observer)
介绍
观察者模式:主题主动向观察者推送
变化,解决观察者对主题对象的依赖。
- 观察者实现
被通知
接口,并在主题上注册,主题只保存观察者的引用
,不关心观察者的实现; - 在主题有变化时调用观察者的通知接口来通知已注册的观察者;
- 通知方式有
推
(主题变化时将变化数据推送给观察者)和拉
(主题只告知变化,观察者主动来拉取数据)
场景
- 一个主题,多个观察者,主题的任何变动,观察者都要第一时刻得到;
- 观察者获取主题变化困难,定时不及时,轮询消耗大;
- 观察者可以随时停止关注某主题;
实现
- 张三和李四是记者,他们需要及时了解城市发生的新闻;
- 张三和李四在电视台注册了自己的信息;
- 城市发生了新闻,电视台遍历注册信息,通知了张三和李四;
- 李四退休了,在电视台注销了自己的信息;
- 城市又发生了新闻,电视台只通知了张三;
装饰者模式(Decorator)
介绍
装饰者模式:包装一个对象,在被装饰对象的基础上添加功能
;
- 装饰者与被装饰对象拥有同一个超类,
装饰者拥有被装饰对象的所有外部接口
,可被调用,外界无法感知调用的是装饰者还是被装饰者; - 装饰者需要被装饰者作为参数传入,并在装饰者内部,在
被装饰者实现的基础上添加或修改某些功能
后,提供同被装饰者一样的接口; - 装饰者也可被另一个装饰者装饰,即嵌套装饰。
- 装饰者是一群包装类,�由于装饰的复杂性,会多出很多个装饰者小类;
场景
- 对象需要动态地添加和修改功能;
- 功能改变后不影响原对象的使用
实现
- 在商店内,花作为被装饰者对象、红丝带和盒子作为花的装饰者;
- 花、红丝带、盒子有共同的超类“商品”,他们都能被卖掉;
- 我们可以在红丝带装饰过花后,再用盒子再包装一次;
- 包装后的花,顾客买时也不会受到任何影响;
工厂模式(Factory)
介绍
工厂模式: 顾名思义,工厂模式是对象的生产器
,解耦用户对具体对象的依赖。
- 实现
依赖倒置
,让用户通过一个"产品工厂"依赖产品的抽象
,而不是一个具体的产品; - 简单工厂模式:接收参数并根据参数创建�对应类,将对象的实例化和具体使用解耦;
- 抽象工厂模式:将工厂
抽象出多个生产接口
,不同类型的工厂调用生产接口时,生产不同类型的对象; - 简单工厂常配合抽象工厂一起使用;
场景
- 根据不同条件需求不同的对象;
- 对象实例化的代码经常需要修改;
实现
- 简单工厂:向鞋厂内传入不同的类型(布制),鞋厂会生产出不同类型的鞋子(布鞋);
- 抽象工厂:有两座鞋厂:李宁鞋厂、Adidas鞋厂,他们能生产对应各自品牌的鞋子;
- 搭配使用:向不同的抽象工厂(李宁)传入不同的类型(运动类型),会生产出对应品牌对应类型的鞋子(李宁运动鞋);
单例模式(Singleton)
介绍
单例模式:保证同一个类全局只有一个实例对象
;
- 在第一次实例化后会使用
静态变量保存
实例,后续全局使用此静态变量; - 一般将构造方法私有化,构造方法添加 final 关键字无法被重写,添加一个类静态方法用于返回此实例;
- 在多线程时应该考虑
并发
问题,防止两次调用都被判定为实例未初始化而重复初始化对象;
场景
- 全局共享同一个实例对象(数据库连接等);
- 某一处对此对象的更新全局可见;
实现
- 利用 Go 中包的可见性规则来隐藏对象的实例化权限;
- 使用包变量保存实例对象,获取实例时判断是否已实例化,如为nil,实例化对象并返回,如有值,直接返回值;
- 待用锁实现 Go routine 并发时的问题;
命令模式(Command)
介绍
命令模式:将一个命令封装成对象
,解耦命令的发起者和执行者。
- 命令对象实现
命令接口(excute[、undo])
,命令发起者实例化命令对象,并传递此对象,并不关心此对象由谁执行; - 命令执行者只负责调用命令对象的执行方法即可,不关心对象是由谁生成的;
- 与策略模式不同之处:策略模式是通过不同的算法做同一件事情(例如排序),而命令模式则是通过不同的命令做不同的事情;
场景
- 命令发起者与执行者无法直接接触;
- 命令需要撤销功能,却不易保存命令执行状态信息时;
实现
- 指挥官创建了一个“从树下跑到草地上”的命令;
- 命令被分配给张三执行,张三作为军人,接到命令后不管命令的具体内容,而是直接调用命令的执行接口执行。
- 指挥官发布了撤销指令,张三又从草地上跑到了树下。
适配器模式(Adapter)
介绍
适配器模式:包装对象提供一个接口,以适配调用者
。
- 适配器通过一个中间对象,
封装目标接口
以适应调用者
调用; - 调用者调用此适配器,以达到调用目标接口的目的;
- 适配器模式与装饰者模式的不同之处:适配器模式不改变接口的功能,而装饰者会添加或修改原接口功能;
场景
- 提供的接口与调用者调用的其他的接口都不一致;
- 为一个特殊接口修改调用者的调用方式得不偿失;
实现
- 张三是个正常人,他能通过说话直接地表达自己;
- 李四是个聋哑人,他没法直接表达自己,但他会写字。
- 笔记本作为一个适配器,用笔记本“包装”了李四之后,当李四需要表达自己想法时,调用笔记本的“表达”功能,笔记本再调用李四“写字”的方法。
外观模式(Facade)
介绍
外观模式:通过封装多个复杂的接口
来提供一个简化接口
来实现一个复杂功能。
- 外观模式是通过封装多个接口来将接口
简单化
; - 外观模式不会改变原有的多个复杂的单一接口,这些接口依然能被单独调用,只是提供了一个额外的接口;
- 外观模式与适配器模式的不同之处:外观模式是
整合
多个接口并添加
一个简化接口,适配器是适配一个接口;
场景
- 实现某一功能需要调用多个复杂接口;
- 经常需要实现此功能,
实现
- 正常的冲咖啡步骤是:磨咖啡豆、烧开水、倒开水搅拌咖啡。
- 我们经常需要直接冲咖啡,而不是使用单一步骤,每次喝咖啡时调用三个方法很麻烦;
- 封装三个接口,额外提供一个 “冲咖啡” 的方法,需要喝咖啡时只需要调用一次冲咖啡方法即可。
模板模式(Template)
介绍
模板模式:模板模式在抽象类或父类
中抽象出算法步骤
作为模板,模板的具体细节推迟到子类实现。
- 模板模式在父类或抽象类中定义一个
算法的骨架
,并在父类或抽象类中实现共同的部分,各个不同的步骤由不同的子类分别实现; - 模板板式在父类的算法步骤中定义
勾子(hook)
,在子类中判断并定义一些不是非通用步骤; - 模板模式与策略模式的不同之处在于,策略模式是针对多个不同的算法,而模板模式是针对一个算法的不同步骤,在模板模式中,只有一个算法;
场景
- 多个算法有多个共同之处,但某些步骤略微不同;
- 各子类步骤顺序一致,但步骤的具体实现有所不同时;
实现
- 有发邮件和发短信两种通讯方式;
- 他们都需要获取目标信息、格式化正文、填写发送方信息,但实现不同;
- 在信息类中抽象出三个步骤,具体的处理方式由两种通讯方式各自实现;
- 发送信息时调用信息类中的发送方法,发送方法会按照顺序自动调用对应的步骤;
迭代器模式(Iterator)
介绍
迭代器模式:迭代器模式允许调用者在不知道类内部实现
的情况下遍历类元素
。
- 迭代器接口常用方法有
length(),next(),previous(),remove()
等; - 各类在内部实现迭代器接口,用对应的方法操作元素;
- 调用者不考虑类内部实现,调用迭代器接口即可;
场景
- 类使用不同的数据结构存储数据;
- 需要对不同的数据类型进行遍历等操作;
实现
- 使用 slice 存储一列战马,使用 map 存储一列士兵;
- 战马和士兵结构都实现了迭代器接口;
- 获取战马数和士兵数,遍历战马和士兵,调用迭代器接口即可;
组合模式(Composite)
介绍
组合器模式:使用一种组件抽象
来同时表达集合与元素�
,使用统一的接口来管理集合和元素。
- 组合模式通常为
树结构
,父结点和子节点具有同样的抽象和接口; - 在操作集合时,会
同时操作集合所属的具体元素
; - 通常给组合模式添加一个迭代器来完成组合结构的迭代;
场景
- 管理的多个对象构成树型层级结构;
- 操作高层级的对象时,需要同时其所属的下级对象,如界面窗口等;
实现
- 将军、队长、士兵构成树型层级结构,且他们都是战士,拥有战斗方法;
- 每位战士都保存着自己的下级名单,没有下级时忽略;
- 每个人在战斗时,都会率领着下级战斗;
状态模式(State)
介绍
状态模式:状态模式抽象出一个事物的状态
作为类,解耦事物和不同状态下的行为;
- 状态模式通过替换状态对象作为状态转换的方式;
- 状态对象实现根据状态动作的接口,可以根据不同的
动作做出对应
的反应; - 状态模式与策略模式的实现相似,但状态模式是对类内部状态作出改变,而策略模式是针对算法封装;
场景
- 事物有多种状态,且可以相互转换;
- 事物多种状态下对同一动作做出的行为不同;
实现
- 植物有 幼苗、开花和成熟 三种状态,且它们会通过浇水和收获的动作进行相互转换;
- 幼苗和开花时不能收获,只能浇水,成熟状态只能收获,不需要再浇水;
- 定义三种状态,和它们对不同动作时的行为,植物通过三种对象的替换来进行状态转换;
代理模式(Proxy)
介绍
代理模式:给对象提供一个代理,由代理对象控制对原对象的调用;
- 代理模式为一个对象(通常是大对象或无法复制的对象)
创建另外一个类作为其访问的接口
,所有对真实对象的请求都通过代理对象
完成; - 代理对象可以
控制用户对真实对象的访问权限
,也可以在访问真实对象时附加功能; - 代理模式可被用作:远程代理,虚拟代理,安全代理,指针引用,延迟加载;
场景
- 对象无法被直接访问时;
- 对象过大,初始化较慢;
- 对象不必要立刻初始化,可使用默认值代替;
实现
- 小明给暗恋对象写了一封信,在等回信;
- 邮递员是个非常忙的人,来不及去收取回信;
- 小明好声好气向邮递员要回信时,邮递员都推拖说自己要去取;
- 小明发怒了,邮递员终于抽时间去取了信给小明;
- 此信中邮递员就是代理模式中的代理,他实现了懒加载。
- 回信内容见源码:)
桥接模式(Bridge)
介绍
桥接模式:将事务的多个维度
都抽象出来以解耦抽象与实际
之间的绑定关系,使抽象和实际向着不同维度改变;
- 桥接模式通过对象的组合来解决事物的多维度变化问题,以替代多继承的不灵活;
- 桥接模式可以轻易在多维度上拓展,而不改变原有模式;
- 桥接模式与策略模式的不同之处:策略模式是针对一个不变的主题替换抽象算法,而桥接模式是策略模式的高维度状态,它的主题也可能会被替换;
场景
- 某事物在多个维度上都有变化;
- 无法使用多继承或使用多继承会很不灵活;
实现
- 作画时可以使用铅笔和圆珠笔等不同的笔,也可以在宣纸或普通A4纸;
- 抽象出笔和纸两种对象;
- 自由组合笔和纸进行作画;
建造者模式(Builder)
介绍
建造者模式:建造者模式分离创建复杂对象的过程和细节
,使得同样的创建过程能创建不同的对象。
- 建造者模式将创建对象部件的
一般过程抽象出接口
,而由不同的建造者类实现具体的接口,实现过程的步骤; - 通过建造者,调用者不用考虑对象创建过程的细节,且建造者也可以被灵活替换;
- 与模板模式的区别:建造者模式使用类的组合进行对象的创建,而模板模式使用类的继承实现对象的具体构造;
- 与工厂模式的区别:工厂模式会返回一个具体类,而建造者模式会建造出一个由多个类组装而成的完整类;
场景
- 对象的创建包含其他对象为类元素,创建过程复杂;
- 多个复杂对象的创建过程具有高度相似性;
实现
- 中国式建筑有金色屋顶和红色大门,而意式建筑有圆项和白色大门;
- 中国建筑师和意式建筑师分别擅长建造不同类型的建筑;
- 我们在盖不同类型的房子时先创建一个建筑师,再用建筑师去创建对应风格的房子;
责任链模式(Chain of Responsibility)
介绍
责任链模式:将请求处理者串成“链”
,依次尝试
处理请求,以此解耦请求和处理者;
- 责任链模式将任务处理者划分先后次序,依次尝试,直到任务被处理;
- 每个处理者
存储自己的下一环
;任务开始时选择最靠前的处理者试图处理任务; - 在遇到自己无法处理的情况,传递给自己的下一环来处理;
场景
- 适用于单个任务,多个处理者;
- 需要依次尝试处理者;
实现
- 公司里 leader、经理和CEO有不同额度的报销限额;
- leader报销不了的金额交给经理,而经理将自己处理不了的给CEO处理;
- 张三要报销200元,leader就能批准;
- 李四要报销8000元,leader报销不了,就交给经理,经理也处理不了,最后交给CEO报销;
蝇量模式(Flyweight)
介绍
蝇量模式:使用一个对象
来存储和模拟多个虚拟对象
,大大减少多个对象的内存占用。
- 实例化多个相似实例会占用较多内存空间,蝇量模式使用一个对象类变量保存多个对象的属性,以一个对象控制多个对象;
- 蝇量模式可以极大地减少内存占用,也可以方便对多个对象进行
统一管理
; - 实例一旦实现了蝇量模式,那么单个实例就无法再独立拥有不同的行为;
场景
- 有很多相似对象,拥有相同的属性项和方法;
- 多个对象只会被统一调用;
实现
- 在一片森林中,有很多大树,他们都只有高度一个属性;
- 将多个大树的属性保存一个森林对象的 map 中;
- 调用森林的砍伐方法,砍伐森林中所有的树;
解释器模式(Intepreter)
介绍
解释器模式:定义一种方法和对应的解释器
,使用解释器解释此方法的语句
来执行;
- 解释器模式需要上下文类来
定义和存储上下文
,解释器类用来将语句来翻译成可执行程序; - 解释器扩展和改变文化非常简单,构建完成后可以很方便地数据格式;
- 解释器模式会将非终结表达式
递归解释
,直到解释为终结符表达式;
场景
- 解释器模式适用于数据结构不规则,但数据要素相同的情况;
- 语法不能太复杂,复杂的最好使用解释形语言来实现以降低复杂性;
实现
- 在php中,php环境是上下文;
- 字符串值不能再向下解释了,如
"hello" "greeting"
都是终结符; - 在上下文中定义了两个变量
$greeting = "hello"; $test = "greeting";
- 现在来解释变量
$$test = "hello"
;
中介者模式(Mediator)
介绍
中介者模式:通过一个中介对象封装多个对象之间的交互
,解耦各个对象之前的相互依赖;
- 对象之间
通过中介者进行交互
,不必再显示调用目标对象; - 中介者对对象之间的关系进行了封装,减少了对象之间的耦合,使得对象可以独立改变和复用;
- 中介者模式与适配器模式和代理模式的不同之处:三者都通过中间对象解决对象之间的沟通问题,但他们要解决的问题和解决问题的对象都不同;
场景
- 中介者模式适合多对多的对象交互情况;
- 中介者适合对象之间交互较多,依赖复杂的情况;
实现
- 联合国作为多个国家之间的中间人存在,各国家之间通过联合国沟通;
- 法国和韩国尝试通过联合国隔空对话;
- 他们双方只向联合国喊话,并从联合国处获取对方国家的回应;
备忘录模式(Memento)
介绍
备忘录模式:使用一个备忘录对象记录并保存
对象内部状态,并能随时恢复
到保存的状态;
- 备忘录对象是一个类似于目标对象的轻量级对象,它保存着目标对象的可变属性;
- 备忘录保管者可以保存多个备忘录,并将对象恢复到任一时刻;
场景
- 备忘录模式适用于需要保存对象历史状态用以支持撤销的场景;
实现
- 时光掌控者保存着许多人类世界的“时间快照”;
- 小明18岁时身高175,体重70,时光掌控者此时获取小明的信息产生了一个快照;
- 小明在不停地长大,80岁时身高170,体重65;
- 时光掌控者选择小明18岁的快照对小时进行了恢复,小明又回到了18岁;
原型模式(Prototype)
介绍
原型模式:通过复制原型对象
再修改属性
的方式来快速创建新对象;
- 原型模式通过
抽象多个对象的相同属性和方法
来设置一个原型; - 原型模式可以通过原型对象设置对象的基本属性,减少创建出的对象的开放;
- 原型模式使得调用者只知道对象的原型而不必了解创建过程即可创建一个新对象;
场景
- 原型模式适用于对象较大或创建过程较复杂的情况;
- 适用于需要创建多个有共同“原型”的对象,也即它们拥有大部分共同属性;
实现
- 据说国家仪仗队的队员都是 年龄20岁、身高180、体重72kg的男性士兵;
- 抽象一个“年龄20岁、身高180、体重72kg”的人作为仪仗队员的“原型”;
- 创建一个仪仗队员原型,并设置姓名来产生一个真实的仪仗队员对象;
访问者模式(Visitor)
介绍
访问者模式:将对一些对象的访问过程抽象出类
,以实现在不改变对象的前提下对这些对象添加操作;
- 访问者模式
分离对象的数据结构和数据操作
; - 访问者模式将数据的访问方法集中到一个类中作为访问者,便于对数据访问的统一管理;
- 如果数据有添加或删除,需要修改多个访问者;
场景
- 访问者模式适用于数据结构稳定的类;
- 对对象的同一种数据有多种不同的操作方式;
实现
- 超市里的商品都有 名称和价格 两种属性,顾客使用购物车保存了要买的商品;
- 设置一个打印机访问者,访问并打印顾客购物车内的商品名称;
- 如果要添加一个商品价格计算器,只需要实现与打印机相同的访问者接口,访问并计算购物车中商品的价格;