设计模式代理模式-模板方法模式-命令模式
- 代理模式
- 基本介绍:
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象,这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的独享
- 代理模式有不同的三种形式:静态代理、动态代理(jdk代理、接口代理)、Cglib代理(可以在内存中动态的创建代理而不需要实现接口,它是属于动态代理的范畴)
- 静态代理
-
基本介绍:静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者继承相同的父类。
-
代码
public interface ITeacherDao { public void teach(); } public class TeacherDao implements ITeacherDao { public void teach() { System.out.println("老师授课中。。。"); } } public class TeacherDaoProxy implements ITeacherDao{ private ITeacherDao target;//目标对象通过接口来聚合 public TeacherDaoProxy(ITeacherDao target) { this.target = target; } @Override public void teach() { System.out.println("代理开始"); target.teach(); System.out.println("代理结束"); } } public class Client { public static void main(String[] args) { new TeacherDaoProxy(new TeacherDao()).teach(); } }
-
静态代理的优缺点
- 优点:在不修改目标对象的功能前提下,通过代理对象对目标功能扩展
- 缺点:因为代理目标需要与目标对象实现一样的接口,所以会有很多代理类
- 一旦接口增加方法,目标对象与代理对象都要维护。
-
- 动态代理
-
基本介绍
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用jdk的api,动态的在内存中构建对象
- 动态代理也叫做:jdk代理、接口代理
-
代码
public interface ITeacherDao { public void teach(); } public class TeacherDao implements ITeacherDao { public void teach() { System.out.println("老师授课中。。。"); } } import java.lang.reflect.InvocationHandler; public class ProxyFactory { public Object target; //构建构造器的时候对目标对象进行初始化 public ProxyFactory(Object target) { this.target = target; } //get目标对象生成一个代理对象 public Object getProxyInstance() { /* * loader 指定当前目标对象使用的类加载器 * interfaces 目标对象实现的接口类型使用泛型的方式确认类型 * h 事件处理执行目标对象的方法时,会触发事件处理器方法 */ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("JDK代理开始"); //通过反射机制调用目标对象的代理方法 Object returnVal = method.invoke(target, args); System.out.println("JDK代理提交"); return returnVal; } }); } } public class Client { public static void main(String[] args) { ITeacherDao object=(ITeacherDao) new ProxyFactory(new TeacherDao()).getProxyInstance(); //内存中生成了代理对象 System.out.println(object.getClass()); object.teach(); } }
-
- Cglib代理
-
基本介绍
- 静态代理和jdk代理模式都要求目标对象是实现一个接口,但是目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib代理。
- Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib代理叫做动态代理。
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口,它广泛的许多aop框架使用,例如spring aop,实现方法拦截。
- 在aop编程中如何选择代理模式:
- 目标对象需要实现接口,用jdk代理
- 目标对象不需要实现接口,用Cglib代理
- Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
-
代码
public class TeacherDao { public void teach() { System.out.println("老师授课中。。。我是cglib代理不需要实现接口"); } } import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class ProxyFactory implements MethodInterceptor{ private Object target; public ProxyFactory(Object target) { this.target = target; } //返回一个代理对象是target对象的代理对象 public Object getProxyInstance() { //1、创建一个工具类 Enhancer enhancer=new Enhancer(); //2、设置父类 enhancer.setSuperclass(target.getClass()); //3、设置回调函数 enhancer.setCallback(this); //4、创建子类对象即代理对象 return enhancer.create(); } //会调用目标对象的方法 @Override public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable { System.out.println("cglib代理开始"); Object interVal = method.invoke(target, args); System.out.println("cglib代理结束"); return interVal; } } public class Client { public static void main(String[] args) { TeacherDao object=(TeacherDao) new ProxyFactory(new TeacherDao()).getProxyInstance(); object.teach(); } }
-
代理模式的几种变体
- 防火墙代理:内网通过代理穿透防火墙,实现对公网的访问。
- 缓存代理:比如:当请求图片文件资源时,先到缓存代理取,如果取到资源则ok,如果娶不到资源,再到公网或者数据库取,然后缓存。
- 远程代理:远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息
- 同步代理:主要使用在多线程编程中,完成多线程间同步工作
-
- 基本介绍:
- 模板方法模式
-
基本介绍:
- 模板方法模式:又叫模板模式,在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
- 简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤。
- 这中类型的设计模式属于行为型模式。
-
代码
//抽象类表示豆浆 public abstract class SoyaMilk { //模板方法做成final不让子类去覆盖 public final void make() { select(); addCondiments(); soka(); beat(); } //选材料 private void select() { System.out.println("第一步选择好的黄豆"); } //添加不同的材料,抽象方法子类具体实现 protected abstract void addCondiments(); private void soka() { System.out.println("第三步黄豆和配料开始浸泡"); } private void beat() { System.out.println("第四步黄豆和配料放到豆浆机去打碎"); } } public class RedbeanSoyaMilk extends SoyaMilk { @Override protected void addCondiments() { System.out.println("第二步添加红豆配料"); } } public class PeanutSoyaMilk extends SoyaMilk { @Override protected void addCondiments() { System.out.println("第二步添加花生配料"); } } public class Client { public static void main(String[] args) { System.out.println("制作红豆豆浆"); SoyaMilk readBeansoyaMilk =new RedbeanSoyaMilk(); readBeansoyaMilk.make(); System.out.println("制作花生豆浆"); SoyaMilk peanutSoyaMilk =new PeanutSoyaMilk(); peanutSoyaMilk.make(); } }
-
模板方法模式的钩子方法
- 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为钩子。
-
代码
对上述的SoyaMilk进行改造 public abstract class SoyaMilk { //模板方法做成final不让子类去覆盖 public final void make() { select(); if(customerWantCondiments()) { addCondiments(); } soka(); beat(); } //选材料 private void select() { System.out.println("第一步选择好的黄豆"); } //添加不同的材料,抽象方法子类具体实现 protected abstract void addCondiments(); private void soka() { System.out.println("第三步黄豆和配料开始浸泡"); } private void beat() { System.out.println("第四步黄豆和配料放到豆浆机去打碎"); } //钩子方法决定是否需要添加配料 boolean customerWantCondiments() { return true; } } public class PureSoyaMilk extends SoyaMilk { @Override protected void addCondiments() { System.out.println("不添加任何配料"); } @Override boolean customerWantCondiments() { return false; } } public class Client { public static void main(String[] args) { System.out.println("制作纯豆浆"); SoyaMilk readBeansoyaMilk =new PureSoyaMilk(); readBeansoyaMilk.make(); } }
-
模板方法的注意事项和细节
- 基本思想:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
- 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
- 既统一了算法,也提供了很大的灵性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
- 该模式的不足之处:每一个不同的实现需要一个子类实现,导致类的个数增加,使得系统更加庞大。
- 一般模板方法加上final关键字,防止子类重写模板方法。
- 模板方法的使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理。
-
- 命令模式
-
基本介绍:
- 命令模式:在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体请求操作者即可,此时,可以使用命令模式来进行设计。
- 命令模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
- 在命令模式中会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持撤销的操作。
- 通俗易通的理解:将军发布命令,士兵去执行。其中有几个角色:将军(发布命令者)、士兵(命令执行者)、命令(连接将军和士兵)Invoker是调用者(将军),Receiver是被调用者(士兵)、 MyCommand是命令,实现了Command接口,持有接受对象。
-
代码
//创建命令接口 public interface Command { //执行动作 public void excute(); //撤销动作 public void undo(); } /** * 它没有任何命令即空执行,用于初始化每个按钮,当调用 * 空命令时对象什么都不做,这也是一种设计模式,这可以省 * 掉空的判断。 * @author Administrator * */ public class NoCommand implements Command{ @Override public void excute() { } @Override public void undo() { } } public class LightOnCommand implements Command{ private LightReceiver light; public LightOnCommand(LightReceiver light) { this.light = light; } @Override public void excute() { //调用接收者的方法 light.on(); } @Override public void undo() { light.off(); } } public class LightOffCommand implements Command{ private LightReceiver light; public LightOffCommand(LightReceiver light) { this.light = light; } @Override public void excute() { //调用接收者的方法 light.off(); } @Override public void undo() { light.on(); } } public class LightReceiver { public void on() { System.out.println("电灯打开"); } public void off() { System.out.println("电灯关闭"); } } public class RemoteController { //开按钮的命令数组 private Command[] onCommands; //关按钮的命令数组 private Command[] offCommands; Command undoCommand; public RemoteController() { onCommands=new Command[5]; offCommands=new Command[5]; for (int i = 0; i < 5; i++) { onCommands[i]=new NoCommand(); offCommands[i]=new NoCommand(); } } //给我们的按钮设置需要的命令 public void setCommand(int no,Command onCommand,Command offCommand) { onCommands[no]=onCommand; offCommands[no]=offCommand; } //按下开的按钮 public void onButtonWasPushed(int no) { onCommands[no].excute(); undoCommand=onCommands[no]; } //按下关的按钮 public void offButtonWasPushed(int no) { offCommands[no].excute(); undoCommand=offCommands[no]; } //按下撤销的按钮 public void undoButtonWasPushed() { undoCommand.undo(); } } public class Client { public static void main(String[] args) { LightReceiver lightReceiver = new LightReceiver(); LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver); LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver); RemoteController remoteController = new RemoteController(); remoteController.setCommand(0, lightOnCommand, lightOffCommand); System.out.println("按下电灯打开的按钮"); remoteController.onButtonWasPushed(0); System.out.println("按下电灯关闭的按钮"); remoteController.offButtonWasPushed(0); System.out.println("按下撤销的按钮"); remoteController.undoButtonWasPushed(); } }
-
命令模式注意事项和细节
- 将发起请求的对象和执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的工作,也就是说:“请求发起者”和请求执行者“之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
- 容易设计一个命令队列。只要命令对象放到列队,就可以多线程的执行命令。
- 容易实现对请求的撤销和重做。
- 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在使用的时候要注意。
- 空命令也是一种设计模式,它为我们省去了判空的操作,在上面的实例中,如果没有空命令,我们没按下一个按键都要判空,这给我们编码带来了一定的麻烦。
- 命令模式经典的应用场景:界面的一个按钮就是一个命令、模拟cmd(dos命令)、订单的撤销和恢复、触发-反馈机制。
-