关键字:设计模式,代理模式,proxy,保护代理,虚拟代理,远程代理,缓冲代理,智能引用代理
代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
说白了,就是当你不能直接访问一个对象时,通过一个代理对象来间接访问,这种方式就叫做代理模式。
代理模式是一种比较常见的结构型设计模式,按照不同的场景,又可以分为保护代理,远程代理,虚拟代理,缓冲代理和智能引用代理。
通过上面的定义介绍,我想我们对代理模式已经有了初步的认识,心中已经有了一种架构图的出现:
Client无法直接访问Real,这时出现了一个Proxy类继承自Real,Client可以直接访问Proxy来使用Real的资源。看上去是这样,但是Proxy和Real不应该是继承的关系。
为什么Proxy不能简单的继承Real来达到代理模式的设计?
来看一下上面介绍过的代理模式的应用场景,我们希望通过代理模式的架构来给原对象创建一个代理类Proxy,它可以为原对象提供不同的访问权限,可以继承一部分原对象开放出来的接口,可以为原对象增添新功能,可以只是一个原对象的创建方法,作为原对象的一个过度,而这些,不能局限于一个基于强连接的直接继承关系。下面我用一段代码来说明Proxy不能简单继承Real的原因
package pattern.proxy;
public class Real {
private long id;
private String password;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
package pattern.proxy;
public class Proxy extends Real {
}
package pattern.proxy;
import org.junit.Test;
public class Client {
private Proxy proxy = null;
@Test
public void testSimpleProxy() {
proxy = new Proxy();
proxy.getId();
proxy.getPassword();// 问题出现了
}
}
发现了没有,如果是保护代理的话,我们不希望Real类中的password属性被透明访问,只有拥有该权限的客户端才可以访问,其他都无法访问password字段内容。所以,我们需要一个更加灵活的Proxy和Real之间的关系,这个关系一定不是强继承的关系,经过这一番思考,我们发现了代理模式中的核心类:抽象代理类
现在来区分代理模式的角色:
下面来看代码:
Real类改为继承Proxy抽象类,其他不变,Proxy抽象类改为
package pattern.proxy;
public abstract class Proxy {
public abstract long getId();
public abstract String getPassword();
}
增加一个同样继承于Proxy类的具体实现类,这里通过与Real建立组合关系将Real的对象作为SuperProxy的成员属性,为了满足多线程要求,这里采用了饿汉单例模式。
package pattern.proxy;
public class SuperProxy extends Proxy {
private static Real real = new Real();// 直接采用饿汉单例
/**
* 要想操作Real,要先执行具体Proxy类中的一些其他方法,或许是创建Real对象,也或许是准备数据。
*/
static {
real.setId(12312l);
real.setPassword("dontknow");
}
@Override
public long getId() {
return real.getId();
}
@Override
public String getPassword() {
return real.getPassword();
}
}
客户端调用方法:
package pattern.proxy;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
public class Client {
private final static Logger logger = LogManager.getLogger();
private Proxy proxy = null;
@Test
public void testSimpleProxy() {
proxy = new SuperProxy();
long id = proxy.getId();
String pwd = proxy.getPassword();
logger.info("id:" + id + " password:" + pwd);
}
}
输出:20:35:10[testSimpleProxy][main]: id:12312 password:dontknow
业务需求:Client在访问Real的时候,要先进行身份验证并且记录访问次数。而同时,不可对Real内部进行修改。
这种情况下,我们可以通过代理模式增加代理类来实现,下面看代码:
首先定义一个代理抽象基类,加入共有抽象方法(此方法一定是已存在与RealSearcher的,因为RealSearcher内部不可改变)
package pattern.proxy.search;
public abstract class Searcher {
public abstract String doSearch(String username, int sid);
}
将原RealSearcher改为继承于代理代理抽象基类,其他不必做任何修改。
package pattern.proxy.search;
import java.util.HashMap;
import java.util.Map;
public class RealSearcher extends Searcher {
private Map<Integer, String> data = new HashMap<Integer, String>();
RealSearcher() {// 模仿数据源,对象构造时初始化数据
data.put(1001, "fridge");
data.put(1002, "book");
data.put(1003, "macrowave oven");
}
public String doSearch(String username, int sid) {
return data.get(sid);
}
}
此时,创建一个新的具体代理类,加入用户校验和访问次数功能。
package pattern.proxy.search;
public class ProxySearcher extends Searcher {
private Searcher searcher = new RealSearcher();
private int count;
public String doSearch(String username, int sid) {
if (validateUser(username)) {
count++;
return "times: " + count + " " + searcher.doSearch(username, sid);
}
return "Identity discrepancy";
}
private boolean validateUser(String username) {
if ("jhon".equals(username))
return true;
return false;
}
}
最后是客户端调用的方式,因为具体代理类和真实对象都是继承于代理抽象基类,因此可以创建抽象基类的不同子类的实例,同时他们都拥有原属于真实对象的查询方法。
package pattern.proxy.search;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
public class Client {
private final static Logger logger = LogManager.getLogger();
@Test
public void testSearcher() {
Searcher searcher = new ProxySearcher();// 创建一个代理类对象,而不是RealSearcher
logger.info(searcher.doSearch("jhon", 1002));
logger.info(searcher.doSearch("jhon", 1001));
logger.info(searcher.doSearch("jack", 1002));
logger.info(searcher.doSearch("java", 1003));
}
}
输出结果:
11:06:03[testSearcher][main]: times: 1 book
11:06:03[testSearcher][main]: times: 2 fridge
11:06:03[testSearcher][main]: Identity discrepancy
11:06:03[testSearcher][main]: Identity discrepancy
通过结果可以看出,我们只改变了真实查找类的继承关系即可实现增加用户验证和访问次数的功能。
我们所有的代理模型都可以采用上面给出的类图结构,上面的代码实例介绍了保护代理和智能引用代理。下面再介绍一下其他的代理场景。
代理模式是非常常用的结构型设计模式,尤其是我们前面介绍过的保护代理,远程代理,虚拟代理,缓冲代理以及智能引用代理,本文介绍了他们的主旨思想,给出了代理模式的核心架构,解释了代理模式的原理,未来在其他内容的研究过程中,会碰到真实场景中的代理模式的应用,我会深入介绍。