总结一下23中设计模式各自的优缺点,及实际情况中如何使用。
详细解析如下,使用实例代码地址:https://github.com/chanyi/designPattern
设计模式分为3大类:
创建型模式(5个):
关注点是如何创建一个对象。将创建的使用和分离。使用者只需要使用,不需要关注创建的过程
单例模式:一个类只能提供一个实例,但是可以扩展到优先多例模式;
原型模式:将一个类作为原型,通过复制,制造出多个和原型类似的新实例
工厂模式:定义一个用于创建产品的接口,由子类决定生产什么产品,类创建模式
抽象工厂模式:提供一个创建产品族的接口,每个子类可以创建一系列相关的产品
建造者模式:将一个对象分解成多个部分,然后分别创建他们
结构型模式(7个):
代理模式:控制客户端对对象的访问
适配器模式:将类的接口换成对象需要的
桥接模式:用组合方式代替继承关系
装饰模式:在不改变现有结构的情况下,动态的增加功能
外观模式:facade,为多个子系统提供一个统一的接口
享元模式:共享对象
组合模式:一个对象下,有个列表属性,存放子对象,构成树结构
行为型模式(11个):
模板方法模式:
策略模式:适合用于多个实现方法的统一接口调用的情况。比如排序接口提供一个统一的接口用来被调用,但是排序时实际比较大小的策略可以有多个,比如按照高度比较,按照宽度比较。所以不同的策略可以通过参数入参到排序的方法内。排序方式只是实现排序的代码即可。
命令模式:
责任链模式:
状态模式:
观察者模式:
中介者模式:
迭代器模式:
访问者模式:
备忘录模式:
解释器模式:
创建型模式(5个):
1、单例模式:
特点:
一个类只有一个实例对象
该单例对象必须由单例类
单例类对外提供一个访问该单例类的一个全局访问点
实现:一般可以通过new获取多个实例。所以单例的情况,构造函数要设置为私有的
类内定一个私有静态实例,再定义一个静态公有函数,返回这个私有静态实例
两种方式:懒汉式和饿汉式
懒汉式:类加载的时候没有生成单例,在调用获取单例的方法(getInstance)时才创建单例
饿汉式:类一加载,就同时创建一个单例,在getInstence时单例已经存在
应用场景:
1)、只要求生成一个对象的时候,比如一个人的身份证,一个班的班长
2)、当对象需要被共享的时候,比如数据库的连接池。web中的配置对象,
3)、当某个类频繁被实例话,对象又频繁被销毁的时候。多线程的线程池和网络连接池
线程池在程序中一般只需要一个即可,线程的创建都在线程池内操作。线程池是不变的。网络池的原理相同
扩展:单例模式可以扩展为有限多例模式
将对象创建好后放在一个list里面。这样获取到的实例就是一个list,实现有限个实例。
有限多例的应用场景:比如玩麻将开始需要执两个骰子,此时骰子作为一个类的话,就需要创建两个实例。
2、原型模式:用一个已经创建的实例当作原型,复制出多个实例来。
java提供了对象的clone方法,实现原型模式很简单
分类:
深克隆:自己完成每个属性的clone
浅克隆:只需要调用clone即可
优点:
1、简化流程
2、动态获取对象
3、某些情况下比直接new快一些
3、工厂方法模式(FactoryMethod)
抽象工厂接口中只生产一个产品
接口:定义一个工厂接口,里面包含创建对象方法
工厂子类:实现工厂接口,实现具体的创建对象方法
分类:
简单工厂模式:创建的产品不多,只要一个工厂类就可以完成(无抽象工厂类)
抽象工厂模式:在不修改源代码的情况下,引入新的产品(有抽象工厂类)
使用于:
用户只知道产品工厂名,不知道产品类型
客户不关心创建的过程,只关心产品的品牌
易于扩展,如果新增一个具体产品,则新建一个具体工厂,继承抽象工厂。在新建的工厂中写代码,不影响原来的代码
实例:
使用工厂方法模式写一个计算器(面试)
思路:
抽象工厂接口:计算接口,方式是返回结果
具体工厂类:加法工厂类,乘法工厂类,除法工厂类,减法工厂类。。。
调用类:直接使用具体的工厂类创建一个对象,调用返回结果方法,即可;
4、抽象工厂模式(abstractFactory)
抽象工厂接口中可以生成两个及多个产品
工厂方法模式只生产一个等级的产品,抽象工厂可以生产多个等级的产品
使用条件:
1、系统中有多个产品族,每个具体的工厂产生一族产品
2、系统一次只可能生产某一族产品,同族的产品一起使用
优点:
对同族多产品共同管理
增加一个新的产品族时不需要修改源代码,满足开闭原则
缺点:
如果产品族中增加一个新产品,整个工厂都要修改
5、建造者模式:
使用多个简单的对象,一步一步构建成一个复杂的对象
将一个复杂的对象拆分成多个简单的对象
使用的实例:
套餐的组成(拆分成多个简单的组成),StringBuilder,
优点:
容易扩展,便于控制细节风险
缺点:
如果内部复杂会有多个建造类
产品必须要有共同点
注意:
和抽象工厂模式相比,更加注重各个组成部分的顺序
结构型模式:
1、代理模式(proxy):
为对象提供一种代理,以控制对象的访问,客户端通过代理访问对象,可以控制客户端对对象属性的访问和操作
实例:
下载很大文件,需要时间比较长
单位内部数据库,限制客户端的访问
优点:
起中介作用,保护目标对象
对目标对象的功能进行扩展
目标对象与客户端分离,可以降低系统的耦合度
缺点:
有了中介,请求速度变慢
增加系统的复杂度
结构:
真实类
抽象类(接口/抽象类)
代理类
2、适配器模式(adapter):将一个类的接口换成客户端所需要的接口,比如将交流电转为直流电供笔记本电脑使用
优点;
将已有的类放到新环境下,使之适应新的需求
可以让两个没有关联的类一起运行
提高了类的复用性
提高类的透明度
灵活性好
缺点:
过多适配器会是系统变得复杂
java是单继承类,最多只能适配一个类
3、桥接模式(bridge):抽象与实现分离,二者可以独立变化,用组合关系代替继承关系,降低了耦合度
优点:
降低耦合度,扩展性强
细节对客户透明
缺点:
设计难度大,对系统要有深入的理解
结构:
抽象类
扩展抽象类
实现类
具体实现类
适用:
一个类有两个独立变化的维度
4、装饰模式(decorator):在不改变现有结构的情况下,动态的给对象增加一些职责
优点:
比继承更灵活
可以设计出不同的装饰类,创造出多个不同行为的组合
缺点:
增加许多子类,增加复杂度
结构:
抽象类
具体类
抽象装饰类:增加一个抽象类的属性,
具体装饰类:具体装饰类继承抽象装饰类,重写抽象装饰类继承的抽象类的抽象方法,然后改写实现动态增加
5、外观模式(facade):为多个复杂的子系统提供一个统一而接口,是其他客户只需要访问这一个接口即可,不需要按个访问每一个复杂的子系统
优点:
降低子系统与客户端的耦合度
减少客户端处理的子系统对象的数量,子系统用起来更方便
编译一个子系统不会影响其他的子系统和客户端
缺点:
不能很好的限制客户使用子系统的类
增加新系统,要修改统一接口,不满足开闭原则
适用场景:
子系统很多的系统
客户端和多个子系统之间有联系的时候
6、享元模式(flyweight):利用共享技术,重用细粒度的对象。减少系统开销
优点:
减少系统开销
缺点:
对象共享,增加程序的复杂度
读取享元模式的外部状态,是运行时间稍微变长
两种状态:
内部状态:不会随着环境改变的内部共享部分
外部状态:随环境改变的外部不可共享的部分
7、组合模式(composite):
在一个对象中包含其他对象,多个对象形成一个树形结构
主要适用于树形结构的对象的情况
优点:
高层模块调用简单,增加子节点自由
缺点:
实现是 叶子和树枝都是实现类,不是接口,违反了依赖倒置原则
行为型模式(11个)
1、模板方法:定义一个操作中的算法骨架,子类实现算法的具体步骤,主要就是封装不可变性,扩展可变性
优点:
父类中封装共用代码,便于代码的复用
部分方法有子类实现,符合开闭原则,
缺点:
每一个具体算法的实现都有有一个对应的子类,类比较多,不好维护
父类的抽象方法由子类实现,子类执行的结果会影响父类的结果,导致一种反向的控制结构,提高代码的阅读难度
结构:
抽象类:给出整个框架,由一个模板方法(定义算法骨架)和几个基本方法构成(算法的具体步骤)
具体子类:实现抽象类的抽象方法
2、策略模式(strategy)定义一系列算法,把每个算法封装起来,算法之间可以相互替换。而且算法的变化不会影响使用。
优点:
替代多重条件判断的方法,这样易于维护
一系列的算法族中的公共部分可以抽取到父类中,避免重复代码
支持开闭原则
算法族的实现放在策略类中,算法族的使用放在使用类中。二者分离
缺点:
策略模式需要很多策略类
对算法的选择需要慎重的考虑
构成:
抽象策略类
具体策略类
使用类(环境类)
应用实际场景:
诸葛亮锦囊,一个锦囊就是一个策略。
多种出行方法,一种出行方式就是一个策略
3、命令模式(command):请求以命令的形式包裹在对象中,并传给调用对象,
调用对象寻找可以处理该命令的合适的对象,然后把命令传给相应的对象,对该对象执行命令
优点:
将行为请求者和行为实现者解耦
新的命令很容易加入到系统中
缺点:
系统会有过多的具体命令类
结构:
调用者:有一个命令对象,在某个时间点调用命令对象的execute方法
命令对象:持有一个接受者和一个execute方法
接受者:动作的执行者
4、责任链模式(chain of Responsibility)
请求发送到责任链上,不需要关注处理的系统和请求的传递过程
优点:
降低对象之间的
增强系统的可扩展性
增加给对象指派责任的灵活性
责任的分担,每个类处理自己的责任,符合类的职责单一原则
缺点:
不能保证每个请求一定被处理,一个请求如果没有明确的接受者,这个请求可能传递到责任链的最后还没被处理
责任链长的情况,请求的处理可能会涉及多个处理对象,影响系统性能
责任链建立的合理性靠客户端来保证。增加客户端你的负责性
结构:
抽象处理者(handler)
具体处理者(concreteHandler)
客户端(client)
使用实例:
请假找不同的领导,不同的领导又不同的责任,批假的权限也都不相同
5、状态模式(state):
把复杂的判断逻辑提取到不同的状态对象中,允许状态对象在其内部状态发生改变是改变其行为
优点:
将特定状态的相关行为局部化到一个状态中,将不同状态分离开,满足单一职责原则
减少对象的相互依赖
有利于扩展,有新的状态,就直接定义一个新的状态类
缺点:
增加类和对象的个数
容易造成代码混乱
结构:
环境角色(context)
抽象状态(state)
具体状态(concreteState)
6、观察者模式(observer)
多个对象之间存在着一对多的依赖关系,当一个对象的状态发生变化,其他对象的行为也跟着发生变化
也称为订阅发布,模型视图模式
优点:
降低了目标与观察者之间的耦合关系,
目标与观察者之间建立一套触发机制
缺点:
目标与观察者依赖关系没有完全解除
当观察者对象很多的时候,通知发布会花费很多时间,影响性能
结构:
抽象主题(subject)抽象目标类:
具体主题(concrete subject)
抽象观察者(observer)
具体观察者(concrete observer)
7、中介者模式(mediator)
为了降低多个对象和类之间的通信复杂性,使用中介类,处理不同类的通信,支持松耦合,解决网状结构
优点:
减低对象之间的耦合度
将对象间的一对多关联变成一对一关联,提高系统的易维护性
缺点:
对象太多的时候,中介者对象会变的很复杂
结构:
抽象中介类(mediator)
具体中介类(concrete mediator)
抽象同事类(colleague)
具体同事类(concrete colleague)
8、迭代器模式(Iterator)
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示
优点:
简化聚合类,遍历任务给遍历器类
封装性好,为遍历不同的聚合结构提供统一的接口
增加新的聚合类,新的遍历器类都很方便,无需修改原有代码
缺点:
增加类的个数,增加复杂度
结构:
抽象聚合类(Aggregate)
具体聚合类(concreteAggregate)
抽象迭代器(Iterator)
具体迭代器类(concreteIterator)
9、访问者模式(visitor)
将某种数据结构中的各元素的操作分离出来封装成独立的类,某些元素的有了新的操作,就定义新的类,不影响原有的数据结构
为每个数据元素中的元素提供多种访问方式
优点:
扩展性好
复用性好
灵活性好
符合单一职责原则
缺点:
每增加一个新的元素类,就需要增加一系列的访问类,违背开闭原则
破坏封装性,具体的元素对访问者公开
违反了依赖倒置原则,访问者类依赖了具体类,没有依赖抽象类
结构:
抽象访问者(visitor)
具体访问者(visitorConcrete)
抽象元素(element)
具体元素(elementConcrete)
对象结构(Object Structure)
10、备忘录模式(memento)
在不破坏封装性的前提下,捕获一个对象的内部状态,并缓存,当需要的时候把对象回复原来的状态,也是一种快照模式
优点:
提供一种可以回复状态的机制,
实现内部封装,除创建人外,其他人都不能访问
简化发起人类,回复过程直接由管理者管理,符合单一职责原则
缺点:
资源消耗大,要花内存去存储快照数据
结构:
发起人(Originator)
备忘录(memento)
管理者(caretaker)
11、解释器模式(interpreter)
给分析对象定义一个语言,定义语言的文法,然后再用解释器解析,该接口解释一个特定的上下文
优点:
扩展性好,使用类来解释文法,可以继承
容易实现,语法树中的每个表达式节点类都是相似的,实现文法比较容易
缺点:
执行效率低:解释器中有很多的循环和递归
会引起类膨胀,每条规则至少定义一个类
可用场景比较少
结构:
抽象表达式(Expression)
终止符表达式(Terminal Expression)
非终止符表达式(Notterminal Expression)
环境角色(context)
客户端(client)