设计模式学习(四)代理模式、适配器模式
结构型模式描述如何将类或对象按某种布局组成更大的结构。
它分为类结构模式和对象结构型模式,类结构模式采用继承机制来组织接口和类,对象结构型模式采用组合或聚合来组合对象,满足“合成复用原则”,灵活性更高。
代理模式:
由于某些原因需要给某对象提供一个代理以控制对该对象的访问,这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
静态代理代理类在编译期就生成,
动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和cGLip代理两种。
结构
代理(Proxy)模式分为三种角色:
(1)抽象主题(subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
(2)真实主题(eal subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象,
(3)代理(proxy)类: 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
优点
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
代理对象可以扩展目标对象的功能;
代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
缺点
增加了系统的复杂度;
静态代理
//卖票接口
public interface SellTickets {void sell();
}//火车站 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {public void sell() {System.out.println("火车站卖票");}
}//代售点
public class ProxyPoint implements SellTickets {private TrainStation station = new TrainStation();public void sell() {System.out.println("代理点收取一些服务费用");station.sell();}
}//测试类
public class Client {public static void main(String[] args) {ProxyPoint pp = new ProxyPoint();pp.sell();}
}
jdk代理
//卖票接口
public interface SellTickets {void sell();
}//火车站 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {public void sell() {System.out.println("火车站卖票");}
}//代理工厂,用来创建代理对象
public class ProxyFactory {private TrainStation station = new TrainStation();public SellTickets getProxyObject() {//使用Proxy获取代理对象/*newProxyInstance()方法参数说明:ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口InvocationHandler h : 代理对象的调用处理程序*/SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),station.getClass().getInterfaces(),new InvocationHandler() {/*InvocationHandler中invoke方法参数说明:proxy : 代理对象method : 对应于在代理对象上调用的接口方法的 Method 实例args : 代理对象调用接口方法时传递的实际参数*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理点收取一些服务费用(JDK动态代理方式)");//执行真实对象Object result = method.invoke(station, args);return result;}});return sellTickets;}
}//测试类
public class Client {public static void main(String[] args) {//获取代理对象ProxyFactory factory = new ProxyFactory();SellTickets proxyObject = factory.getProxyObject();proxyObject.sell();}
}
CGLIB 动态代理
CGLIB(Code Generation Library)是一个高性能的代码生成库,它可以在运行时动态生成目标类的子类,从而实现代理功能。与 JDK 动态代理不同,CGLIB 不需要目标类实现接口。
(1)加入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
(2)实现
//火车站
public class TrainStation {public void sell() {System.out.println("火车站卖票");}
}//代理工厂
public class ProxyFactory implements MethodInterceptor {private TrainStation target = new TrainStation();public TrainStation getProxyObject() {//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数Enhancer enhancer =new Enhancer();//设置父类的字节码对象enhancer.setSuperclass(target.getClass());//设置回调函数enhancer.setCallback(this);//创建代理对象TrainStation obj = (TrainStation) enhancer.create();return obj;}/*intercept方法参数说明:o : 代理对象method : 真实对象中的方法的Method实例args : 实际参数methodProxy :代理对象中的方法的method实例*/public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);return result;}
}//测试类
public class Client {public static void main(String[] args) {//创建代理工厂对象ProxyFactory factory = new ProxyFactory();//获取代理对象TrainStation proxyObject = factory.getProxyObject();proxyObject.sell();}
}
使用场景
日志记录:在方法调用前后记录日志
性能监控:统计方法执行时间
事务管理:在方法调用前后开启和提交事务
权限控制:在方法调用前验证权限
缓存:对方法返回结果进行缓存
远程调用:如 RMI 中的远程代理
AOP 编程:Spring AOP 底层就用到了动态代理
防火墙代理:当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
对比
静态代理:简单直观,但灵活性差,维护成本高
JDK 动态代理:基于接口,无需第三方依赖,适用于大多数场景,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。
CGLIB 动态代理:基于继承,适用于没有实现接口的类,性能较好,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。
适配器模式
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
结构
(1)目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
(2)适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
(3)适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
类适配器
类适配器通过 继承适配者类 + 实现目标接口 的方式,完成接口转换。由于 Java 不支持多继承,类适配器仅适用于 “适配者是类” 的场景(若适配者是接口,需用其他方式),类适配器模式违背了合成复用原则 。
1. 场景假设
假设我们有一个老系统的 SDCard
类(适配者),它能读取 SD 卡的数据,但新模块需要通过 UsbInterface
接口(目标接口)读取数据。我们需要一个适配器,让 SD 卡能通过 USB 接口被访问,类适配器是客户类有一个接口规范的情况下可用,反之不可用 。
2. 代码实现
(1)定义目标接口(Target)
客户端期望的 USB 接口,包含读取数据的方法
// 目标接口:USB设备接口
public interface UsbInterface {// 通过USB读取数据String readDataViaUsb();
}
(2)定义适配者(Adaptee)
老系统的 SD 卡类,接口不符合目标要求:
// 适配者:SD卡(老组件,接口不兼容)
public class SDCard {// SD卡自带的读取方法(与USB接口方法名/规范不同)public String readFromSD() {return "SD卡数据:[照片.jpg, 文档.pdf]";}
}
(3)实现类适配器(Adapter)
通过继承 SDCard
并实现 UsbInterface
,将 SD 卡的方法 “翻译” 成 USB 接口的方法:
// 类适配器:通过继承SDCard,实现USB接口
public class SDCardToUsbAdapter extends SDCard implements UsbInterface {@Overridepublic String readDataViaUsb() {// 调用父类(SDCard)的方法,转换为USB接口的输出System.out.println("类适配器:正在将SD卡数据转换为USB格式...");return super.readFromSD(); // 复用SDCard的功能}
}
(4)客户端调用
客户端只需面向 UsbInterface
编程,无需关心底层是 SD 卡还是其他设备:
public class Client {public static void main(String[] args) {// 1. 创建适配器(封装了SDCard)UsbInterface usbDevice = new SDCardToUsbAdapter();// 2. 客户端通过USB接口读取数据(无需知道底层是SD卡)String data = usbDevice.readDataViaUsb();System.out.println("客户端读取到数据:" + data);}
}
对象适配器
对象适配器通过 实现目标接口 + 持有适配者对象 的方式(组合模式)完成适配,解决了类适配器的单继承局限性,是更常用的实现方式,对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。 。
1. 场景扩展
新增一个 TF卡
类(适配者),要求适配器同时支持 SD 卡和 TF 卡通过 USB 接口读取数据。此时类适配器无法满足(无法继承两个类),对象适配器派上用场。
2. 代码实现
(1)复用目标接口和原有适配者
目标接口 UsbInterface
和适配者 SDCard
不变,新增适配者 TFCard
:
// 新增适配者:TF卡(接口不兼容,需适配)
public class TFCard {public String readFromTF() {return "TF卡数据:[音乐.mp3, 视频.mp4]";}
}
(2)实现对象适配器(Adapter)
通过 “组合” 方式持有适配者对象,而非继承适配者类:
// 对象适配器:通过持有适配者对象,实现USB接口
public class MemoryCardToUsbAdapter implements UsbInterface {// 持有适配者对象(可接收SDCard、TFCard等不同适配者)private Object memoryCard;// 构造器注入适配者(支持不同类型的存储卡)public MemoryCardToUsbAdapter(Object memoryCard) {this.memoryCard = memoryCard;}@Overridepublic String readDataViaUsb() {if (memoryCard instanceof SDCard) {// 调用SDCard的方法System.out.println("对象适配器:SD卡数据转换为USB格式...");return ((SDCard) memoryCard).readFromSD();} else if (memoryCard instanceof TFCard) {// 调用TFCard的方法System.out.println("对象适配器:TF卡数据转换为USB格式...");return ((TFCard) memoryCard).readFromTF();} else {throw new UnsupportedOperationException("不支持的存储卡类型");}}
}
(3)客户端调用
客户端可传入不同的适配者对象,适配器自动适配:
public class Client {public static void main(String[] args) {// 1. 适配SD卡UsbInterface sdUsb = new MemoryCardToUsbAdapter(new SDCard());System.out.println("读取SD卡数据:" + sdUsb.readDataViaUsb());System.out.println("------------------------");// 2. 适配TF卡UsbInterface tfUsb = new MemoryCardToUsbAdapter(new TFCard());System.out.println("读取TF卡数据:" + tfUsb.readDataViaUsb());}
}
jdk
1. 案例 1:Arrays.asList()
与 Enumeration
的适配
在 JDK 1.2 之前,集合框架只有 Vector
、Hashtable
等古老类,它们通过 Enumeration
接口遍历元素;JDK 1.2 引入了 Collection
框架,新增 Iterator
接口(目标接口)。为了让老的 Enumeration
兼容新的 Iterator
,JDK 提供了适配器。
核心角色映射
(1)目标接口(Target):java.util.Iterator
(新的遍历接口)
(2)适配者(Adaptee):java.util.Enumeration
(老的遍历接口)
(3)适配器(Adapter):java.util.Collections.EnumerationIterator
(内部类,隐藏实现)
2. 案例 2:java.io
流中的适配器
java.io
包中,InputStreamReader
和 OutputStreamWriter
是典型的适配器,它们将字节流(InputStream
/OutputStream
)适配为字符流(Reader
/Writer
)。
核心角色映射
(1)目标接口(Target):java.io.Reader
/ java.io.Writer
(字符流接口)
(2)适配者(Adaptee):java.io.InputStream
/ java.io.OutputStream
(字节流类)
(3)适配器(Adapter):java.io.InputStreamReader
/ java.io.OutputStreamWriter
适用场景
(1)以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
(2)使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。