设计模式
2022-03-04 17:15:33 2 举报
AI智能生成
设计原则、设计模式脑图,应用场景案例,最全笔记,通俗易懂
作者其他创作
大纲/内容
每个类的角色职责应当是单一的
单一职责
对扩展开放,对修改关闭
开闭原则
父类与子类的可替换性(引用父类的地方,一定也可以替换为其子类)
里氏替换
对高层接口的独立、分化;切勿将接口定义成全能型
接口隔离
高层模块不依赖于底层模块,只依赖上层抽象
依赖倒置
模块间应保持陌生,减少关联,达到松耦合的目的
迪米特法则
设计原则
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
意图
一个全局使用的类频繁地创建与销毁。
主要解决
当您想控制实例的数目,节省系统资源的时候
何时使用
判断系统是否已经有这个实例,如果有则返回,如果没有则创建
如何解决
线程安全
饿汉模式
非线程安全
懒汉模式
双重检验锁
如何实现
1、生成唯一序列号、流程单据号、人事编号
2、WEB中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来
3、创建一个对象需要消耗的资源过多,比如I/O与数据库的连接等
应用场景
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
2、避免对资源的多重占用(比如写文件操作)。
优点
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
缺点
优缺点
getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
注意事项
单例模式
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
主要解决:在运行期建立和删除原型
1、当一个系统应该独立于它的产品创建,构成和表示时。
2、当要实例化的类是在运行时刻指定时,例如:通过动态装载。
3、为了避免创建一个与产品类层次平行的工厂类层次时。
4、当一个类的实例只能有几个不同状态组合中的一种时,建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一下。
利用已有的一个原型对象,快速地生成和原型对象一样的实例。
细胞分裂、资源优化场景、游戏中的角色、武器属性等。
优点:1、性能提高;2、逃避构造函数的约束。
缺点:对已有的类,配备克隆方法需要对类的功能进行通盘考虑;必须实现Cloneable接口。
与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
原型模式
定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
主要解决接口选择的问题
我们明确的计划不同条件下创建不同的实例时。
让其子类实现工厂接口,返回的也是一个抽象的产品
创建过程在其子类执行
1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
3、设计一个连接服务器的框架,需要三个协议,\"POP3\"、\"IMAP\"、\"HTTP\",可以把这三个作为产品类,共同实现一个接口。
4、播放视频不需要考虑格式问题,自动解码
使用场景
1、一个调用者想创建一个对象,只要知道其名称就可以了。
2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
3、屏蔽产品的具体实现,调用者只关心产品的接口。
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
工厂模式
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。
主要解决接口选择问题
系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
在一个产品族里面,定义多个产品
在一个工厂里聚合多个同类产品
1、QQ 换皮肤,一整套一起换。 2、生成不同操作系统的程序。
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
产品族难扩展,产品等级易扩展。
抽象工厂模式
将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
“一个复杂对象”的创建工作
一些基本部件不会变,而其组合经常变化的时候。
将变与不变分离开。
建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。
1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的\"套餐\"。
2、JAVA 中的 StringBuilder。
1、建造者独立,易扩展。
2、便于控制细节风险
1、产品必须有共同点,范围有限制。
2、会有很多的建造类
与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
建造者模式
创建篇
单例模式类结构
原型模式类结构
工厂模式类结构
抽象工厂类结构
建造者模式类结构
为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
降低访问复杂系统内部子系统的复杂度,简化客户端之间的接口
1、客户端不需要知道系统内部的复杂联系,整个系统只需要提供一个“接待员”即可。
2、定义系统的入口
客户端不与系统耦合,外观类与系统耦合。
在客户端和复杂系统之间再加上一层,这一层将调用顺序、依赖关系等处理好。
1、商场的接待员;2、java的三层开发模式
3、机场内的导航功能
4、电子签功能,用户只需点击页面的发送电子签按钮,系统将匹配模板生成电子签、发送邮件链接及验证码给签署方
1、减少系统互相依赖。
2、提高灵活性、安全性
不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
在层次化结构中,可以使用外观模式定义系统中每一层的入口
外观模式
将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
在树形结构的问题中,模糊简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
1、具有树形结构的对象
树枝和叶子实现统一接口,树枝内部组合该接口。
树枝内部组合该接口,并且具有内部属性list,里面放component
集团的组织结构、树形菜单、文件、文件夹的管理
高层模块调用简单、节点自由增加
组合模式,叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则
定义时为具体类
组合模式
动态的给一个对象添加额外的职责。对增加功能来说,装饰器模式比生成子类更为灵活。
通过继承方式扩展一个类,随着扩展功能的增多,子类会很膨胀。解决继承方式的这一弊端
在不想增加很多子类的情况下扩展类
将具体功能职责划分,同时继承装饰者模式
1、Component类充当抽象角色,不应该具体实现。
2、修饰类引用和继承Component类,具体扩展类重写父类方法。
1、扩展一个类额功能。
2、动态增加功能,动态撤销
装饰类的和被装饰类可以独立发展,不会互相耦合,装饰者模式使继承的一个代替模式,装饰模式可以动态扩展一个实现类的功能
多层装饰比较复杂
可代替继承
装饰器模式
将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类一起工作。
主要解决在软件系统中,常常要将一些\"现存的对象\"放到新的环境中,而新环境要求的接口是现对象不能满足的。
1、系统需要使用现有的类,而此类的接口不符合系统的需要
2、通过接口转换,将一个类插入另一个类系中。
继承或依赖(推荐)
适配器继承或依赖已有的对象,实现想要的目标接口。
1、JAVA中的JDBC
2、有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
1、可以让任何两个没有关联的类一起运行。
2、提高了类的复用、透明度以及灵活性
1、过多地使用适配器,会让系统非常凌乱,不易进行整体把握。
2、由于JAVA至多继承一个类,所以至多只能适配一个适配者类,而且目标必须是抽象类
适配器不是在详细设计时添加的,而是解决正在服役的项目问题。
适配器模式
使用共享技术有效地支持大量细粒度的对象。
在有大量对象时,有可能会造成内存溢出,我们把其中公共的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
1、系统中有大量的对象,并且消耗大量的内存。
2、这些对象的状态大部分可以外部化。
3、系统不依赖于这些对象身份,这些对象时不可分辨的
用唯一标识码判断,如果内存中有,则返回这个唯一标识码所标识 的对象
用HashMap存储这些对象
1、JAVA的String,如果有则返回,如果没有则创建一个字符串缓存池里面。
2、数据库连接池
大大减少对象的创建,降低系统的内存,使效率提高
提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。
2、这些类必须有一个工厂对象加以控制
享元模式
为其他对象提供一种代理以控制对这个对象的访问。
在直接访问对象时带来的问题,如性能问题、安全问题。
想在访问一个类时做一些控制
增加中间层
实现与被代理类组合
1、Windows里面的快捷键
2、火车票代售点
3、Spring aop
职责清晰、高扩展性、智能化
1、可能会造成请求处理速度变慢。
2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变代理类的接口。
2、和装饰器模式的区别:装饰器是为了增强功能,而代理模式是为了加以控制。
代理模式
将抽象部分与实现部分分离,使它们都可以独立的变化
在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
实现系统可能有多个角度分类每一种角度都可能变化
把这种多角度分类分离出来,让它们独立变化,减少它们之间的耦合
抽象类依赖实现类
路由器的桥接模式
2、跨平台视频播放器;平台和视频格式两个独立变化的纬度
1、抽象与实现的分离;2、优秀的扩展能力;3、实现细节对客户透明。
增大了系统的理解与设计难度
对于两个独立变化的维度,使用桥接模式再合适不过了
桥接模式
结构篇
外观模式类结构
组合模式类结构
装饰器模式类结构
适配器模式类结构
享元模式类结构
代理模式类结构
桥接模式类结构
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
一些方法通用,却在每一个子类都重写了这一方法。
有一些通用的方法
将这些通用的算法抽象出来。
在抽象类实现,其他步骤在子类实现。
电子签
1、封装不变的部分,扩展可变的部分。
2、提取公共代码,以便于维护
每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
为防止恶意操作,一般模板方法都加上final关键词
模板模式
提供一种方法顺序访问一个聚合对象中各个元素,而又无须暴露该对象的内部表示
不同的方式来遍历整个聚合对象
需要遍历一个聚合对象
把在元素之间游走的责任交给迭代器,而不是聚合对象
定义接口:hashNext,next.
JAVA中的iterator
简化了对聚合对象的遍历
由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性
迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器来负责,这样既可以做到不暴露集合的内部结构,又可以让外部代码透明地访问集合内部的数据。
迭代器
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,知道有对象处理它为止。
职责链上的处理者负责处理请求,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
在处理消息的时候要过滤很多道
拦截的类都实现统一接口
Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去
拦截器
工作流审批办理
1、降低请求的发送者和接收者的耦合度
2、简化了对象,使得对象不需要知道链的结构
3、增强给对象指派职责的灵活性
4、增加新的请求处理类很方便
1、不能保证请求一定被接收
2、对系统性能有一定影响,也有可能造成循环调用
在 JAVA WEB 中遇到很多应用
责任链模式
定义一系列的算法,把他们一个个封装起来,并且使他们可互相替换。
在有多种算法相似的情况下,使用if...else所带来的复杂和难以维护
一个系统有许多类,而区分他们的只是他们直接的行为
将这些算法封装成一个一个的类,任意的替换
实现同一个接口
1、旅游的出行方式,每一种都是一个策略
2、12306的某一列车的选票
1、算法可以自由切换
2、避免使用多重条件判断
3、扩展性良好
1、策略类会增多
2、所有策略类都需要对外暴露
如果一个系统的策略类多于4个,就需要考虑使用混合模式,解决策略类膨胀的问题
策略模式
允许对象在内部状态发生改变是改变他的行为,对象看起来好像修改了它的类
对象的行为依赖于他的状态(属性),并且可以根据他的状态改变而改变它的相关行为。
代码中包含大量与对象状态有关的条件语句
将各种具体的状态类抽象出来
1、行为随状态改变而改变的场景。
2、条件、分支语句的代替者
1、封装了转换规则。
2、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
3、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
1、状态模式的使用必然会增加系统类和对象的个数
2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱
在行为受状态约束的时候使用状态模式,而且状态不超过5个
状态模式
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
实现恢复、撤销操作
需要做恢复、撤销操作等功能时
通过一个备忘录类专门存储对象状态
客户不与备忘录类耦合,与备忘录管理类耦合
1、游戏存档
2、数据库的事务管理
3、Windows的系统还原
1、给用户提供一个可恢复状态的机制,能够回到某个历史的状态
2、实现信息的封装,使得用户不需要关心状态的保存细节
保存数据会占用较大资源以及内存
1、为了符合迪米特原则,还要增加一个管理备忘录的类。
2、为了节约内存,可使用原型模式+备忘录模式
备忘录模式
用一个中介对象来封装一系列的对象交互,中介者使各个对象不需要显示地相互引用,从而使其松散耦合,而且可以独立地改变他们之间的交互
对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。
多个类相互耦合,形成了网状结构
将上述网状结构分离为星型结构
对象 Colleague 之间的通信封装到一个类中单独处理。
1、机场调度系统
2、MVC框架,C是M和V的中介者
1、降低了类的复杂度,将一对多转化为一对一
2、各个类之间的解耦
3、符合迪米特原则
中介者会庞大,变得复杂难以维护
不应当在职责混乱的时候使用
中介者模式
将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化
需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适
在某些场合,需要对行为进行记录、撤销/重做、事务等处理时
通过调用者调用接收者执行命令,顺序:调用者--->命令--->接收者
定义三个角色:1、received真正的命令执行对象;2、Command 3、invoker使用命令对象的入口
认为是命令的地方都可以使用命令模式,比如GUI中每一个按钮都是一条命令
1、降低系统的耦合度。
2、新的命令可以很容易添加到系统中去
使用命令模式可能会导致某些系统有过多的具体命令类
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,也可以考虑使用命令模式
命令模式
主要将数据结构与数据操作分离
稳定的数据结构和易变的操作耦合问题
需要对一个对象结构中的对象进行很多不同且不相关的操作,而需要避免让这些操作污染这些对象的类,使用访问者模式将这些封装到类中
在被访问的类里面加一个对外提供接待访问者的接口
在数据基础类里面有一个方法接受访问者,将自身引用传入访问者
记者对当事人的访问,这就是访问者模式
1、符合单一职责原则
2、优秀的灵活性、扩展性
1、具体元素对访问者公布细节,违反了迪米特原则。
2、具体元素变更比较困难
3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象
访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器
访问者模式
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变是,所有依赖于它的对象都得到通知并自动更新。
一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作
需要进行类似广播通知时
使用面向对象技术,可以将这种依赖关系弱化
在抽象类里面有一个ArrayList存放观察者们。
1、线上拍卖,拍卖师观察最高价,然后通知给其他竞价者竞价
2、工作流中的监听器
1、观察者和被观察者都是抽象耦合的
2、建立一套触发机制
1、被观察者对象有过多的直接或间接的观察者,通知观察者将会花费很多时间
2、如果观察者与被观察者存在循环依赖,则可能会导致系统崩溃
3、观察者模式只是通知到被观察者的状态的改变,不能给观察者通知到被观察者的变化过程。
1、JAVA中已经有了对观察者模式的支持类
2、避免循环引用
3、如果顺序执行,某一观察者错误会导致系统的卡壳,一般采用异步方式
观察者模式
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子
对于一些固定文法构建一个解释句子的解释器
特定类型的问题发生的频率足够高时,就值得构建一个解释器来解析该问题
构建语法树,定义终极表达式与非终极表达式
构建环境类、包含解释器之外的一些全局信息,一般就是HashMap
1、编译器
2、SQL解析
3、运算表达式计算
1、可扩展性能好、灵活
2、易于实现简单文法
1、可利用场景比较少
2、复杂的文法比较难以维护
3、解释器模式会引起类膨胀
4、解释器模式采用递归调用方法
可利用场景比较少,JAVA中如果碰到可以用expression4J代替
解释器模式
行为篇
模板模式类结构
迭代器模式类结构
责任链模式类结构
策略模式类结构
状态模式类结构
备忘录模式类结构
中介者模式类结构
命令模式类结构
访问者模式类结构
观察者模式类结构
解释器模式类结构
设计模式
0 条评论
回复 删除
下一页