当前位置: 移动技术网 > 移动技术>移动开发>Android > Android面试题: 动态代理和代理模式

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

2019年01月06日  | 移动技术网移动技术  | 我要评论

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中,动态代理则是面向切面编程的一个重要的实现手段。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网