与接口相关的设计模式(2):代理模式、标识类型模式及常量接口模式

在上文与接口相关的设计模式(1)中,详细介绍了定制服务模式和适配器模式,下面我们来看第三种与接口相关的模式:代理模式。

代理模式

定义:为对象提供一种代理,以控制对这个对象的访问。

分类:

  1. 远程代理(Remote Proxy)—为不同地理的对象提供局域网代表对象。(类似于客户端和服务器端)
  2. 虚拟代理(Virtual Proxy)—根据需要将资源消耗很大的对象进行延迟,真正需要的时候才进行创建。(网页中图片的加载,先用一张虚拟的图片进行显示,等图片加载完成后再进行显示)
  3. 保护代理(Protect Proxy)—控制用户的访问权限。(发帖功能)
  4. 智能引用代理(Smart Reference Proxy)—提供对目标对象一些额外的服务。(火车站代售处为火车站代理)

以下以智能引用代理为例,介绍代理的两种实现方式:静态代理和动态代理。

  • 静态代理:代理和被代理对象在代理之前是确定的,它们都实现了相同的接口或者继承相同的抽象类。

下面为简单示例,演示一个汽车对象被代理实现计时:

package com.proxy;

public interface Moveable {//汽车的行驶功能
    void move();
}

如果没有代理,汽车行驶的计时功能要在汽车本身行驶时实现:

package com.proxy;

import java.util.Random;

public class Car implements Moveable{
    @Override
    public void move() {
        long startTime = System.currentTimeMillis();
        System.out.println("汽车开始行驶");

        try{
            Thread.sleep(new Random().nextInt(1000));
            System.out.println("行驶中");
        }catch(InterruptedException e){
            e.printStackTrace();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("汽车行驶结束,行驶时间为:"+(endTime-startTime)+" ms");
    }
}

对于这种情景,我们可以创建一个代理来专门实现计时功能,如下:

package com.proxy;

public class CarProxy1 extends Car{

    @Override
    public void move() {
        long startTime = System.currentTimeMillis();
        System.out.println("汽车开始行驶");
        super.move();
        long endTime = System.currentTimeMillis();
        System.out.println("汽车行驶结束,行驶时间为:"+(endTime-startTime)+" ms");
    }
}

此时在Car类中的move方法可以只行驶,将计时功能交给代理去实现,Car类中的move方法代码如下:

public void move() {

        try{
            Thread.sleep(new Random().nextInt(1000));
            System.out.println("行驶中");
        }catch(InterruptedException e){
            e.printStackTrace();
        }

    }

现在我们要访问汽车时不用再直接实例化Car类,而是通过访问它的代理:CarProxy1实例,如下:

package com.proxy;

public class Test {
    public static void main(String []args){
       CarProxy1 car  = new CarProxy1();
       car.move();
    }
}

以上是通过继承的方式实现,还可以通过组合的方式实现,即不通过继承Car类,而是通过包装Car类来调用Car实例的行驶功能。代码如下:

package com.proxy;

public class CarProxy2 implements Moveable{

    private Car car;
    public CarProxy2(Car car) {
        super();
        this.car = car;
    }

    @Override
    public void move() {
        long startTime = System.currentTimeMillis();
        System.out.println("汽车开始行驶");
        car.move();
        long endTime = System.currentTimeMillis();
        System.out.println("汽车行驶结束,行驶时间为:"+(endTime-startTime)+" ms");
    }
}

通过组合方式创建的代理类,在实例化代理前,需要先实例化Car对象,代码如下:

package com.proxy;

public class Test {
    public static void main(String []args){
        Car car = new Car();
        CarProxy2 carProxy2  = new CarProxy2(car);
        carProxy2.move();
    }
}

与适配器模式中的类的适配器模式、对象的适配器模式相似,代理模式中组合方式比继承方式更灵活,推荐使用组合方式。比如再对Car实现日志记录等功能,使用继承方式则要再新建一个代理继承CarProxy1,之后再添加功能就要无限的向下继承,难于维护。

动态代理

上例中对汽车创建了一个时间记录代理,那么如果我们要对火车、飞机都记录时间呢?就需要分别对火车、飞机创建代理类。我们需要一个方法来把记录时间这个代理抽离出来,这样当需要对不同的交通工具实现记录时间功能的时候,直接运用这个抽离出的代理,这样可大大减少代码的重用率。这就引出了动态代理。

动态代理就是动态产生代理,实现对不同类,不同方法的代理。下面来看JDK动态代理。以下为JDK动态代理的实现机制:

如图所示,JDK的动态代理其实就是在代理类ProxySubject与被代理类RealSubject之间加入了一个ProxyHandler类,这个ProxyHandler类实现了InvocationHandler接口,它充当事务处理器,比如类似于上面给汽车计时的日志处理,时间处理等事务都是在这个ProxyHandler类中完成的。

InvocationHandler接口源码如下:

package java.lang.reflect;
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

其中第一个参数proxy代表代理对象;第二个参数method代表被代理的方法;第三个参数代表该方法的参数数组。

JDK动态代理实现步骤:

  1. 创建一个实现InvocationHandler的类,必须实现invoke方法
  2. 创建被代理的类和接口
  3. 调用Proxy的静态方法,创建一个代理类
  4. 通过代理调用方法

1,创建一个实现InvocationHandler的类,必须实现invoke方法:

package com.jdkproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler {
    private Object object;
    public TimeHandler(Object object) {
        super();
        //被代理的对象
        this.object = object;
    }
    /**
     * proxy: 代理对象
     * method: 被代理对象的方法
     * args:方法的参数
     * return:  调用方法的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        long startTime = System.currentTimeMillis();
        System.out.println("汽车开始行驶");

        method.invoke(object);

        long endTime = System.currentTimeMillis();
        System.out.println("汽车行驶结束,行驶时间为:"+(endTime-startTime)+" ms");
        return null;
    }

}

步骤 2,3,4,以下为Test类:

package com.jdkproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

import com.proxy.Car;
import com.proxy.Moveable;

public class Test {

    public static void main(String[]args){
        Car car = new Car();
        InvocationHandler h = new TimeHandler(car);
        Class<?> cls = car.getClass();
        /**
         * loader:类加载器
         * interfaces:实现的接口
         * h InvocationHandler
         */
        Moveable  m = (Moveable) Proxy.newProxyInstance(
                cls.getClassLoader(), cls.getInterfaces(), h);
        m.move();
    }

}

运行结果:

CGLIB动态代理:

CGLIB代理实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强。主要使用于改造遗留系统,这些系统一般不会继承特地的接口。

cglib动态代理实现需要导入相关包,此处导入的为cglib-nodep-2.1_3.jar,然后需要创建事务处理器类,并实现MethodInterceptor接口,事务处理器类CglibProxy源码如下:

package com.cjlibproxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();
    public Object getProxy(Class clazz){
        //设置创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    /**
     * 拦截所有目标类方法的调用
     * obj 目标类的实例
     * m 目标方法的反射对象
     * args 方法的参数
     * proxy 代理类的实例
     */
    @Override
    public Object intercept(Object obj, Method m, Object[] args,
            MethodProxy proxy) throws Throwable {
        //代理的业务逻辑
        System.out.println("代理:开车");

        //代理类调用父类的方法
        proxy.invokeSuper(obj, args);

        //代理的业务逻辑
        System.out.println("代理:到了");
        return null;
    }

}

有一个火车类,并没有实现接口:

package com.cjlibproxy;

public class Train {
    public void move(){
        System.out.println("火车行驶中...");
    }

}

测试类:

package com.cjlibproxy;

public class Test {

    public static void main(String[]args){
        CglibProxy proxy = new CglibProxy();
        Train t = (Train) proxy.getProxy(Train.class);
        t.move();
    }
}

测试结果:

总结: JDK动态代理和CGLIB动态代理的使用都很简单,如果能理解内部的具体实现流程才能更深刻的理解动态代理,关于JDK动态代理的模拟实现会在之后文章补充。

标识类型模式

定义一个不包含任何方法的接口,用它仅仅来表示一种抽象类型。所有实现该接口的类意味着属于这种类型。

比如定义一个Food接口,其中不包含任何方法:

public interface Food{}//实现该接口的类都是食物类型

鱼肉:

public class Fish implements Food{...}

进食方法:

public void eat(Food food){...}

进食:

Food fish = new Fish();//Fish实现了Food接口,标识其食物类型
eat(fish);//合法
Book book = new Book();//Book未实现Food接口
eat(book);//编译错误

所谓标识类型模式就是借助Java编译器来对传给eat()方法的food参数进行语义上的约束。Food接口被称为标识类型接口,这种接口没有任何方法,仅代表一种抽象类型。在JDK中,有如下两个典型的表示类型接口。

  • java.io.Serializable接口:实现该接口的类的实例可以被序列化
  • java.io.Remote接口:实现该接口的类的实例可以作为远程对象

常量接口模式

在一个软件系统中会使用一些常量,一种流行的做法是把相关的常量放在一个专门的常量接口中定义,例如:

package com.FinalInterface;

public interface MyConstants {
    public static final double MATH_PI = 3.1415926;
    public static final double MATH_E = 2.71828;

}

以下Circle类需要访问以上MATH_PI常量,一种方式是采用直接访问方式,如下:

package com.FinalInterface;

public class Circle {
    private double r;//半径
    public Circle(double r){
        this.r = r;
    }
    public double getCircumference(){
        return 2 * r * MyConstants.MATH_PI;
    }
}

在JDK1.5中引入了”import static“语句,它允许类A直接访问另一个接口B或类B中的静态常量,而不必指定接口B或类B的名字,而且类A无须实现接口B或者继承类B。如下:

package com.FinalInterface;
import static com.FinalInterface.MyConstants.*;
public class Circle {
    private double r;//半径
    public Circle(double r){
        this.r = r;
    }
    public double getCircumference(){
        return 2 * r * MATH_PI;
    }
}

import static 语句既可以简化编程,又能防止Circle类继承并公开MyConstants中的静态常量。

结合上我的上一篇:与接口相关的设计模式(1):定制服务模式和适配器模式详解,一共记录了与接口相关的5种设计模式,分别是定制服务模式、适配器模式、代理模式、标识类型模式以及常量接口模式。接口是构建松耦合的软件系统的重要法宝。接口的优势在于一个类可以实现多个接口,接口获得这一优势是以不允许为任何方法提供实现作为代价的(暂不考虑JAVA8的Default方法)。

我们可以把接口作为系统中最高层次的抽象类型。站在外界使用者(另一个系统)的角度,接口向使用者承诺系统能提供哪些服务;站在系统本身的角度,接口指定系统必须实现哪些服务。系统之间通过接口进行交互,这可以提高系统之间的松耦合。

至于抽象类呢,它用来定制系统中的扩展点。可以把抽象类看作介于”抽象“和”实现“之间的半成品。抽象类力所能及的完成了部分实现,但还有一些功能有待于它的子类去实现。

时间: 2024-08-22 10:53:47

与接口相关的设计模式(2):代理模式、标识类型模式及常量接口模式的相关文章

Java设计模式之--------&gt;&quot;代理模式&quot;

01.什么是代理模式? 解析:代理(Proxy):代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不想或者不能直接引用另一个对象, 而代理对象可以在客户端和目标对象之间起到中介的作用.代理模式的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象. 这些额外的操作通常需要与实际对象进行通信. 02.代理的组成都有什么? 解析:由三部分组成,分别是:抽象对象(Subject 接口),真实对象(RealSubject 实现了Subje

设计模式之代理模式(Proxy)详解及代码示例

一.代理模式的定义 代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问.这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介,代理模式也叫做委托模式. 二.为什么使用代理模式 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口. 开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委

标识类型模式和常量接口模式

标识类型模式 定义一个不包含任何方法的接口,用它仅仅来表示一种抽象类型.所有实现该接口的类意味着属于这种类型. 比如定义一个Food接口,其中不包含任何方法: public interface Food{}//实现该接口的类都是食物类型 鱼肉: public class Fish implements Food{...} 进食方法: public void eat(Food food){...} 进食: Food fish = new Fish();//Fish实现了Food接口,标识其食物类型

【设计模式】代理模式

代理模式在所需对象和用户代码之间增加了一层对象,这个对象被称为代理.用户代码只需要直接操作代理对象即可.著名的代理模式的例子就是引用计数指针对象,它使得我们对真实对象的操作都需要经过引用计数指针对象.下面是用C++写的一个运用了代理模式的例子. #include <iostream> #include <string> using namespace std; // 作为接口的抽象基类 class Subject { public: virtual void DoAction()

设计模式之代理模式(Proxy)摘录

23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象.创建型模式有两个不断出现的主旋律.第一,它们都将关于该系统使用哪些具体的类的信息封装起来.第二,它们隐藏了这些类的实例是如何被创建和放在一起的.整个系统关于这些对象所知道的是由抽象类所定义的接口.因此,创建型模式在什么被创建,谁创建它,它是怎样被创建的,以

C#设计模式之代理模式(一)

原文地址:http://blog.csdn.net/lovelion/article/details/8227953 代理模式是常用的结构型设计模式之一,当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口.根据代理模式的使用目的不同,代理模式又可以分为多种类型,例如保护代理.远程代理.虚拟代理.缓冲代理等,它们应用于不同的场合,满足用户的不同需求. 15.1 代理模式概述 近年来,代购已逐步成为电

设计模式之代理模式学习

代理模式是一种结构型模式,通常源于下边的情况:人们对于复杂的软件系统经常有一种处理手法,即添加一个间接层,从而对系统获得一种更为灵活,满足特定须要的解决方式.比例如以下图中,B有安全控制或者是缓存控制.B中存在A根本不须要知道的控制方式.A直接使用B,结构上复杂,实现也不方便.A直接使用B的方式,控制不够灵活,可能要用到A根本不知道的方式.添加一个间接层,从而对系统获得更为灵活.满足特定须要的解决方式.假如A须要调用B三次,B比方处理远程的另外一个进程,C做为B的代理人.A和C处于一个地址空间,

设计模式:代理模式与装饰模式

1.装饰者模式与代理模式 (静态代理) 在日常开发里面,我们经常需要给某个类的方法增加加某些特定的功能. 例如:有婴儿,婴儿会吃饭和走动,如以下类 1 package com.scl.designpattern.proxy; 2 3 //婴儿类 4 public class Child implements Human 5 { 6 public void eat() 7 { 8 System.out.println("eat something...."); 9 } 10 11 @Ov

探索设计模式之----代理模式

代理模式是一种非常重要的设计模式,在Java语言中有着广泛的应用,包括Spring AOP的核心设计思想,都和代理模式有密切关系. 代理模式主要分两种:一种是静态代理,一种是动态代理.两种代理方式的实现有着本质的差异. 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在 客户端和目标对象之间起到中介的作用. 代理模式一般涉及到的角色有: 抽象角色:声明真实对象和代理对象的共同接口. 代理角色:代理对象角色内部含有