策略模式
将行为封装为接口,实现算法族。在超类里面放入行为接口对象,在子类里面具体设定行为
原则:分离变化部分封装为接口,基于接口编程各种功能此模式让行为算法得变化独立与算法的使用者
举个栗子:鸭子抽象类里面有叫声,游泳方法,外形显示为抽象方法当有新的需求如鸭子会飞的方法时,需把飞的方法放到超类里面,但有些鸭子时不会叫,不会游泳,不会飞的。所以把叫,游泳,飞的行为封装为接口,这样就有了叫,游泳,飞各自的算法族,将接口对象放到超类里面,这样代码复用性提高,维护更方便
观察者模式
对象之间多对一依赖的一种设计方案
被依赖的对象为Subject,依赖的对象为Observer,Subject通知Observer变化
举个栗子:互联网气象站对象中有获取温度、湿度、气压、数据变化四个方法,实现Subject接口包括注册,删除,发布通知三个功能。公告板或第三方需要气象信息服务的对象实现Observer接口包括更新气象数据和显示功能。当有第三方需要获取气象服务时在互联网气象站对象 中注册,在气象信息发生变化时触发数据变化方法循环通知Subject里面所有注册的对象,第三方更新气象数据并进行显示
备注:JAVA中内置观察者Observeable是一个类而不是接口
装饰者模式
动态的将新功能附加到对象上,在对象功能扩张方面,它比继承更有弹性
举个栗子:饮料店有各种各样的单品,比如奶茶、咖啡。咖啡有不同种类如shortBlack、Espresso,饮料里面还可以添加调料如糖、甜奶、豆浆。将饮料抽象为超类drink(),包括描述Desc描述属性,price价格属性。抽象咖啡单品coffee()为子类继承drink,装饰类decorater()也继承drink。调料类milk继承decorater类,在decorater类中传入超类drink()对象,调用父类cost()方法将超类的价格加在子类的价格上,当有多个调料时会调用多次decorater类,每个调料调用传入超类对象的cost()方法,如此递归调用后得出drink()对象的总价。
单例模式
确保一个类只有一个实例对象,并提供一个全局访问点(private 修饰构造方法)
多线程优化:
- 同步(synchronized)getInstance方法
- “急切”创建实例(在类创建时就创建该对象,无论是否会用到)
- 双重检查加锁(使用volatile修饰初始化对象)
工厂模式
简单工厂模式:定义了一个创建对象的类,有这个类来封装实例化对象的行为
工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类,工厂方法模式将对象的实例化推迟到子类
依赖抽象原则
- 变量不要持有具体类的引用
- 不要让类继承具体类,要继承自抽象类和接口
- 不要覆盖已实现的方法
抽象工厂模式:定义了一个接口用于创建相关或有依赖的对象族,而无需指明具体类
举个栗子:有一家披萨店能做不同口味的披萨,简单工厂模式定义了一个创建披萨对象的类,用这个类来创建不同口味的披萨对象
当这个披萨店要开分店时,得再创建一个分店的创建披萨类,这时就需要修改原来的代码。工厂模式把创建披萨的类抽象,具体哪个分店需要怎样的创建披萨类则继承该抽象类,在子类里面维护不同口味的披萨类的对象创建
抽象工厂模式则是定义了一个抽象工厂接口用于创建披萨,不同的工厂继承接口来维护该工厂生产的不同口味的披萨,在创建披萨时传入不同的工厂对象,使用不同的工厂对象产出不同的披萨
命令模式
将请求,命令,动作等封装成对象,这样可以让项目使用这些对象来参数化其他对象。使得命令得请求者和执行者解耦
举个栗子:遥控器开关灯,音响功能传统面向对象方法定义一个遥控器接口,在实现该接口的遥控器对象构造里面传入要遥控的对象,通过开关调用传入对象的开关功能
命令模式则是在此基础上将遥控器的命令定义为一个接口,不同媒体实现该接口维护各自的媒体命令。在实现遥控器接口的遥控器对象构造里面传入命令对象,将命令对象分配到遥控器对象的开关命令数组里面,通过传入命令数组下标调用对应命令对象的方法
适配器模式
将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容
-
用户角度看不到被适配者,是解耦的
-
用户调用适配器转化出来的目标接口方法
-
适配器再调用被适配者的相关接口方法
-
用户收到反馈结果,感觉只是和目标接口交互
类适配器:通过多重继承目标接口和被适配者类的方式来实现适配
举个栗子:鹅冒充鸭子。一家老北京烤鹅店,有天老板突然想吃烤鸭。目标接口为Duck()、适配器类Adapter()、被适配者Goose(),Adapter()实现目标接口使得对外表现为Duck。
对象适配器使用组合的方法实现适配,在适配器类中传入被适配者对象,在适配器类中调用被适配器对象的方法。这样做的好处比较灵活,被适配器的子类的对象也可以用这个适配器。
类适配器使用继承的方法实现适配,在适配器中直接继承被施佩器这个类,直接调用父类的方法。如果被适配器的子类要使用这个适配器就得重新继承,这样显得不够灵活。在实际使用中建议是多用组合少用继承
外观模式
提供一个统一的接口,来访问子系统中一群接口相关的功能
外观模式定义了一个高层接口,让子系统更容易使用
外观与命令模式的侧重点
- 外观模式对外暴露接口的简化,使得外面和里面解耦
- 命令模式则是侧重于把命令包装成对象,实现接口,控制器和设备的解耦
举个栗子:还是上次的媒体遥控的场景,不同设备有各自的遥控器,这样在使用上就得一个一个的去启动。
外观模式里面让所有遥控统一继承一个接口,对外暴露这一个接口让第三方去一键启动所有设备。因为统一接口的遥控与具体遥控的实现分离,使得第三方和具体设备遥控实现解耦
模板模式
封装了一个算法步骤,并允许子类为一个或多个步骤提供实现
模板模式可以使子类在不改变算法的结构上,重新定义算法中的某些步骤
模板模式中一般有三种方法:抽象方法(需由子类来实现的)(具体的方法)由超类来实现子类不管
(可选的方法hook钩子)子类根据需求可选是否覆盖该方法
好莱坞原则:别调用我们,我们会调用你程序之间无需知道调用的细节,使得解耦(通过经纪人把明星和各种活动解耦,就如模板模式中的子类和超类)
与策略模式相比:模板模式封装的是步骤,把各种方法的顺序或流程关系封装起来策略模式偏重于把某一种方法或功能体系化变成一个方法族模板模式通过继承方式,而策略模式是用接口的方式。将两者结合使用时,用接口替换模板模式里面的抽象方法,定义时用接口对象代替,运行时可用设置的方式带入具体接口的实现
举个栗子:。冲泡饮料首先盛水把水烧开,然后冲泡奶茶,把奶茶倒到杯子,加佐料冲泡茶同样煮水和倒到杯子的步骤是相同的,只是把奶茶换成茶,佐料变换一下。模板模式就是把冲泡的步骤封装,将冲泡的饮料和加佐料的方法抽象,让子类去实现具体是冲泡什么或者加什么佐料,也可以将加佐料作为可选方法根据需求是否覆盖
迭代器模式
提供一种方法顺序访问一个聚合对象中的各个对象,使访问的对象和聚合对象解耦
原则:对修改关闭,对扩展开放开发对接口的开发而不对具体实现的开发。
举个栗子:两家连锁店合并。他们的产品目录完全不同需要合并,但他们的产品目录一家是用数组,另一家是用ArrayList做的,若重新开发会非常麻烦。这里使用迭代器模式,让这两家连锁店实现迭代器接口,在原来的数据结构中增加一个获取迭代器的方法,同时覆盖接口的hasNext和next方法,在迭代器中返回对应的数据结构。这样新的连锁店打印产品目录时只需要获取两家连锁店的迭代器对象,将对象放进数组循环遍历即可
单一责任原则;一个类应该只有一个引起变化的原因
组合模式
将对象聚合成树形结构升来表现 “整体/部分” 的层次关系,组合模式能让客户以一致的方式来处理个别对象及对象组合,也就是我们可以忽视对象组合和个体对象之间的差别
举个栗子:还是两家连锁店合并,对面迭代器模式已经可以打印两家合并后的菜单了,但在有新的需求需要添加餐后点心子菜单时,因为子菜单的数据结构和原来菜单可能不一样就不能把子菜单也放到菜单里面。这时用组合模式让这些菜单都继承一个抽象的菜单组件类,因为不管是原来的菜单还是子菜单都是这个抽象的子类,所以把原来菜单和子菜单类用这个抽象替换,这样原来的菜单和子菜单都统一用这个抽象进行处理,就忽略了这两者的差异来实现需求
状态模式
能根据内部状态的变化,改变对象的行为,看起来像改变了了类
举个栗子:自动贩卖玩具机卖玩具,玩具机贩卖有投硬币、点击对应玩具的购买按钮、玩具掉落、退回硬币这么几个功能。状态有待机、选择玩具、玩具掉落、售空这四种状态,根据不同的状态的状态对操作有不同的提示。传统java面向流程编程会写一个自动贩卖机类,把所有操作都放到一个类里面,这样这个类会很冗肿,维护扩张也很麻烦。使用状态模式让这个四种状态和分别独立成一个类都实现一个状态接口。每种状态分别维护对应四个功能的操作和提示代码。原来的自动贩卖机也实现状态接口并且有一个state接口对象和set方法及四个状态对象,并在每种状态构造中传入自动贩卖机对象,通过这个对象的set方法将状态对象赋给接口state从而改变贩卖机的状态。这使得贩卖机每进行一步操作,内部改变一次状态对象,使用该对象内部定义的方法进行反馈
代理模式
为一个对象提供一个替身,以控制对这个对象的访问
被代理的对象可以是远程对象,创建开销大的对象或需要安全控制的对象
代理模式有很多变体,都是为了控制和管理对象的访问
远程代理:远程对象的本地代表,通过它可以让远程对象当成本地对象来调用。远程代理通过网络和真正的远程对象沟通信息
RMI远程方法调用是计算机之间通过网络实现对象调用的一种通讯机制
步骤:
- 制作远程接口:接口文件
- 远程接口的实现:Service文件
- RMI服务端注册,开启服务
- RMI代理端通过RMI查询到服务端,建立联系,通过接口调用远程方法
常见的几种代理模式:
- 虚拟代理:为创建开销大的对象提供代理服务,真正的对象创建前和创建中时,由虚拟代理来扮演替身
举个栗子:打开网站,在网站未完成加载时由一个转动的小图标或者进度条代表网站加载的过程
- 动态代理:运行时动态的创建代理对象,并将方法调用转发到指定类
几种变体:
- 防火墙代理(公司访问网络在出口的位置放个防火墙,之后用内网地址通过这个代理去访问)
- 缓存代理(如用redis做缓存系统会先访问缓存里面的数据,没有再去找数据库的)
- 智能引用代理(引用或访问某个对象的时候,这个代理提供或增加一些辅助功能)
- 同步代理(多线程之间同步)
- 写入时复制代理(复制东西时实际时不操作的,只有在真正写入的时候复制的工作才开始)
代理模式和装饰者模式的差别
装饰者模式是装饰以后会添加新的功能
代理模式的目的只是对目标对象的控制和管理
复合模式
在一个解决方案中结合两个或多个模式,能解决一般性或一系列问题
复杂鸭子项目
- 多种鸭子,不同鸭子叫声、飞行、游泳方式不同(策略模式)
- 需要加入几只普通的鹅,用鸭子模拟鹅来代替(适配器模式)
- 要统计鸭子的叫声的次数,添加功能(装饰者模式)
- 统一生产鸭子(工厂模式)
- 要管理遍历一群鸭子(不同的对象-组合模式、相同的对象-迭代器模式)
- 追踪鸭子的行为,如鸭子叫声提醒(观察者模式)
经典MVC模式
MVC:Modal、VIew、Controller
Modal:是程序主体,代表业务数据和业务逻辑
View:用户交互界面,显示数据,接受输入,但不参与实际业务逻辑
Controller:接受用户输入,并解析反馈给Modal
Modal与View和Controller是观察者模式
View以组合模式管理控件
View与Controller是策略模式关系,Controller提供策略
桥接模式
将实现和抽象放在两个不同的类层次中,是两个层次可以独立改变
桥接的目的是分离抽象与实现,使得抽象和实现可以独立变化
系统有多维度分类时,而每种分类又有可能变化,考虑使用桥接模式
举个栗子:生产遥控器的厂家接了LG和三星电视遥控的单子,这两个电视遥控器都继承一个遥控器接口有自己不同的实现,生产遥控器的厂家自己定义了一个遥控器接口,将开关放到同一个方法里面,接口还有切换频道和调节音量的功能方法,以后可能会有更多不同的功能。传统做法厂家需要每个电视遥控器都写一个类继承该品牌的遥控器实现类和实现厂家自己的接口,这种做法当需求增加有更多品牌的电视遥控器单子时,要写更多的实现类。使用桥接模式将厂家接口抽象为抽象类,在构造方法中传入各品牌都实现的接口对象,在抽象里通过这个接口对象去实现和扩展不同的功能,这样通过传入接口对象这个桥梁使得整个需求可以很好的扩展,各品牌遥控器的实现与遥控器厂家功能增加互不干扰,独立改变。
桥接模式与策略模式的差异
- 桥接的目的是让接口实现和抽象层可以分别演化,从而提高移植性
- 策略的目的是将复杂的算法封装起来,从而便于替换不同的算法
- 桥接模式往往是为了利用已有的方法和类
- 策略模式是为了扩展和修改现有的功能和方法,并提供动态配置
- 桥接模式强调接口对象仅提供基本操作
- 策略模式强调接口对象提供的是一种算法
生产器模式(建造者模式)
封装了一个复杂对象的构造过程,并允许按步骤构造
优点
- 将复杂对象的创建过程封装起来
- 允许对象通过几个步骤来创建并且可以改变过程(工厂模式只有一个步骤)
- 只需指定具体的生成器就能生成特定的对象,隐藏类的内部结构
- 对象的实现可以被替换
生成器模式和工厂模式的区别
- 生成器一般用来创建复杂的对象
- 生成器模式强调一步步创建对象,可以改变步骤和生成不同的对象
- 一般来说生成器模式时不直接返回对象
责任链模式
如果多个对象都有机会处理请求,责任链可使请求的发送者和接受者解耦,请求沿着责任链传递,直到有一个对象处理了为止
优点
- 将请求的发送者和接受者解耦,使多个对象都有机会处理这个请求
- 可以简化对象,因为它无须知道链结构
- 可以动态增加或删除处理请求的链结构
缺点
- 请求从链的开头进行遍历,对性能有一定的损耗
- 并不保证请求一定被处理
蝇量模式(享元模式)
通过共享的方式高效地支持大量细粒度的对象
优点
- 减少运行的对象实例个数,节省创建开销和内存
- 将许多“虚拟”对象的状态集中管理
缺点
- 系统设计更加复杂
- 需要专门维护对象的外部状态
解释器模式
定义一个语法,定义一个解释器,该解释器处理该语法的句子
将某些复杂问题,表达为某种语法规则,然后构建解释器来解释这类句子
优点
- 容易修改,修改语法规则只要修改相应非终结符即可
- 扩展方便,扩展语法只要增加非终结符类即可
缺点
- 对于复杂的语法结构会产生复杂的类层次结构,不利于管理和维护
- 解释器使用递归方式,效率会受影响
中介者模式
用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式的相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
优点
- 通过将对象彼此解耦,可以增加对象的复用性
- 通过将控制逻辑集中,可以简化系统维护
- 可以让对象之间所传递的消息变得简单而且大幅j减少
- 提供系统的灵活性,使得系统 易于扩展和维护
缺点
- 中介者承担了较多得责任,一旦中介者出现了问题,整个系统就会受影响
- 如果设计不当,中介者本身变得过于复杂
适用场合
- 一组对象之间的通信方式比较复杂,导致相互依赖,结构混乱
- 一个对象引用很多其他对象并直接与这些对象通信,导致难以复用该对象
备忘录模式
在不破坏封装的前提下,存储关键对象的重要状态,从而可以在将来把对象还原到存储的那个状态
优点
- 状态存储在外面,不和关键对象混在一起,这可以帮助维护内聚
- 提供了容易实现的恢复能力
- 保持了关键对象的数据封装
缺点
- 资源消耗上面备忘录对象会很昂贵
- 存储和恢复状态的过程会比较耗时
使用场合
- 必须保存一个对象在某一时刻的(整体或部分)状态,在对象以外的地方,以后需要时恢复到先前的状态时
原型模式
通过复制现有的实例来创建新的实例,无需知道相应类的信息
优点
- 使用原型模式创建对象比直接new一个对象更有效
- 隐藏了创建新实例的复杂性
- 重复创建相似对象时可以考虑使用原型模式
缺点
- 每个类必须配备一个克隆方法
- 深层复制比较复杂
注意事项
使用原型模式复制一个对象不会调用类的构造方法,所以单例模式和原型模式是冲突的
Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝
适用场合
- 复制对象的结构和数据
- 希望对目标对象的修改不会影响既有的原型对象
- 创建对象成本较大的情况下
访问者模式
对于一组对象,在不改变 数据结构的前提下,增加作用于这些结构元素的新功能
适用于数据结构相对稳定,它把数据结构和作用于其上的操作解耦,使得操作集合可以相对自由的演化
优点
- 符合单一职责原则
- 扩张性良好
- 有利于系统的维护和管理
缺点
- 增加新的元素类变得困难
- 破坏封装性
适用场合
- 系统有比较稳定的数据结构,又有经常变化的功能需求
总结
创造型模式:对象实例化的模式,创造型模式解耦了对象实例化的过程
简单工厂:一个工厂类根据传入的参量决定创建出哪种产品类的实例
工厂方法:定义一个创建对象的接口,让子类决定实例化哪个类
抽象工厂:创建相关或依赖对象的家族,而无需明确指定具体类
单例模式:某个类只能有一个实例,提供一个全局访问点
生成器模式:封装一个复杂对象的构造过程,并可以按步骤构造
原型模式:通过复制现有实例来创建新的实例
结构型模式:把类和对象结合在一起形成更大的结构
适配器模式:将一个类的方法接口转换成客户希望的另外一个接口
组合模式:将对象组合成树形结构以表示“整体-部分”的层次结构
装饰器模式:动态地给对象增加新的功能
代理模式:为其他对象提供一个代理以控制这个对象地访问
蝇量模式(享元模式):通过共享地技术有效地支持大量细粒度地对象
外观模式:提供统一地方法访问子系统地一群接口
桥接模式:将抽象部分和它地实现相分离,使他们可以独立地变化
行为型模式:类和对象如何交互,及划分职责和算法
模板模式:定义了一个算法结构,而将一些结构步骤延迟到子类中实现
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器
策略模式:定义一系列的算法,把它们封装起来,并且使它们可以相互转换
状态模式:允许一个对象在其内部状态改变时改变它的行为
观察者模式:对象之间一对多的依赖关系
备忘录模式:在不破坏封装的前提下,保存对象的内部状态
中介者模式:用一个中介对象来封装一系列对象交互
命令模式:将命令请求封装为一个对象,使得可用不同的请求来进行参数化
访问者模式:在不改变数据结构的前提下,增加作用于一组对象元素的新功能
责任链模式:请求发送者和接受者之间解耦,使的多个对象都有机会处理这个请求
迭代器模式:一种遍历访问聚合对象的种各个元素的方式,不暴露该对象的内部结构
如何使用
保持简单
- 尽可能用最简单的方式解决问题
- 简单而弹性的设计,一般用模式是最好的方法
- 不要为了模式而使用模式
设计模式并非万能
- 模式是通用问题的经验总结
- 使用模式时要考虑对其他不部分的影响
- 不需要预留任何弹性时,删除掉模式
- 平衡于妥协
何时需要模式
- 找出设计中会变化的部分,通常时使用模式的时候
- 重构
重构的时间就时设计模式的时间
- 重构就是改变代码来改进组织方式的过程
- 利用模式来重构