结构型设计模式
结构型模式描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。
有哪些
结构型模式可以分为类结构型模式和对象结构型模式:
- 类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。
- 对象结构型模式关心类与对象的组合,通过关联关系使得在一 个类中定义另一个类的实例对象,然后通过该对象调用其方法。 根据 “合成复用原则”,在系统中尽量使用关联关系来替代继 承关系,因此大部分结构型模式都是对象结构型模式。
- 外观模式(门面模式)
- 享元模式
- 桥接模式
- 代理模式
- 装饰者模式
- 适配器模式
- 组合模式
各设计模式图解
2.1 门面模式(外观模式)
为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层的接口,这个接口使得这一子系统更加容易使用。讲人话就是提供一个简单明确的接口,将众多复杂的系统功能整合了交给客户端。
源码应用
java.lang.Class给一组复杂的操作提供了统一的操作入口
优点
- 实现了客户端与子系统间的解耦,客户端不需要知道子系统的复杂操作,简化调用过程。
- 可以帮我们合理的划分访问层次
- 符合迪米特法则,客户端和外观类打交道,将子系统隐藏起来
缺点
- 在没有抽象外观类的时候,增加新的子系统可能需要修改外观类或客户端代码。违背ocp原则
应用场景
- 设计初期阶段,应该要有意识的将不同的两个层分离
- 开发阶段,子系统变得复杂时,增加外观类减少他们之间的依赖
- 维护一个大型遗留项目时,可以为新系统开发一个外观类,新系统与外观类交互,外观类与遗留代码交互
2.2 享元模式
运用共享技术有效地支持大量细粒度的对象,享元模式的本质是缓存共享对象,降低内存消耗。
源码应用
-
池化技术都是参考了享元模式,例如线程池,数据库连接池,常量池等等
-
JDK的Integer应用享元模式缓存了[-128,127]- 在
IntegerCache的静态代码块中对[-128,127(可设置)]进行了缓存。通过Interger的valueOf方法获取的时候在这个范围内直接从缓存中获取。这就是享元模式的应用
- 在
优点
- 减少重复对象造成的内存浪费
- 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享
缺点
- 提高了系统的复杂性,需要分离出内部和外部状态
- 对象在缓冲池复用要考虑线程问题
应用场景
- 一个程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时考虑使用
2.3 桥接模式
将抽象部分和他的实现部分分离,使它们可以独立地变化(实现指的是抽象类和他的派生类用来实现自己的对象)。
实现系统可能有多角度分类,每种分类都可能有变化,那么就把这种多角度分离出来让他们独立变化,让它们各自变化,减少他们之间的耦合。
源码应用
jdbc中使用了桥接模式
优点
- 扩展性好,更改实现只需要增加不同的实现子类,符合开闭原则;
- 将抽象与实现分离,让二者可以独立变化,替代多层继承方案,减少类的个数;
缺点
- 在设计之前需要识别出两个独立变化的维度(抽象和实现),因此使用范围具有局限性;
- 增加了系统复杂性,增加了理解和设计难度,要求开发者针对抽象进行设计和编程
使用场景
- 系统又多角度分类(多个引起变化的维度),即可使用桥接模式
2.4 代理模式
为对象提供一个替身,以控制对这个对象的访问。
源码应用
-
Thread类静态代理
// 实现了Runable接口 public class Thread implements Runnable { . . . /* What will be run. */ private Runnable target; public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name; Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { /* Determine if it's an applet or not */ /* If there is a security manager, ask the security manager what to do. */ if (security != null) { g = security.getThreadGroup(); } /* If the security doesn't have a strong opinion of the matter use the parent thread group. */ if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */ g.checkAccess(); /* * Do we have the required permissions? */ if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); } @Override public void run() { // 代理 if (target != null) { target.run(); } } . . . }
优点
-
将调用者和被调用者分离,一定程度上降低系统耦合性,扩展性好;
-
不同类型的代理可以对客户端对目标对象的访问进行不同的控制
- 远程代理,为一个对象在不同的地址空间提供局部代表。可以隐藏一个对象存在于不同地址空间的事实;
- 虚拟代理,是用一个小对象来代替一个大对象。减少系统资源的消耗,对系统进行优化并提高运行速度;
- 安全代理,可以控制客户端对真实对象的访问权限;
- 智能指引,调用真实对象时,代理处理另外一些事;
缺点
- 代理模式会造成系统中类的数量增加;
- 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
适用场景
- 代理模式主要是有两个作用:保护对象,增强对象。需要这些的时候就考虑适用代理模式
2.5 装饰模式
不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能)
源码应用
- io流就用到了装饰模式。以字节输入流举例
优点
- 装饰者是继承的有力补充,比继承更加灵活。不改变原有对象的情况下动态的给对象扩展功能;
- 装饰者模式可以在运行时扩展一个对象的功能,另外也可以通过配置文件在运行时选择不同的装饰器,从而实现不同的行为。也可以通过不同的组合,可以实现不同效果;
- 完全遵循
开闭模式,可以根据需要增加新的装饰类
缺点
- 需要创建很多的装饰类,增加系统复杂性
应用场景
- 用于扩展一个类的功能或给一个类添加附加职责。
- 动态的给一个对象添加功能,这些功能可以再动态的撤销。
对比动态代理
- 装饰模式其实是代理模式的一种特殊情况,其共同点是都具有相同的接口,不同的是,装饰模式重在对功能的增强或减弱,而代理模式注重控制代理者的权限,装饰者模式必须满足
is-a的关系
对比配置起模式
- 是一种特殊的配置器模式,有层级关系。需要注入父类
- 装饰者满足
is-a关系 ,适配器满足has-a关系 - 装饰者注重扩展和覆盖,适配器注重兼容和转换(方向不一样)
2.6 适配器模式
将一个接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
源码应用
SpringMVC的HandlerAdapter
Spring-AdvisorAdapter
优点
- 符合开闭原则,使用适配器而不需要改变现有类,提高类的复用性;
- 目标类和适配器类解耦,提高程序的扩展性;
缺点
- 会增加系统的复杂性,增加代码的阅读难度
应用场景
- 随着软件不断迭代,功能类似而接口不同的情况下的解决方案;
- 例如当初在开发一个医院的crm系统时,原先对接的是医院老
HIS系统,但是后面老HIS系统被淘汰,新系统上线。37个门店逐个上线的过程中,涉及到新老系统并行使用,这时就十分需要适配器模式了。
2.7 组合模式
将对象组合成树形结构以表示【部分-整体】的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象(整体和部分使用一样的api操作,做到客户端无感)
源码应用
JDK中MAP用到了组合模式
优点
- 简化客户端操作,客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题
- 具有较强的扩展性。更改组合时,只需要调整内部的层次关系,客户端不需要在改动
缺点
- 在增加新构件时很难对容器中的构件类型进行限制
- 要求较高的抽象性,叶子和节点差异较多就不适用组合模式
应用场景
- 需求是体现部分与整体层次的结构时,以及希望用户可以忽略组合对象和单个对象的不同,统一的使用组合结构中的所有对象时,就应该考虑使用组合模式了
- 需要处理一个树形结构的时候