设计模式中的设计思想、图片和部分代码参考自《Head First设计模式》,作者Eric Freeman & Elisabeth Freeman & Kathy Siezza & Bert Bates。
在这里我只是对这本书进行学习阅读,并向大家分享一些心得体会。
Java在java.lang.reflect包中有自己的代理支持,利用这个包你可以在运行时动态地创建一个代理,实现一个或多个接口,并将方法地调用转发到我们所指定地类。因为实际地代理类是在运行时创建的,我们称这个Java技术为:动态代理。
我们要利用Java地动态代理创建我们下一个代理实现(保护代理)。但在这之前,先让我们看一下类图,了解一下动态代理是怎么一回事。就和真实世界中大多数的事物一样,它和代理模式的传统意义有一点出入。
因为Proxy是Java自动帮我们创建的,所以我们需要想办法来告诉Proxy类,我需要做什么。我们不能像以前一样把代码放在Proxy类在,因为Proxy不是我们直接实现的。既然代码不能放在Proxy中,那么应该放在那里?代码需要放在InvocationHandler中。InvocationHandler的工作是响应代理的任何调用。我们可以把InvocationHandler想成是代理受到方法调用后,请求做实际工作的对象。
某约会网站需要让我们帮他们制作约会后的评分系统。可以为约会对象打分。
玩家信息超类-PersonBean:
//用户信息接口
public interface PersonBean {
//名称
String getName();
//性别
String getGender();
//爱好
String getInterests();
//平均评分
int getHotOrNotRating();
void setName(String name);
void setGender(String gender);
void setInterests(String interests);
void setHotOrNotRating(int rating);
}
玩家信息实现类-PersonBeanImpl:
public class PersonBeanImpl implements PersonBean {
String name;
String gender;
String interests;
//总评分
int rating;
//被评价次数
int ratingCount = 0;
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public String getInterests() {
return interests;
}
//获得平均评分
public int getHotOrNotRating() {
if (ratingCount == 0) return 0;
return (rating/ratingCount);
}
public void setName(String name) {
this.name = name;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setInterests(String interests) {
this.interests = interests;
}
//更新总评分和被评价次数
public void setHotOrNotRating(int rating) {
this.rating += rating;
ratingCount++;
}
}
这个评分看似解决了为我们的需求,但是有一个缺陷。就是被评价者可以随意修改自己的HotOrNotRating值,甚至是修改其他人的性别、名字,这样很不合理。所以我们需要为PersonBean施加保护。
所以这时候就需要用保护代理,什么是保护代理?这是一种根据访问权限决定客户可否访问对象的代理。比方说如果某公司有个项目后台管理系统。那么这个系统只允许普通员工有查权限,而经理可以有部分模块的写权限和所有模块的读权限,而CEO则拥有所有模块的读写权限。
同理在这个约会评分系统中,我们希望被评价者拥有对自己信息中“名称”、“性别”、“爱好”,拥有读写权限,但是对自己的HotOrNotRating值只有读权限。同时我们希望评价者,对他人的信息中的HotOrNotRating值拥有读写权限,但是对他人的“名称”、“性别”、“爱好”三个信息只有读权限。
下面用娱乐圈的一张图来描述一下保护代理:
首先我们要明确一个需求的核心,就是:被评价者不可以改变自己的HotOrNotRating值,也不可以改变他人的“名称”、“性别”、“爱好”三个信息。
要解决这个问题,我们必须创建两个代理:一个是访问自己的PersonBean对象,另一个是访问他人的PersonBean对象。这样代理就可以分情况的对PersonBean进行保护。也就是说我们需要创建两个InvocationHandler。
创建这种代理,我们必须使用Java API的动态代理。Java会帮助我们创建两个代理,我们只需要提供handler来处理代理转来的方法。
步骤1:
创建两个InvocationHandler。InvocationHandler实现了代理行为,正如我们所看到的,Java负责创建真实代理类和对象,我们只需要提供在方法调用发生时知道做什么的handler。
步骤2:
书写代码产生代理类,并实例化它。
步骤3:
利用适当的代理包装任何PersonBean对象,当我们需要使用PersonBean对象时,使用方无非就是:被评价者(称为拥有者),或者是评价者(称为非拥有者),无论是哪一方,我们都为PersonBean创建适合的代理。
步骤1:创建InvocationHandler
我们需要书写两个InvocationHandler(调用处理器),其中一个给拥有者使用,另一个给非拥有者使用。
InvocationHandler的工作原理:当代理的方法被调用时,代理就会把这个调用转发给InvocationHandler,但是这并不是通过调用InvocationHandler的相应方法做到的。那么是如何做到的?我们可以先看下InvocationHandler的API文档:
整个InvocationHandler接口中,只有invoke()一个方法,不管代理被调用的是何种方法,InvocationHandler中被调用都是invoke方法(注:invoke方法中有个参数为method可用于区分代理中的不同方法)。
工作原理:
当proxy调用invoke()时,InvocationHandler是如何做保护的(或者说是如何做权限控制的) ,通常我们会先检查该方法是否是来自proxy,并基于该方法的名称和变量做决定。
被评价者的InvocationHandler-OwnerInvocationHandler:
public class OwnerInvocationHandler implements InvocationHandler {
//持有RealSubject的引用(这样底层就可以调用RealSubject的方法)
PersonBean person;
public OwnerInvocationHandler(PersonBean person) {
this.person = person;
}
//proxy的任何代码被调用,就会导致invoke方法被调用
public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {
try {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().equals("setHotOrNotRating")) {
//不允许被评价者调用setHotOrNotRating方法,会抛出异常
throw new IllegalAccessException();
} else if (method.getName().startsWith("set")) {
return method.invoke(person, args);
}
} catch (InvocationTargetException e) {
//捕捉真正主题抛出的异常
e.printStackTrace();
}
//如果调用的是其他的方法,这里不予理会
return null;
}
}
评价者的InvocationHandler-NonOwnerInvocationHandler:
//本类和OwnerInvocationHandler同理
public class NonOwnerInvocationHandler implements InvocationHandler {
PersonBean person;
public NonOwnerInvocationHandler(PersonBean person) {
this.person = person;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {
try {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().equals("setHotOrNotRating")) {
return method.invoke(person, args);
} else if (method.getName().startsWith("set")) {
//不允许评价者设置被评价者的“名称”、“性别”、“爱好”三个信息
throw new IllegalAccessException();
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
步骤2:创建Proxy类并实例化Proxy对象:
需要编写一个以PersonBean为参数,并知道如何为PersonBean对象创建拥有者代理的方法。也就是说我们要创建一个代理,把它的方法调用转发给OwnerInvocationHandler:
注:
对于方法PersonBean getOwnerProxy(PersonBean person),它的形参和返回类型都是PersonBean,但是形参中的PersonBean是RealSubject(真正的主题,被代理者);返回类型的PersonBean是Proxy(代理者),但是由于被代理者和代理者实现了相同的接口,所以导致方法的形参和返回值相同,但是它们并不是一类对象。
测试代码:
public class MatchMakingTestDrive {
//装载PersonBean的容器
HashMap datingDB = new HashMap();
public static void main(String[] args) {
MatchMakingTestDrive test = new MatchMakingTestDrive();
test.drive();
}
public MatchMakingTestDrive() {
//初始化datingDB
initializeDatabase();
}
public void drive() {
//取得PersonBean
PersonBean joe = getPersonFromDatabase("Joe Javabean");
//创建出PersonBean的"被评分者代理"对象
PersonBean ownerProxy = getOwnerProxy(joe);
System.out.println("Name is " + ownerProxy.getName());
//被评分者可以设置“名称”、“性别”、“爱好”三个信息
ownerProxy.setInterests("bowling, Go");
System.out.println("Interests set from owner proxy");
try {
//被评分者不能设置HotOrNotRating值
ownerProxy.setHotOrNotRating(10);
} catch (Exception e) {
System.out.println("Can't set rating from owner proxy");
}
System.out.println("Rating is " + ownerProxy.getHotOrNotRating());
//创建出PersonBean的"评分者代理"对象
PersonBean nonOwnerProxy = getNonOwnerProxy(joe);
System.out.println("Name is " + nonOwnerProxy.getName());
try {
//评分者不可以设置“名称”、“性别”、“爱好”三个信息
nonOwnerProxy.setInterests("bowling, Go");
} catch (Exception e) {
System.out.println("Can't set interests from non owner proxy");
}
//评分者可以设置HotOrNotRating值
nonOwnerProxy.setHotOrNotRating(3);
System.out.println("Rating set from non owner proxy");
System.out.println("Rating is " + nonOwnerProxy.getHotOrNotRating());
}
//返回"被评分者的代理"对象
PersonBean getOwnerProxy(PersonBean person) {
return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new OwnerInvocationHandler(person));
}
//返回"评分者的代理"对象
PersonBean getNonOwnerProxy(PersonBean person) {
return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new NonOwnerInvocationHandler(person));
}
//根据名称从容器中获得PersonBean
PersonBean getPersonFromDatabase(String name) {
return (PersonBean)datingDB.get(name);
}
void initializeDatabase() {
PersonBean joe = new PersonBeanImpl();
joe.setName("Joe Javabean");
joe.setInterests("cars, computers, music");
joe.setHotOrNotRating(7);
datingDB.put(joe.getName(), joe);
PersonBean kelly = new PersonBeanImpl();
kelly.setName("Kelly Klosure");
kelly.setInterests("ebay, movies, music");
kelly.setHotOrNotRating(6);
datingDB.put(kelly.getName(), kelly);
}
}
输出结果:
Name is Joe Javabean
Interests set from owner proxy
Can't set rating from owner proxy
Rating is 7
Name is Joe Javabean
Can't set interests from non owner proxy
Rating set from non owner proxy
Rating is 5
问题的根源是,被评价者和评价者都可以对PersonBean(RealSubject)进行修改,但是为了做到让PersonBean对被评分者的setHotOrNotRating屏蔽,同时做到让PersonBean对评分者的基本信息设置的屏蔽,我们将PersonBean保护起来(权限控制),所以我们需要创建两种代理,这两种代理分别放开了不同的权限,PersonBean的操作者无非就是被评价者和评价者,根据操作者的不同,我们分配给它不同的代理。对于Java的动态代理,Proxy是自动创建的,但是需要我们书写InvocationHandler来做具体的访问权限控制,从而做到对PersonBean保护。
至此,历时三个月看完了《HeadFirst设计模式》这本书,但是我觉得这只是一个设计模式的入门,未来还要在工作学习中进行深入学习。本书的常规设计模式章节,有的地方理解还不是很完全、附录部分的设计模式还没有去看。这些知识点需要在日后进行补充和跟进,但是总体来说,已经对设计模式有了初步的认识,感谢《HeadFirst设计模式》这本书的作者们。