欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android面试题: 动态代理和代理模式

程序员文章站 2022-11-10 22:12:21
android面试题: 动态代理和代理模式。 1. 代理模式(proxy pattern) 代理模式根据不用的职责和应用场景有很多种形式,例如远程代理,虚拟代理,保护代理,防火墙代理、智能引用代理、...

android面试题: 动态代理和代理模式。

1. 代理模式(proxy pattern)

代理模式根据不用的职责和应用场景有很多种形式,例如远程代理,虚拟代理,保护代理,防火墙代理、智能引用代理、缓存代理、同步代理、复杂隐藏代理、写入复制代理等。
但是它们都有一个共同的特点,为其它对象提供一种代理以控制对这个对象的访问。

1.1 和装饰者模式的区别

一般情况下,我们容易将装饰者模式和代理模式搞混。
- 装饰者模式:是在不使用继承,不改变原有对象的情况下增加或扩展对象行为,但是并不会禁用你对象的某个行为。
- 代理模式:而代理模式是控制这个对象的访问。

例如我们看看下面这个虚拟代理的例子:
当一个对象的创建开销很大,我们想要在真正使用到的时候才真正创建这个对象的时候,就可以用到虚拟代理。

interface image {
    public void displayimage();
}
class realimage implements image {
    private string filename;
    public realimage(string filename) { 
        this.filename = filename;
        loadimagefromdisk();
    }

    private void loadimagefromdisk() {
        system.out.println("loading   " + filename);
    }

    public void displayimage() { 
        system.out.println("displaying " + filename); 
    }
}
class proxyimage implements image {
    private string filename;
    private image image;

    public proxyimage(string filename) { 
        this.filename = filename; 
    }
    public void displayimage() {
        if(image == null)
              image = new realimage(filename);
        image.displayimage();
    }
}
class proxyexample {
    public static void main(string[] args) {
        image image1 = new proxyimage("hires_10mb_photo1");
        image image2 = new proxyimage("hires_10mb_photo2");     

        image1.displayimage(); // loading necessary
        image2.displayimage(); // loading necessary
    }
}

例如proxyimage,我们隐藏了realimage中的loadimagefromdisk()方法,创建proxyimage对象并不会立刻创建realimage对象,实现了对象的懒加载。

这和装饰者模式是有些出入的,它们的职责和目的不一样。装饰模式主要是强调对对象的功能扩展,而代理模式虽然也可以扩展对象的功能,但更偏向于对象的访问限制。

2. 动态代理

假设有这么一个person类。

public interface person {

    string getname();

    void setname(string name);

    string getgender();

    void setgender(string gender);

    int getage();

    void setage(int age);
}
public class personimpl implements person {
    private string name;
    private string gender;
    private int age;

    @override
    public string getname() {
        return name;
    }

    @override
    public void setname(string name) {
        this.name = name;
    }

    @override
    public string getgender() {
        return gender;
    }

    @override
    public void setgender(string gender) {
        this.gender = gender;
    }

    @override
    public int getage() {
        return age;
    }

    @override
    public void setage(int age) {
        this.age = age;
    }
}

如果我们想记录这个person对象的信息修改次数怎么办呢?
我们是不是可以弄一个装饰类扩展一下。

public class decorperson implements person {

    private personimpl person;
    private int modifiedtimes;

    public decorperson(personimpl person) {
        this.person = person;
    }

    @override
    public string getname() {
        return person.getname();
    }

    @override
    public void setname(string name) {
        modifiedtimes++;
        person.setname(name);
    }

    @override
    public string getgender() {
        return person.getgender();
    }

    @override
    public void setgender(string gender) {
        modifiedtimes++;
        person.setgender(gender);
    }

    @override
    public int getage() {
        return person.getage();
    }

    @override
    public void setage(int age) {
        modifiedtimes++;
        person.setage(age);
    }

    public int getmodifiedtimes() {
        return modifiedtimes;
    }
}

如果person类拥有非常非常多setxxx()方法,为每一个setter方法都增加代码会不会非常繁琐。或者当我们再给person类添加一个job属性时,这个decorperson是不是也要跟着修改呢。
所以java就提供了一个代理类给我们使用java.lang.reflect.proxy,可以通过proxy.newproxyinstance(classloader loader, class[] interfaces, invocationhandler h)这个方法创建代理对象。

首先我们需要实现一个invocationhandler对象。

public class personinvocationhandler implements invocationhandler {

    private person person;
    private int modifiedtimes;

    public personinvocationhandler(person person) {
        this.person = person;
    }

    public int getmodifiedtimes() {
        return modifiedtimes;
    }

    @override
    public object invoke(object proxy, method method, object[] args) throws throwable {
        if (method.getname().startswith("set")) {
            modifiedtimes++;
        }
        return method.invoke(person, args);
    }
}

紧接着我们通过proxy类创建person的代理对象。

public class main {

    public static void main(string[] args) {
    // write your code here
        personinvocationhandler handler = new personinvocationhandler(new personimpl());

        person person = (person) proxy.newproxyinstance(
                person.class.getclassloader(),
                new class[]{person.class},
                handler);

        person.setage(17);
        person.setname("aitsuki");
        person.setgender("male");
        person.setage(18)

        system.out.println(handler.getmodifiedtimes()); // 最终打印结果为 4
    }
}

当然,如果我们需要隐藏person的来历,我们可以提供一个工厂用来创建person,让用户感觉不到这其实是一个代理对象。

public class personfactory {

    public static person getperson() {
        personinvocationhandler handler = new personinvocationhandler(new personimpl());

        return (person) proxy.newproxyinstance(
                person.class.getclassloader(),
                new class[]{person.class},
                handler);
    }
}

上面演示了使用proxy类实现动态代理的方式。那么现在就来说说动态代理的概念:

动态代理,严格意义上来说指的并不是一种代理模式,而是一种反射的运用技术,它可以在程序运行时通过反射创建一个目标接口的代理对象,是实现代理模式的一种途径。
如果你非要说动态代理是一种设计模式,它反而更像是装饰者模式或监听器模式,因为它是监听目标对象的方法调用,实现对某个方法的扩展。从而提高程序的扩展性和灵活性,以及是java实现aop(面向切面)的一个重要技术。

那么相对于动态代理就出现了“静态代理”这个词了,指的就需要在级别中维护一个代理类,当然,“静态代理”也并不是指一种代理模式了。

既然说到了动态代理,那么再简单说说面向切面编程。

面向切面编程可以看作是对面向对象编程的扩展,“切面”,我们可以理解为“关注点”。
例如上面的person对象,它的主要关注点就是对象的操作,比如修改名字,性别等。而它的横切关注点则是日志记录,比如记录对象的修改次数。

但是如果直接在person的实现类中直接写入日志操作的相关代码,那么日志系统就和person产生了耦合,需要将日志记录这部分逻辑给单独的提取出来,而java中,动态代理则是面向切面编程的一个重要的实现手段。