该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
代理模式:
控制对象访问;
你是一个白脸,提供很好且很友善的服务,但是你不希望每个人都叫你做事,所以找了黑脸控制对你的访问;
这就是代理要做的:控制和管理访问;
糖果机监视器:
我们上一章实现了糖果机,可以取得糖果数量-getCount()方法,取得糖果机状态-getState()方法;
我们想要为糖果机加上一个位置字段:我们依然在初始化过程中为其赋初值,并提供相应的get方法;
糖果监视器:
我们新建一个类:GumballMonitor,以便取得机器的位置、糖果的库存及当前机器的状态;
我们糖果机的修改,及糖果监视器:
GumballMachine的修改:
String location;
public GumballMachine(String location, int numberGumballs){
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
this.location = location;
….
}
State getLocation(){
return getLocation;
}
糖果监视器:
测试输出:
现在的问题是:
监视器和糖果机在同一个JVM上面执行,但如果需要其他地方的监视器,监测到不同JVM上运行的糖果机,就需要交给监视器一个远程糖果机对象的代理;
所谓的代理(proxy),就是代表某个真实的对象;
在这个例子中,代理就像是糖果机一样,但其实幕后是它利用网络和一个远程的真实糖果机沟通;
只需要将GumballMachine代理版本的引用交给监视器就可以了;
这个代理假装它是真正的对象,但是其实一切的动作是它利用网络和真正的对象沟通;
远程代理:
远程代理就好比“远程对象的本地代表”;
远程对象是在其他JVM堆中,本地代表则是一种由本地方法调用的对象,其行为会转发到远程对象中;
你的客户对象所做的就像是在做远程方法调用,但其实只是调用本地堆中的“代理”对象上的方法,再由代理处理所有网络通信的底层细节;
类似的场景,通过RMI可以让我们找到JVM内的对象,并允许我们调用他们的方法,实现Java远程方法调用:
客户对象调用客户辅助对象的某方法doSomething();
客户辅助对象打包调用信息(变量、方法名称等),然后通过网络将他们运送给服务辅助对象;
服务辅助对象吧来自客户辅助对象的信息解包,找到被调用的方法,然后调用服务对象执行真正的方法;
服务对象上的方法被调用,将结果返回给服务辅助对象;
服务辅助对象将调用的返回信息打包,然后通过网络运回给客户辅助对象;
客户辅助对象将返回值解包,返回给客户对象;
对客户来说,这些步骤都是透明的;
RMI将客户辅助对象称为Stub(桩),服务辅助对象称为skeleton(骨架);
实现远程代理:
查看java.rmi.server.UnicastRemoteObject文档;
定义代理模式:
我们说了半天远程代理,尽管如此,远程代理只是一般代理的一种实现;
代理模式的定义:
代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问;
在上面的例子中,代理之所以需要控制访问,是因为我们的客户不知道如何和远程对象沟通,远程代理控制访问,好帮我们处理网络上的细节;
几种代理控制访问的方式:
远程代理控制访问远程对象;
虚拟代理控制访问创建开销大的资源;
保护代理基于权限控制对资源的访问;
类图:
Subject接口提供方法,RealSubject和Proxy实现同一接口,Proxy在RealSubject真正出现的地方取代他;
虚拟代理(Virtual Proxy):
虚拟代理作为创建开销大的对象的代表;经常直到我们真正需要一个对象的时候采取创建它;
当对象在创建前和创建中时,由虚拟代理来扮演对象的替身;
对象创建后,代理就会将请求直接委托给对象;
在加载大的图片的过程中,经常使用此种代理;
小结:
与装饰者模式的比较:
ImageProxy对ImageIcon进行了包装,看起来好像Decorator(装饰者),但两者目的不一样;
装饰者为对象增加行为,而代理是控制对象的访问;
使用代理的方式:
通常提供一个工厂,实例化并返回主题,主题可以用代理包装主题再返回;这样客户就不知道他们使用的是代理还是真东西;
除了现有的几种代理模式,还有一种缓存代理,可以维护之前的对象,在可能的情况下使用缓存对象;
与适配器模式的比较:
代理和适配器虽然都是当前实际工作的类前面,但是适配器会改变对象适配的接口,而代理则实现相同的接口;
保护代理(动态代理):
它可以根据客户的角色来决定是否允许客户访问特定方法;
值得一提的是:
在即将介绍的保护代理之前,我们已经介绍的远程代理和虚拟代理实质都符合我们之前代理模式的类图,符合该类图的代理模式我们称之为 “静态代理”;既然有静态代理,相应的就一定有“动态代理”;保护代理可以使用动态代理模式实现;
动态代理模式类图:
实现保护代理:
Java 的java.lang.relect提供了对该动态代理的支持,利用它可以在运行时动态地创建一个代理类,实现一个或多个接口;
并将方法的调用转发到你所指的类,之所以叫动态代理,就是因为实际的代理类是在运行时创建的;
示例场景:
对象间的约会服务系统,加入Hot喜欢标签,Not不喜欢标签;
我们通过Person bean获取一个人的信息;(定义了个人信息相关的接口)
系统不应该允许用户篡改别人的信息,我们需要定义使用PersonBean的方式,让不同的用户有不同的权限;
因此,这是一个使用保护代理的绝佳例子;
保护代理是一种根据访问权限决定客户可否访问对象的代理;
在示例场景中,我们希望用户自己可以设置自己的信息,同时防止别人更改这些信息;
实现步骤:
1.创建两个InvocationHandler:OwnerInvocationHandler和NonOwnerInvocationHandler;
OwnerInvocationHandler :当用户看自己的bean时;
NonOwnerInvocationHandler:当用户看另一个人的bean时;
2.创建动态代理:
用户不可以修改自己的HotOrNot评分,也不可以修改其他人的个人信息;
因此需要两个代理:一个访问自己的PersonBean对象,一个访问另一个人的Person对象;以控制各种情况的请求;
创建动态代理,我们需要使用Java API的动态代理:
Java会为我们创建两个代理,我们只需要提供handle来处理代理转来的方法;
当代理方法被调用时,代理会把调用转发给InvocationHandler,但这并不是通过调用InvocationHandler方法做到的;
实际上,不管代理调用何种方法,处理器都会被调用invoke()方法;
Proxy本身是利用静态的Proxy.newProxyInstance()方法在运行时动态创建的;
具体的实现代码如下:
【PersonBean.java】
public interface PersonBean{
String getName();
String getGender();
String getIntrests();
int getHotOrNotRating();
void setName(String name);
void setGender(String gender);
void setIntersts(String interests);
void setHotOrNotRating(int rating);
}
【PersonBeanImpl.java】
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 getIntrests(){
return interests;
}
public int getHotOrNotRating(){
if (ratingCount == 0){
return 0;
}else{
return (rating/ratingCount);
}
}
public void setName(String name){
this.name = name;
}
public void setGender(String gender){
this.gender = gender;
}
public void setIntersts(String interests){
this.interests = interests;
}
public void setHotOrNotRating(int rating){
this.rating += rating;
ratingCount++;
}
}
【OwnerInvocationHandler.java】
import java.lang.reflect.*;
public class OwnerInvocationHandler implements InvocationHandler{
PersonBean person;
public OwnerInvocationHandler(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")){
throw new IllegalAccessException();
}else if(method.getName().startsWith("set")){
return method.invoke(person, args);
}
}catch (InvocationTargetException e){
e.printStackTrace();
}
return null;
}
}
【NonOwnerInvocationHandler.java】
import java.lang.reflect.*;
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;
}
}
【MatchMakingTestDrive.java】
import java.lang.reflect.*;
public class MatchMakingTestDrive{
public static void main(String[] args){
MatchMakingTestDrive test = new MatchMakingTestDrive();
test.drive();
}
public MatchMakingTestDrive(){
}
public void drive(){
//own
PersonBean joe = new PersonBeanImpl();
joe.setName("Joe");
PersonBean ownProxy = getOwnerProxy(joe);
System.out.println("Name is " + ownProxy.getName());
ownProxy.setIntersts("football");
System.out.println("Intersts is " + ownProxy.getIntrests());
try{
ownProxy.setHotOrNotRating(10);
}catch (Exception e){
System.out.println("Can`t set rating!");
}
System.out.println("Rating is " + ownProxy.getHotOrNotRating());
//non own
PersonBean mark = new PersonBeanImpl();
mark.setName("Mark");
PersonBean nonownProxy = getNonOwnerProxy(mark);
System.out.println("Name is " + nonownProxy.getName());
nonownProxy.setHotOrNotRating(20);
System.out.println("Rating is " + nonownProxy.getHotOrNotRating());
try{
nonownProxy.setIntersts("football");
}catch (Exception e){
System.out.println("Can`t set other info!");
}
System.out.println("Rating is " + nonownProxy.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)
);
}
}
测试运行:
总结:
1.代理模式为另一个对象提供代表,以便控制客户对对象的访问,管理访问的方式有许多种;
2.远程代理管理客户和远程对象之间的交互;
3.虚拟代理控制访问实例化开销大的对象;
4.保护代理基于调用者 控制对 对象方法的访问;
5.代理模式有许多变体,如缓存代理、同步代理、防火墙代理、写时复制代理等;
6.代理在结构上类似装饰者,但是目的不同;装饰者为对象加行为,代理则是访问控制;
7.Java内置的代理支持,可以根据需求建立动态代理,并将所有的调用分配给所选的处理器;
8.和其他包装着一样,代理会造成你的设计中类的数量增加;
OO基础:
抽象;
封装
继承;
多态;
OO原则:
封装变化
多用组合,少用继承
针对接口编程,不针对实现编程
为交互对象之间的松耦合设计而努力;
类应该对扩展开放,对修改关闭;
依赖抽象,不要依赖具体类;
只和朋友交谈(最少知识原则);
别找我,我会找你(好莱坞原则:由超类主控一切,需要的时候自然会去调用子类);
类应该只有一个改变的理由(单一职责原则);
OO模式:
策略模式:定义算法族,分别封装起来,让他们之间互相替换,此模式让算法的变化独立于使用算法的客户;
观察者模式:在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新;
装饰者模式:动态地将责任附加到对象上;想要扩展功能,装饰者提供有别于继承的另一种选择;
简单工厂模式;
工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个;工厂方法让类把实例化推迟到子类;
抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确具体的类;
单件模式:确保一个类只有一个实例,并提供全局访问点;
命令模式:将请求封装成对象,这可以让你使用不同的请求,队列或者日志请求来参数化其他对象;命令模式也支持撤销操作;
适配器模式:将一个类的接口转换成客户期待的另一个接口,适配器让原来不兼容的类可以合作无间;
外观模式:提供了一个统一的接口,用来访问子系统中的一群接口;外观模式定义了高层接口,让子系统更容易使用;
模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中;模板方法使得子类可以在不改变算法结构的情况下,重新定义/捕获算法中的某些步骤;
迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示;
组合模式:允许你将对象组成树形结构来表现整体/部分的层次结构;组合能让客户以一致的方式处理个别对象和对象组合;
状态模式:允许对象在内部状态改变时改变他的行为,对象看起来好像修改了它的类;
——代理模式:为另一个对象提供一个替身或占位符以访问这个对象;