软件设计模式---代理模式

in 软件设计模式 with 0 comment

代理模式是结构型模式的一种,它的作用是为某个对象提供一种代理以控制该对象的访问.即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特征,由于它是结构型模式,所以满足“合成复用原则”,在组合关系或聚合关系比继承关系耦合度低. 代理模式的通用类图: image.png

代理模式的优点

  1. 职责清晰 具体角色是实现具体的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,代码清晰。在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
  2. 高扩展性 具体主题角色随时会发生变化,但是只要实现了接口,接口不变,代理类就可以不做任何修改继续使用,符合“开闭原则”。 另外,代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,同样符合开闭原则。

代理模式的使用场景

代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是通过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。 为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。 更通俗的说,代理解决的问题是:当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理。但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

代理模式有多种不同的实现方式。如果按照代理创建的时期来进行分类:静态代理、动态代理。

静态代理实现逃课场景

博主逃课王,遇见了一个喜欢点名的老师,所以,找了一个老表在老师要点名之前,通知博主,要点名了,然后博主飞速前往教师答道. 代码实现:

public interface People {
    //做第一件事
    void doOne();
    //做第二件事
    void doTwo();
}
public class Student implements People {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public void doOne() {
        System.out.println(name+"到..."+"然后睡着了...");
    }

    @Override
    public void doTwo() {
        System.out.println(name+"交作业...");
    }
}
public class Proxy {
    private Student student;

    public Proxy(String Name) {
        this.student = new Student(Name);
    }
    public void doSomething(){
        doOne();
        student.doOne();
        doTwo();
        student.doTwo();
    }
    public void doOne(){
        System.out.println("喂喂喂"+student.getName()+"老师来第一件事");
    }
    public void doTwo(){
        System.out.println("喂喂喂"+student.getName()+"老师来第二件事");
    }
}

最后一个main方法

public static void main(String[] args) {
        Proxy proxy = new Proxy("彭雷");
        proxy.doSomething();
    }

最后输出 image.png

主要是Proxy类中一个有一个类Student student所以可以调用Student的方法,它的构造方法就是传入Student的实例,所以在doSomething方法中,不仅可以调用Student的方法,也可以扩展自己类的方法.正是完成了高扩展型.

在彭雷逃课的时候,老表会先调用他自己的doOne方法,即通知彭雷,老师来第一件事了,就是要点名了,然后彭雷火急火燎的赶去教师答到,然后睡着了..然后老师来了第二件事,要交作业了,老表赶紧调用doTwo方法把彭雷叫醒,然后彭雷交作业

动态代理实现坏学生逃课行为和好学生课堂互动

在之前的静态代理,已经实现了在彭雷逃课的时候,老表给彭雷打报告,让彭雷免受挂科之灾. 但是目前的老表只能通知彭雷老师要整活了,因为老表人太好,长得也帅,班上学委因为昨晚学习太晚,上课也要老表通知他老师要整活了.所以,现在需要使用动态代理来完成代理. 代码实现: 同样的,定义一个抽象类,彭雷要逃课和交作业;学霸要起立回答问题和把收齐的作业交给老师

public interface People {
    //做第一件事
    void doOne();
    //做第二件事
    void doTwo();
}
public class BadStudent implements People {
    private String name;
    public BadStudent(String name) {
        this.name = name;
    }


    @Override
    public void doOne() {
        System.out.println(name+"答到...");
    }

    @Override
    public void doTwo() {
        System.out.println(name+"交作业...");
    }
}

public class GoodStudent implements People {
    private String name;
    public GoodStudent(String name) {
        this.name = name;
    }

    @Override
    public void doOne() {
        System.out.println(name+"起立回答问题");
    }

    @Override
    public void doTwo() {
        System.out.println(name+"把收齐的作业交给老师");
    }
}
public class DynamicProxy implements InvocationHandler {

    private Object object;

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

    @Override
    //InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法,args表示方法的参数
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("老师开始整活了");
        method.invoke(object,args);
        System.out.println("告辞");
        return null;
    }

}

最后一个main方法

public static void main(String[] args) {
        //实例化目标对象
        //GoodStudent student = new GoodStudent("学委");
        BadStudent student = new BadStudent("彭雷");
        //实例化调用处理类
        DynamicProxy proxy = new DynamicProxy(student);
        //准备一个类加载器
        ClassLoader classLoader = MyProxy.class.getClassLoader();
        //获取目标对象的接口类对象数组
        Class<?>[] interfaces = student.getClass().getInterfaces();
        /*第一个参数:classLoader,使用handler对象的classloader对象来加载我们的代理对象
          第二个参数:interfaces,这里为代理类提供的接口是真实对象实现的接口,这样代理对象就能像真实对象一样调用接口中的所有方法
          第三个参数:proxy,我们将代理对象关联到上面的InvocationHandler对象上
         */
        //创建代理(老表)
        People people = (People) Proxy.newProxyInstance(classLoader,interfaces,proxy);
        people.doOne();
        people.doTwo();
    }

当student是彭雷时,调用BadStudent的方法 image.png 当student是学霸时,调用GoodStudent的方法 image.png 这样,就完成了动态代理 相比静态代理,一个非常显著的优点是动态代理可以在自定义调用处理器统一处理委托类的方法,而不必一个个编写。 而动态代理有一个缺点,那就是只能代理基于接口的类,而无法代理没有接口的委托类

总结

静态代理类优缺点 优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。 缺点: 1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。 2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

动态代理优点: 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强