一、基本概念
代理模式是一种设计模式,它的定义是:为其他对象提供一种代理以控制对这个对象的访问。代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。
代理模式的主要优点有:
- 职责清晰,实现了对真实主题的访问控制和保护。
- 高扩展性,只要实现了抽象主题的接口,都可以用代理⁵。
- 智能化,可以使用动态代理实现更灵活的功能⁵。
代理模式的主要缺点是:
- 在客户端和目标对象之间增加了一个代理对象,会造成请求处理速度变慢。
- 增加了系统的复杂度。
代理模式的主要角色有:
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
根据代理类创建时机的不同,代理模式可以分为静态代理和动态代理。
静态代理是指在编译时就已经确定了代理类和被代理类之间的关系,代理类需要实现与被代理类相同的接口,并在内部调用被代理类的方法,并且可以添加一些额外的处理逻辑。静态代理的优点是简单易用,缺点是不够灵活,每一个被代理类都需要一个对应的代理类。
动态代理是指在运行时根据被代理类动态生成对应的代理类,不需要事先编写代码。动态代理可以利用反射机制或者第三方库来实现。动态代理的优点是更加灵活和智能,可以根据不同的被代理类生成不同的代理类,并且可以在运行时修改或增加方法。动态代理的缺点是相对复杂,需要掌握反射机制或者第三方库的使用。
- 虚拟代理:用于延迟创建开销大的对象,直到真正需要时才创建,例如图片加载。
- 远程代理:用于访问远程对象,隐藏了远程对象所在的网络地址和通信细节,例如RMI。
- 保护代理:用于控制对敏感对象的访问权限,根据不同的用户提供不同的访问控制,例如文件系统。
- 缓存代理:用于为开销大的运算结果提供暂时的存储,以便多次请求时可以共享结果,提高性能,例如计算器。
- 智能引用:用于在访问对象时执行一些附加操作,例如计算对象的引用次数,释放对象占用的资源等。
代理模式的uml图如下:
uml图
二、安卓源码中的实例
在安卓开发中,有很多地方使用了代理模式,例如:
- Binder机制:Binder是安卓中实现跨进程通信(IPC)的一种机制,它可以让客户端和服务端之间通过一个中间人(Binder驱动)进行通信。Binder机制中涉及到两种代理模式:静态代理和动态代理。静态代理是指客户端和服务端都需要定义一个AIDL接口,并且通过工具生成对应的Stub类和Proxy类。Stub类是服务端的真实主题,它继承了Binder类并实现了AIDL接口;Proxy类是客户端的代理主题,它也实现了AIDL接口,并持有一个IBinder对象(Stub类的引用)。客户端通过Proxy类调用Stub类的方法,实际上是通过Binder驱动转发给服务端进程。动态代理是指客户端不需要定义AIDL接口,而是通过反射机制动态生成一个代理对象,该对象实现了服务端提供的接口,并持有一个IBinder对象。客户端通过该代理对象调用服务端的方法,也是通过Binder驱动转发给服务端进程。
- Retrofit:Retrofit是一个流行的网络请求框架,它可以让开发者通过简单地定义一个接口,并添加一些注解,就可以进行网络请求。Retrofit内部使用了动态代理模式,它通过反射机制在运行时创建一个代理对象,该对象实现了开发者定义的接口,并持有一个CallAdapter对象。开发者通过该代理对象调用接口中的方法,实际上是通过CallAdapter对象将请求封装成一个Call对象,并执行网络请求。
在安卓开发中,有很多场景可以使用到代理模式。例如: - 延迟加载:为了提高性能和节省资源,可以使用代理模式来实现延迟加载,即只有在真正需要时才创建真实对象。例如,Android中的Glide框架就使用了代理模式来实现图片的延迟加载,它允许用户通过Glide.with()方法来创建一个RequestManager对象,该对象是一个代理对象,它会根据用户的配置和生命周期来管理图片加载的请求。当用户调用RequestManager.load()方法时,才会真正开始加载图片,并显示在ImageView上。
三、Kotlin、Java和C++实现
代理模式可以用不同的编程语言来实现,下面我们分别用Kotlin、Java和C++来举例说明。
3.1 Kotlin实现
假设我们有一个打印机接口,定义了打印文本和图片的方法:
interface Printer {
fun printText(text: String)
fun printImage(image: String)
}
然后我们有一个真实的打印机类,实现了打印机接口:
class RealPrinter : Printer {
override fun printText(text: String) {
println("RealPrinter prints text: $text")
}
override fun printImage(image: String) {
println("RealPrinter prints image: $image")
}
}
接下来,我们想要在打印之前和之后添加一些日志信息,但是又不想修改真实打印机类的代码,这时候就可以用代理模式来实现。我们可以定义一个代理打印机类,也实现了打印机接口,并持有一个真实打印机对象的引用:
class ProxyPrinter(private val printer: Printer) : Printer {
override fun printText(text: String) {
println("ProxyPrinter starts printing text")
printer.printText(text)
println("ProxyPrinter finishes printing text")
}
override fun printImage(image: String) {
println("ProxyPrinter starts printing image")
printer.printImage(image)
println("ProxyPrinter finishes printing image")
}
}
这样,我们就可以通过代理打印机对象来调用真实打印机对象的方法,并在前后添加日志信息:
fun main() {
val realPrinter = RealPrinter()
val proxyPrinter = ProxyPrinter(realPrinter)
proxyPrinter.printText("Hello World")
proxyPrinter.printImage("Kotlin.png")
}
输出结果如下:
ProxyPrinter starts printing text
RealPrinter prints text: Hello World
ProxyPrinter finishes printing text
ProxyPrinter starts printing image
RealPrinter prints image: Kotlin.png
ProxyPrinter finishes printing image
这是一种静态代理的实现方式,我们需要手动编写代理类,并实现所有需要代理的方法。如果我们想要更简洁和灵活的方式,我们可以使用Kotlin提供的委托模式。委托模式是指一个类可以将某些方法或属性的实现委托给另一个对象,而不是自己去实现。Kotlin提供了by关键字来支持委托模式。
使用委托模式,我们可以省略代理类的定义,而直接在创建代理对象时指定委托对象,并重写需要添加额外操作的方法:
fun main() {
val realPrinter = RealPrinter()
val proxyPrinter = object : Printer by realPrinter {
override fun printText(text: String) {
println("ProxyPrinter starts printing text")
realPrinter.printText(text)
println("ProxyPrinter finishes printing text")
}
override fun printImage(image: String) {
println("ProxyPrinter starts printing image")
realPrinter.printImage(image)
println("ProxyPrinter finishes printing image")
}
}
proxyPrinter.printText("Hello World")
proxyPrinter.printImage("Kotlin.png")
}
输出结果与上面相同。
这是一种动态代理的实现方式,我们不需要提前创建代理类,而是利用反射机制在运行时创建代理对象,并动态地添加额外的操作。这样可以减少代码量,并提高可扩展性。
3.2 Java实现
Java是一种广泛使用的面向对象编程语言,它也支持代理模式的实现,但是没有Kotlin那么简洁和灵活。Java中的代理模式主要分为静态代理和动态代理两种。
静态代理的实现方式与Kotlin类似,我们需要定义一个接口,一个真实主题类和一个代理主题类,代理主题类持有真实主题类的引用,并实现接口中的所有方法,在调用真实主题类的方法前后添加额外的操作。例如:
// 定义一个打印机接口
public interface Printer {
void printText(String text);
void printImage(String image);
}
// 定义一个真实的打印机类,实现打印机接口
public class RealPrinter implements Printer {
@Override
public void printText(String text) {
System.out.println("RealPrinter prints text: " + text);
}
@Override
public void printImage(String image) {
System.out.println("RealPrinter prints image: " + image);
}
}
// 定义一个代理打印机类,持有真实打印机类的引用,并实现打印机接口
public class ProxyPrinter implements Printer {
private Printer printer;
public ProxyPrinter(Printer printer) {
this.printer = printer;
}
@Override
public void printText(String text) {
System.out.println("ProxyPrinter starts printing text");
printer.printText(text);
System.out.println("ProxyPrinter finishes printing text");
}
@Override
public void printImage(String image) {
System.out.println("ProxyPrinter starts printing image");
printer.printImage(image);
System.out.println("ProxyPrinter finishes printing image");
}
}
然后我们可以通过代理打印机对象来调用真实打印机对象的方法,并在前后添加日志信息:
public class Main {
public static void main(String[] args) {
Printer realPrinter = new RealPrinter();
Printer proxyPrinter = new ProxyPrinter(realPrinter);
proxyPrinter.printText("Hello World");
proxyPrinter.printImage("Java.png");
}
}
输出结果如下:
ProxyPrinter starts printing text
RealPrinter prints text: Hello World
ProxyPrinter finishes printing text
ProxyPrinter starts printing image
RealPrinter prints image: Java.png
ProxyPrinter finishes printing image
动态代理的实现方式与Kotlin也类似,但是需要使用Java提供的java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。Proxy类可以在运行时动态地创建一个代理对象,该对象实现了指定的接口;InvocationHandler接口可以定义一个调用处理器,用于处理代理对象的方法调用,并在调用真实主题对象的方法前后添加额外的操作。例如:
public class Main {
public static void main(String[] args) {
Printer realPrinter = new RealPrinter();
Printer proxyPrinter = (Printer) Proxy.newProxyInstance(
realPrinter.getClass().getClassLoader(),
realPrinter.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("ProxyPrinter starts printing " + method.getName());
Object returnValue = method.invoke(realPrinter, args);
System.out.println("ProxyPrinter finishes printing " + method.getName());
return returnValue;
}
}
);
proxyPrinter.printText("Hello World");
proxyPrinter.printImage("Java.png");
}
}
输出结果与上面相同。
这种方式可以避免编写代理类,而是利用反射机制在运行时动态地创建代理对象,并根据需要添加额外的操作。但是这种方式也有一些缺点,例如性能开销较大,需要生成字节码并加载到内存中;只能针对接口进行代理,不能针对类进行代理。
网友评论