十三:状态模式
状态模式允许对象在内部状态改变它的行为,对象看起来好像修改了它的类。
意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
何时使用:代码中包含大量与对象状态有关的条件语句。
如何解决:将各种具体的状态类抽象出来。
关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if...else 等条件选择语句。
应用实例: 1、打篮球的时候运动员可以有正常状态、不正常状态和超常状态。 2、曾侯乙编钟中,'钟是抽象接口','钟A'等是具体状态,'曾侯乙编钟'是具体环境(Context)。
优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。
注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。
实现:
首先创建状态接口:
/*
* 状态接口
* @author zzf
* @date 2018年10月26日 下午4:16:18
*/
public interface State {
public void doAction(Context context);
}
启动状态:
/*
* 启动状态
* @author zzf
* @date 2018年10月26日 下午4:17:17
*/
public class StartState implements State {
public void doAction(Context context) {
System.out.println("启动状态");
context.setState(this);
}
public String toString() {
return "Start State";
}
}
停止状态:
/*
* @author zzf
* @date 2018年10月26日 下午4:17:36
*/
public class StopState implements State {
public void doAction(Context context) {
System.out.println("停止状态");
context.setState(this);
}
public String toString() {
return "Stop State";
}
}
通过Context类进行测试:
/*
* @author zzf
* @date 2018年10月26日 下午4:16:56
*/
public class Context {
private State state;
public Context() {
state = null;
}
public void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
public static void main(String[] args) {
Context context = new Context();
State startState = new StartState();
startState.doAction(context);
System.out.println(context.getState().toString());
State stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}
通过context.setState(this);将State类实例注入到Context的State实例中,最后调用getState()获取到的就是该实例数据。(与组合模式及其相似)
要点:
1、状态模式允许一个对象基于内部状态而拥有不同的行为。
2、状态模式用类代表状态。
3、Context会将行为委托给当前状态对象。
4、通过将每个状态封装进一个类,我们把以后需要做的任何改变局部化了。
5、状态模式和策略模式有相同的类图,但是他们意图不同。
6、状态模式通常会用行为或算法配置Context类。
7、状态模式允许Context随着状态改变而改变行为。
8、状态转换可以由State类或Context类控制。
9、使用状态模式通常会导致设计中类的数目大量增加。
10、状态类可以被多个Context实例共享。
十四:代理模式
代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。
使用代理模式创建代表对象,让代表对象控制某对象的访问,被代理的对象可以是远程的对象,创建开销大的对象或需要安全控制的对象。
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。
应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。
注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
实现:
静态代理:
首先创建接口:
/*
* 图片
* @author zzf
* @date 2018年10月29日 下午5:50:02
*/
public interface Image {
void display();
}
然后创建实现类:
/*
* @author zzf
* @date 2018年10月29日 下午5:50:23
*/
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
System.out.println("显示图片名:" + fileName);
}
}
代理类:
/*
* @author zzf
* @date 2018年10月29日 下午5:51:19
*/
public class ProxyImage implements Image {
private Image realImage;
public ProxyImage(Image image) {
this.realImage = image;
}
@Override
public void display() {
System.out.print("代理类:");
realImage.display();
}
}
测试类:
/*
* @author zzf
* @date 2018年10月29日 下午5:51:57
*/
public class ProxyPatternDemo {
public static void main(String[] args) {
new RealImage("图片").display();
Image image = new ProxyImage(new RealImage("图片"));
System.out.println("-----------");
image.display();
}
}
动态代理:
/*
* @author zzf
* @date 2018年10月29日 下午6:23:38
*/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicProxyHandler implements InvocationHandler {
private Object object;
public DynamicProxyHandler(final Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("显示图片前:");
Object result = method.invoke(object, args);
System.out.println("显示图片后。");
return result;
}
}
测试类:
/*
* @author zzf
* @date 2018年10月29日 下午6:24:10
*/
import java.lang.reflect.Proxy;
public class ImageProxyTest {
public static void main(String[] args) {
Image image = new RealImage("图片");
Image images = (Image) Proxy.newProxyInstance(Image.class.getClassLoader(), new
Class[]{Image.class}, new DynamicProxyHandler(image));
images.display();
}
}
CGLIB代理:
需要引入cglib的jar包:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>
图片类:
/*
* 图片
* @author zzf
* @date 2018年10月29日 下午5:50:02
*/
public class RealImage {
public void display(String fileName){
System.out.println("显示图片名:" + fileName);
}
}
代理类:
/*
* @author zzf
* @date 2018年10月30日 上午9:28:52
*/
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ImageProxy implements MethodInterceptor {
// 通过Enhancer 创建代理对象
private Enhancer enhancer = new Enhancer();
// 通过Class对象获取代理对象
public Object getProxy(Class c) {
// 设置创建子类的类
enhancer.setSuperclass(c);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object obj, Method m, Object[] args, MethodProxy proxy) throws Throwable {
// TODO Auto-generated method stub
System.out.print("代理开始前,参数为:");
for (Object objs : args) {
System.out.print(objs);
}
System.out.println();
// 代理类调用父类的方法
proxy.invokeSuper(obj, args);
System.out.println("代理后");
return null;
}
}
测试类:
/*
* @author zzf
* @date 2018年10月30日 上午9:31:35
*/
public class CglibProxyTest {
public static void main(String[] args) {
//创建我们的代理类
ImageProxy Proxy = new ImageProxy();
RealImage image = (RealImage)Proxy.getProxy(RealImage.class);
image.display("图片名");
}
}
要点:
1、代理模式为另一个对象提供代表,以便控制客户对对象的访问,关闭访问的方式有许多种。
2、远程代理管理客户和远程对象之间的交互。
3、虚拟代理控制访问实例化开销大的对象。
4、保护代理基于调用者控制对对象方法的访问。
5、代理模式有许多变体,例如:缓存代理、同步代理、防火墙代理和写入时复制代理。
6、代理在结构上类似装饰者,但是目的不同。
7、装饰者模式为对象加上行为,而代理则是控制访问。
8、Java内置的代理支持,可以根据需要建立动态代理,并将所有调用分配到所选的处理器。
9、和其他的包装者一样,代理会造成设计中类的数目增加。