设计模式系列之五:代理模式

代理模式

代理模式的定义很简单:给某一对象提供一个代理对象,并由代理对象控制对原对象的引用。

代理模式的结构

有些情况下,一个客户不想活着不能够直接引用一个对象,可以通过代理对象在客户端和目标对象之间起到中介作用。代理模式中的角色有:

1、抽象对象角色

声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象

2、目标对象角色

定义了代理对象所代表的目标对象

3、代理对象角色

代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象

静态代理示例

假如一个接口里面有一个方法,想在调用这个接口的前后都加一点东西,就可以使用代理模式。静态代理是代理模式最简单的实现,先定义一个静态代理接口,里面有一个print()方法,看一下:

public interface StaticHelloWorld
{
    // 定义一个接口,里面有一个打印方法
    void print();
}

让一个子类实现它,打印一句”Hello World”出来:

public class StaticHelloWorldImpl implements StaticHelloWorld
{
    public void print()
    {
        System.out.println("Hello World");
    }
}

给这个接口创建一个代理对象,来实现对接口实现类的代理。注意,这里的重点是代理对象和实际对象实现的是同一个接口,因为希望在任何时候让代理对象替代实际对象:

public class StaticProxy implements StaticHelloWorld
{
    private StaticHelloWorld staticHelloWorld;

    public StaticProxy(StaticHelloWorld staticHelloWorldImpl)
    {
        this.staticHelloWorld = staticHelloWorldImpl;
    }

    public void print()
    {
        System.out.println("Before Hello World!");
        staticHelloWorld.print();
        System.out.println("After Hello World!");
    }
}

写一个类去调用代理对象,在代理对象的构造函数中传入一个实际对象即可:

public class StaticTestMain
{
    public static void main(String[] args)
    {
        StaticHelloWorld shw = new StaticHelloWorldImpl();
        StaticProxy sp = new StaticProxy(shw);
        sp.print();
    }
}

运行结果为:

Before Hello World!
Hello World
After Hello World!

这个很明显,就不说了。

静态代理的缺点

静态代理的特点是静态代理的代理类是程序员创建的,在程序运行之前静态代理的.class文件已经存在了。

从静态代理来看,看到静态代理模式确实可以有一个代理对象来控制实际对象的引用,并通过代理对象来使用实际对象。这种模式在代理量较小的时候还可以,但是代理量一大起来,就存在着三个比较大的缺点:

1、如果想换一种代理内容,比如我在”Hello World”前后不想输入”Before XXX”和”After XXX”了,想输出运行前后系统当前时间,就必须新写一个代理对象。这样很容易造成代理对象的膨胀。

2、代理内容无法复用,也就是说”Before XXX”和”After XXX”只可以给某一个类使用,另一个类如果也想使用这个代理内容,必须自己也写一个,同样,造成的后果就是代理类的无限膨胀

3、接口里面如果新增了一个方法,实际对象实现了这个方法,代理对象也必须新增内容,去给这个新增方法增加代理内容(假如需要的话)

利用JDK中的代理类Proxy实现动态代理的示例

由于静态代理的局限性,所以产生了动态代理的概念。看一下,首先还是定义一个动态代理接口:

public interface DynamicHelloWorld
{
    // 动态代理类,有一个print()方法
    String print();
}

写一个类去实现它,打印方法打印”Enter DynamicHelloWorldImpl.print()”并返回”DynamicHelloWorldImpl”:

public class DynamicHelloWorldImpl implements DynamicHelloWorld
{
    public String print()
    {
        System.out.println("Enter DynamicHelloWorldImpl.print()");

        return "DynamicHelloWorldImpl";
    }
}

最关键的一部分,动态代理类,也是不太好理解的一部分。在Java中,动态代理需要实现InvocationHandler接口。

InvocationHandler接口里面只有一个方法invoke(),至于如何实现,完全看使用者自己的喜好,没有固定。可以像下面这样,把newInstance即生成一个动态代理类的过程放到InvocationHandler的实现类中:

public class DynamicProxy implements InvocationHandler
{
    private Object target;

    public Object newInstance(Object target)
    {
        this.target = target;

        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable
    {
        System.out.println("Before DynamicProxy");
        method.invoke(target, args);
        System.out.println("After DynamicProxy");
        return null;
    }
}

也可以像下面这样,让动态代理类在外面生成,只在构造函数中传入一个target:

public class DynamicProxy implements InvocationHandler
{
    private Object target;

    public DynamicProxy(Object target)
    {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable
    {
        System.out.println("Before DynamicProxy");
        method.invoke(target, args);
        System.out.println("After DynamicProxy");
        return null;
    }
}

如果是前者的写法,那么main函数要这么写:

public class DynamicTestMain
{
    public static void main(String[] args) throws Exception
    {
        DynamicProxy dp = new DynamicProxy();
        DynamicHelloWorld dhwi = new DynamicHelloWorldImpl();
        DynamicHelloWorld dhw = (DynamicHelloWorld)dp.newInstance(dhwi);
        dhw.print();
    }
}

如果是后者的写法,那么main函数要这么写:

public class DynamicTestMain
{
    public static void main(String[] args) throws Exception
    {
        DynamicHelloWorld dhwi = new DynamicHelloWorldImpl();
        InvocationHandler ih = new DynamicProxy(dhwi);
        DynamicHelloWorld dhw =
                (DynamicHelloWorld)Proxy.
                newProxyInstance(DynamicHelloWorld.class.getClassLoader(),
                                 new Class<?>[]{DynamicHelloWorld.class}, ih);
        dhw.print();
    }
}

不管哪种写法,运行结果都是一样的:

Before DynamicProxy
Enter DynamicHelloWorldImpl.print()
After DynamicProxy

动态代理解析

上面两种写法,本质上都是一样的。万变不离其宗,归纳起来,实现一个动态代理可以总结为如下四步:

1、获取要被代理的对象,也就是实际对象

2、实现InvocationHandler接口,生成实际的代理内容

3、利用Proxy.newInstance()方法生成一个代理内容,第三个参数传入InvocationHandler的实现类

4、代理对象调用接口内部的方法

动态代理,利用动态编译+反射技术,把对实际对象的方法调用转换成对传入的InvocationHandler接口实现类的invoke方法的调用,这是动态代理模式实现的关键点。

动态代理的优点

1、最直观的,类少了很多

2、代理内容也就是InvocationHandler接口的实现类可以复用,可以给A接口用、也可以给B接口用,A接口用了InvocationHandler接口实现类A的代理,不想用了,可以方便地换成InvocationHandler接口实现B的代理

3、最重要的,用了动态代理,就可以在不修改原来代码的基础上,就在原来代码的基础上做操作,这就是AOP即面向切面编程

动态代理的缺点

动态代理有一个最大的缺点,就是它只能针对接口生成代理,不能只针对某一个类生成代理,比方说我们在调用Proxy的newProxyInstance方法的时候,第二个参数传某个具体类的getClass(),那么会报错:

Exception in thread “main” java.lang.IllegalArgumentException: proxy.DynamicHelloWorldImpl is not an interface

这是因为java.lang.reflect.Proxy的newProxyInstance方法会判断传入的Class是不是一个接口:

...
/*
  * Verify that the Class object actually represents an
  * interface.
  */
 if (!interfaceClass.isInterface()) {
 throw new IllegalArgumentException(
    interfaceClass.getName() + " is not an interface");
}
...

而实际使用中,我们为某一个单独的类实现一个代理也很正常,这种情况下,我们就可以考虑使用CGLIB(一种字节码增强技术)来为某一个类实现代理了。

时间: 2024-11-03 12:32:03

设计模式系列之五:代理模式的相关文章

C# 设计模式系列教程-代理模式_C#教程

1. 概述 为其它对象提供一种代理以控制对这个对象的访问. 解决的问题:如果直接访问对象比较困难,或直接访问会给使用者或系统带来一系列问题.这样对于客户端(调用者)来说,就不需要直接与真实对象进行交互,解除了调用者与真实对象的耦合. 2. 模式中的角色 2.1 抽象实体(Subject):定义了真实实体(RealSubject)和代理(Proxy)的公共接口,这样就在任何时候使用真实实体(RealSubject)的地方使用代理(Proxy). 2.2 代理(Proxy):保存一个引用使得代理可以

Android设计模式系列之组合模式_Android

Android中对组合模式的应用,可谓是泛滥成粥,随处可见,那就是View和ViewGroup类的使用.在android UI设计,几乎所有的widget和布局类都依靠这两个类. 组合模式,Composite Pattern,是一个非常巧妙的模式.几乎所有的面向对象系统都应用到了组合模式. 1.意图 将对象View和ViewGroup组合成树形结构以表示"部分-整体"的层次结构(View可以做为ViewGroup的一部分). 组合模式使得用户对单个对象View和组合对象ViewGrou

Android设计模式系列之组合模式

Android中对组合模式的应用,可谓是泛滥成粥,随处可见,那就是View和ViewGroup类的使用.在android UI设计,几乎所有的widget和布局类都依靠这两个类. 组合模式,Composite Pattern,是一个非常巧妙的模式.几乎所有的面向对象系统都应用到了组合模式. 1.意图 将对象View和ViewGroup组合成树形结构以表示"部分-整体"的层次结构(View可以做为ViewGroup的一部分). 组合模式使得用户对单个对象View和组合对象ViewGrou

浅析设计模式中的代理模式在C++编程中的运用_C 语言

由遇到的问题引出代理模式 至少在以下集中情况下可以用代理模式解决问题: 创建开销大的对象时候,比如显示一幅大的图片,我们将这个创建的过程交给代理去完成,GoF 称之为虚代理(Virtual Proxy): 为网络上的对象创建一个局部的本地代理,比如要操作一个网络上的一个对象(网络性能不好的时候,问题尤其突出),我们将这个操纵的过程交给一个代理去完成,GoF 称之为远程代理(Remote Proxy): 对对象进行控制访问的时候,比如在 Jive 论坛中不同权限的用户(如管理员.普通用户等)将获得

Ruby使用设计模式中的代理模式与装饰模式的代码实例_ruby专题

代理模式 需求: 小明让小李替他追小丽(送洋娃娃,送花,送巧克力) 没有代理的代码: # -*- encoding: utf-8 -*- #追求者类 class Pursuit attr_accessor :mm def initialize(mm) @mm = mm end def give_dolls puts "#{mm.name} 送你洋娃娃" end def give_flowers puts "#{mm.name} 送你鲜花" end def give_

Java使用设计模式中的代理模式构建项目的实例展示_java

概念 代理模式(Proxy):代理模式其实就是多一个代理类出来,替原对象进行一些操作.比如咱有的时候打官司需要请律师,因为律师在法律方面有专长,可以替咱进行操作表达咱的想法,这就是代理的意思.代理模式分为两类:1.静态代理(不使用jdk里面的方法):2.动态代理(使用jdk里面的InvocationHandler和Proxy). 静态代理由程序员创建或工具生成代理类的源码,再编译代理类.所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了. 动态代理类的源

实例讲解如何在iOS应用开发中使用设计模式中的代理模式_IOS

代理模式是OC中一种常见的设计模式,那么什么是代理模式呢?举个栗子,假设你是一个日发货量过万的淘宝卖家(A),但是每天的派件不可能你本人或者让你的员工去派件,因此你发布了一条信息(B),上面注明各种要求,各大快递公司看到有那么大的利益纷纷上门沟通,最后你选择了一件快递公司(C).那么在上面的例子中,我们即是委托人,发布的信息即协议(protocol),上面规定了派件人需要完成的事,而最后选择的快递公司也就是代理人(delegate),代理我们去派件. 类图: 根据以上类图,可以知道在代理模式中的

Java设计模式之虚拟代理模式

虚拟代理模式(Virtual Proxy)是一种节省内存的技术,它建议创建那些占用大量内存或处理复杂的对象时,把创建这类对象推迟到使用它的时候.在特定的应用中,不同部分的功能由不同的对象组成,应用启动的时候,不会立即使用所有的对象.在这种情况下,虚拟代理模式建议推迟对象的创建直到应用程序需要它为止.对象被应用第一次引用时创建并且同一个实例可以被重用.这种方法优缺点并存. 优点: 这种方法的优点是,在应用程序启动时,由于不需要创建和装载所有的对象,因此加速了应用程序的启动. 缺点: 因为不能保证特

Java设计模式之计数代理模式

描述: 计数代理模式在客户对象调用服务提供者对象上方法的前后执行诸如日志(logging)和计数(counting)一系列附加功能时很有用.计数代理模式建议把这些附加功能封装在一个单独的对象,这个对象就是指计数代理对象,而不是把这些附加的功能实现放到服务提供者的内部.良好的对象设计的一个特征就是对象要专注于提供特定的功能.换句话说,理想的对象不应该做各种不相干的事情.把诸如日志(logging)和计数(counting)等类似的功能封装为一个单独的对象,而让服务提供者对象仅提供它自己的特定功能.