设计模式之结构型(7)
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
一.适配器模式
1.一句话描述
- 修改一个类接口以适用用户期望的另一接口
2.类图/代码
image.pngCallable 目标接口, Runnable 被适配接口, RunnableAdapter适配类, 属于对象适配器
3.模式分类
根据适配类和被适配类的关系 分成两种
- 对象适配模式: 适配类与被适配类是组合关系
- 类适配模式: 适配类继承被适配类
4.实战案例
-
Spring工具包的枚举迭代器EnumerationIterator
image.png -
SpringMVC的HandlerAdapter
image.png -
JDK RunnableAdapter类 把Runnable 适配成Callable
5.总结
- 1.接口转换, 转换成用户期望的接口
- 2.代码复用,可以复用旧类(被适配类)代码
- 3.便于扩展, 只需增加适配器类就可以使其与现有代码无缝集成
生活中的插座/转接线 也是非常典型的适配模式,
二、装饰器模式
1.一句话描述
- 在原有的类上增强一个类的功能
2.类图
image.pngFilterInputStream 是装饰器, 以组合方式引用 Inpustream, 增强具体InputStream之类的功能
3. 实战案例
-
JDK IO输入流设计
image.png -
JDK IO输出流设计
image.png
4.总结
1.动态地将功能附加到类上,无需修改原有类代码, 灵活性高
2.每个具体装饰器类专注于一个特定功能, 吻合单一职责原则
3.多个装饰器可以按需组合,实现复杂的功能组合
三、代理模式
1. 一句话描述
- 用代理对象来代替真实对象,以控制对真实对象的访问
2.类图/代码/分类
代理分为静态代理和动态代理
(1)静态代理
静态代理在编译时就将接口、实现类、代理类编译成class文件。
静态代理.png
(2)动态代理
动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
JDK动态代理:
RealSubject realSubject = new RealSubject();
Class c = realSubject.getClass();
Subject subject = (Subject) Proxy.newProxyInstance(c.getClassLoader(), c.getInterfaces(), new DynamicInvocationHandler(realSubject));
subject.request();
class DynamicInvocationHandler implements InvocationHandler{
private RealSubject realSubject;
public DynamicInvocationHandler(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("pre ...");
Object object = method.invoke(realSubject,args);
System.out.println("after ....");
return object;
}
}
CGLIB动态代理:
RealSubject realSubject = new RealSubject();
Class c = realSubject.getClass();
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(c.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(c);
// 设置方法拦截器
enhancer.setCallback(new CGLibMethodInterceptor());
RealSubject proxy = (RealSubject) enhancer.create();
proxy.request();
class CGLibMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("pre ...");
Object object = methodProxy.invokeSuper(o,objects);
System.out.println("after ....1111");
return object;
}
}
3.实战案例
- Spring AOP 动态代理, 如目标对象实现了接口默认使用JDK动态代理,否则采用CGLIB动态代理
4.总结
动态代理有JDK 动态代理、CGLIB 动态代理
JDK动态代理只能代理实现了接口的类,CGLIB可以代理未实现任何接口的类(继承)
JDK动态代理直接写Class字节码,CGLIB代理使用ASM框架写Class字节码,相对复杂,生成代理类的效率比较低
四、外观模式
1. 一句话描述
- 提供统一访问入口
2.类图/代码
image.png3.实战案例
- SLFJ中的外观模式, 抽象了各种日志框架Logback, log4j, commons-logging, logging
- spring jdbc中的外观模式
JdbcUtils
- Tomcat中
org.apache.catalina.connector.RequestFacade
4.总结
1.简化接口,让接口更简单,引入外观类可以将子系统与客户端解耦
2.吻合迪米特法则
3.只需增加一个外观类,客户端只跟外观类交互,由外观类跟多个子系统类交互
五、桥接模式
1.一句话描述
- 把抽象和实现分离,使他们能独立改变
2.类图
image.png3.实战案例
4.总结
- 解决一个类存在两个独立变化的维度且两个维度都需要进行扩展(吻合开闭原则)
- 采用组合方式,更加灵活(吻合 合成/聚合复用原则)
六、组合模式
1.一句话描述
- 可以以一致的方式处理个别对象和对象组合(整体/部分)
2.类图
image.png3.实战案例
- 业务系统中菜单就是经典组合模式
4.总结
经常跟迭代器模式在一起对比,其实代码结构类似,迭代器模式侧重的遍历这个行为,是行为模式,而组合模式关注重点是结构关系,即整理和部分
七、享元模式
1.一句话描述
- 该模式通过共享技术支持大量细粒度对象复用,以减少内存使用量
2.类图
image.png3.实战案例
- Integer、Long、Short、Byte等都是利用了享元模式来缓存-128到127之间的数据
Integer i1 = 12;
Integer i2 = new Integer(12); //无法利用享元
Integer i3 = Integer.valueOf(12);
System.out.println(i==i1); //true
System.out.println(i==i2); //false
System.out.println(i==i3); //true
-
JVM为String类开辟 字符串常量池,存储字符串常量
-
游戏中的道具、棋子(俄罗斯方块)
4.总结
- 当需要创建大量相似对象时,抽取享元类(复用)和非享元类,以减少内存使用
享元模式VS单例模式,单例模式是为了保证对象全局唯一,享元模式对象复用,节省内存(多例)