设计模式
标签(空格分隔): 设计模式优点 应用场景
整理自《设计模式之禅》
单例模式
优点:
- 只有一个实例,减少了内存开支;
- 可以避免对系统资源的多重占用;
- 可以在系统中设置全局的访问点,优化和共享资源访问;
缺点:
- 没有接口,扩展困难;
- 对测试开发不利;
应用场景:
- 要求生成唯一序列号的场景;
- 需要一个共享访问点;
- 创建一个对象需要消耗过多的资源时
- 需要定义大量的静态常量和静态方法时(也可直接声明为static的方式);
工厂方法模式
优点:
- 良好的封装性,代码结构清晰;
- 扩展非常好;
- 屏蔽产品类;
应用场景:
- 是new一个对象的替代品;
- 需要灵活的,可扩展的框架时;
- 使用在测试驱动开发的框架下;
抽象工厂模式
优点:
- 封装性;
- 产品族内部的约束为非公开状态;
缺点:
- 产品族扩展困难;
模板方法模式
优点:
- 封装不变部分,扩展可变部分,把不变的算法封装到父类实现,可变的部分则通过继承来扩展;
- 提取公共部分代码,便于维护;
- 行为由父类控制,子类实现;
缺点:
- 子类对父类产生影响,子类执行的结果影响了父类的结果;
应用场景:
- 多个子类有公有的方法,且逻辑相同时;
- 重要,复杂的算法,可以把核心算法设计为模板方法;
- 重构时,把相同的代码抽取到父类,然后通过钩子函数结束其行为;
建造者模式
优点:
- 封装性,使得客户端不必知道产品内部的组成细节,我们不用关心每一个具体的模型内部是如何实现的。
- 建造者独立,容易扩展
- 便于控制细节风险,由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响;
建造者模式的应用场景:
- 相同的方法,不同的执行顺序,会产生不同的结果时;
- 多个部件或零件,都可以装配到一个对象中,但产生的运行结果又不相同时,如
Android
中的AlertDialog
的构造; - 产品类非常复杂,或产品类的的调用顺序不同产生不同的效果;
代理模式
优点:
- 职责清晰,其实的角色就是实现实际的业务的逻辑,不用关心其他非本职责的事务;
- 高扩展性,具体主题角色随时都会发生变化,但只要它实现了接口,我们的代理类就可以在完全不做任何修改的情况下使用;
原型模式(通过实现Cloneable接口)
优点:
- 性能优良,原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好,特别是要在循环体内产生大量对象时,
- 避免构造函数的约束,直接是在内存中拷贝的,构造函数是不会执行的。
应用场景:
- 类初始化需要消化非常多的资源时
- 性能和安全要求的场景,通过 new产生一个对象需要非常繁琐的数据准备和访问权限时;
- 一个对象多个修改者的场景,一个对象需要提供给多个对象访问,而且各个调用者都可以修改其值时;
注意地方:浅拷贝与深拷贝
Java的Object类提供的clone
方法只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,其他的原始类型如int,char等都会被拷贝,拷贝后的对象与原生对象共享内部元素的地址(浅拷贝),如果拷贝后的对象修改了原生对象的数组,则原生对象也会看到修改。如果需要进行深拷贝,则需要在复写的clone
方法里对私有的类变量(内部数组,引用对象)进行独立的拷贝。并且使用final
关键字修饰的变量不能被拷贝;
中介者模式
优点:
- 减少了类间的依赖,把原有的一对多的依赖变成了一对一的依赖;
缺点:
- 中介者会膨胀得很大,而且逻辑复杂;原本N个对象的依赖关系转换为中介者与对象的依赖关系;
命令模式
优点:
- 类间解耦,调用者与接收者之间没有任何依赖关系,调用者实现功能时不需要了解到底是哪个接收者执行,只需调用Command抽象类的execute方法就可以了;
- 可扩展性,Command的子类可以非常容易扩展,并且调用者和高层模块不产生严重的代码耦合;
缺点:
- Command类膨胀厉害,如果有N个命令,则Command类的子类就为N个;
应用场景:如Android中各种事件的处理;
责任链模式
优点:
- 请求与处理分开,请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌;
缺点:
- 性能问题,每个请求都是从链头遍历到链尾的,当这个责任链比较长时,遍历开销会比较大;
应用场景:
- 如Android事件的传递机制;
装饰器模式
优点:
- 装饰类和被装饰类可以独立发展,而不会互相耦合;
- 装饰模式是继承关系的一个替代方案,不管装饰多少层,最终返回的也还是那个对象;
- 装饰模式可以动态地扩展一个实现类的功能;
缺点:
- 多层的装饰比较复杂,当使用多层装饰出现问题时,排查问题的工作量比较大
应用场景:
- 需要扩展一个类的功能,或给一个类增加附加功能;
- 需要为一批兄弟类进行改装或加装功能;
策略模式
优点:
- 算法可以自由切换,只要实现抽象策略,它就成为策略家庭的一个成员;
- 避免使用多重条件判断,
- 扩展性良好,在现有的系统中增加一个策略太容易,只要实现接口就可以了;
缺点:
- 策略类数量多,每一个策略都是一个类,复用的可能性很小;
- 所有的策略类都需要对外暴露,上层模块必须知道有哪些策略,然后决定使用哪一个策略;
应用场景:
- 多个类只有在算法或行为上稍有不同的场景;
- 算法需要自由切换的场景;
- 需要屏蔽算法规则的场景;
适配器模式
优点:
- 让两个没有任何联系的类在一起运行;
- 增加了类的透明性;
- 提高了类的复用度;
- 灵活性好,当不需要适配器时,只要删掉这个适配器就可以了,
应用场景:
- 修改一个已经投产的接口时,
- Android中各种Adapter,
迭代器模式
- 迭代器模式是为解决遍历容器中的元素而诞生的,没有人会单独写一个迭代器,使用Java提供的Itreator就可以满足要求了;
组合模式
优点:
- 高层模块调用简单,高层模块不需要关心自己处理的是单个对象还是整个组合结构,
- 节点可以自由增加;
缺点:
- 调用时会直接使用实现类,不符合面向接口编程思想;
应用场景:
- 维护和展示部分-整体关系的场景,如树型菜单,文件和文件夹的管理;
- 只要是树型结构,就要考虑使用组合模式;
观察者模式
优点:
- 观察者与被观察者之间是抽象耦合,不管是增加观察者还是被观察者都非常容易扩展;
- 建立一套触发机制;
缺点:
- 一个被观察者,多个观察者,开发与调度会比较复杂,在Java中消息的通知默认是顺序执行,其中一个观察者卡壳,会影响整体的执行效率,一般要考虑采用异步的方式;
应用场景:
- 关联行为场景,如Android中数据变化会引起UI的变化;
- 事件多级触发场景;
- 跨系统的消息交换场景;
门面模式
优点:
- 减少系统的相互依赖,所有的依赖都是与门面对象的依赖,与子系统无关。
- 提高了灵活性;
- 提高了安全性,想让你访问子系统的哪些业务就开通哪些逻辑;
缺点:
- 不符合开闭原则,当出现bug后,只能通过修改门面角色的代码来修复;
应用场景:
- 为一个复杂的模块或子系统提供一个供外界访问的接口,如Android的
Context
类只是一个抽象类,所有的功能都是在ContextImpl
类实现的,我们不会察觉到ContextImpl
的存在,只需要调用Context就可以了; - 子系统相对独立,外界对子系统的访问只要黑箱操作即可;
- 预防低水平开发人员带来的风险,被限定在指定的子系统开发;
备忘录模式
应用场景:
- 需要保存和恢复数据的相关状态场景;
- 提供一个可回滚的操作场景;
- 需要监控的副本场景中;
- 数据库连接的事务管理就是用的备忘录模式;
注意事项:
- 备忘录的生命期,要主动管理它的生命周期,建立就要使用,不使用就删除;
- 备忘录的性能,不要在频繁建立备份的场景中使用备忘录模式;(对象的创建是需要消耗资源的)
访问者模式
优点:
- 符合单一职责原则,具体元素角色负责数据的加载,而访问者类则负责数据的呈现;
- 优秀的扩展性,
- 灵活性非常高;
缺点:
- 具体元素对访问者公布细节,访问者要访问一个类就必须要求这个类公布一些方法和数据;
- 具体元素变更比较困难;具体元素角色的增加、删除、修改都是比较困难;
- 违背了依赖倒置原则,访问者依赖的是具体的元素,而不是抽象的元素;
应用场景 :
- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作;
- 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作”污染“这些对象的类;
- 业务规则要求遍历多个不同的对象;
状态模式
优点:
- 结构清晰,避免了过多的
switch...case
或if...else
语句的使用; - 遵循设计原则,每个状态就是一个子类;
- 封装性非常好,将状态变换放置到类的内部来实现;
缺点:
- 子类会太多,也就是类膨胀,有多少个状态,就会有多少个子类;
应用场景:
- 行为随状态改变而改变的场景,如权限设计;
- 条件、分支判断语句的替代者,通过扩展子类实现条件的判断处理;
- 状态的个数最好不要超过5个;
解释器模式(现在使用较少)
优点:
- 扩展性好,
缺点:
- 解释器模式会引起类膨胀;
- 采用了递归调用方法;
享元模式
优点:
- 大大减少应用程序创建的对象,降低程序内存的占用;
缺点:
- 提高了系统复杂性,需要分离出内部和外部状态;
应用场景:
- 系统中存在大量的相似对象;
- 需要缓冲池的场景;
- 细粒度的对象都具有较接近的外部状态;且内部状态与环境无关
桥梁模式
优点:
- 抽象与实现分离;
- 优秀的扩充能力;
- 实现细节对客户透明;
应用场景:
- 不希望或不适用继承的场景;
- 接口或抽象类不稳定的情况;
- 重要性要求较高的场景;