今天就Java语言中关于类功能方法增强这一块的内容进行了梳理与整理。便借此PO出自己的第一篇关于技术方面的博文,将自己的所学,所思,所感码出来,是分享,亦是学习。
这里所提到的方法增强就是通过Java的一些特性来对一个类的功能进行丰富与增强,便于对现有的项目进行扩展。这里主要介绍3种方法,即继承或实现接口类、装饰者模式和动态代理。首先从基础的概念入手:
继承
是面向对象最显著的一个特性。在Java中,类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类,现有类被称作父类,子类会自动拥有父类所有可继承的属性和方法。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
装饰者模式
即Decorator模式(别名Wrapper)指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
动态代理
即Proxy Pattern,23种常用的面向对象软件的设计模式之一。为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
在对三者的概念有了初步的了解和认知的前提下,在分别介绍每个手段的特性。
===========继 承===========
java是面向对象的语言,继承机制使他的可扩展性大大增强,我们可以通过继承方式对现有类进行扩展增强。子类继承父类之后可以获得父类所有的公共方法,子类可以进行重写等操作,这种方式简便易学,但是随之而来的是代码的耦合性大大的增强,不利于后期的维护,所以对于继承这种方法,谨慎使用。
class Fu{ public void show(){ System.out.println("Fu类中的show方法执行"); } } class Zi extends Fu{ public void show2(){ System.out.println("Zi类中的show2方法执行"); } } public class Test{ public static void main(String[] args) { Zi z = new Zi(); z.show(); //子类中没有show方法,但是可以找到父类方法去执行 z.show2(); } }
当在程序中通过对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。
============装饰者模式===========
设计原则:
1. 多用组合,少用继承。
利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。
2. 类应设计的对扩展开放,对修改关闭。
要点:
1. 装饰者和被装饰对象有相同的超类型。
2. 可以用一个或多个装饰者包装一个对象。
3. 装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的。
4. 对象可以在任何时候被装饰,所以可以在运行时动态的,不限量的用你喜欢的装饰者来装饰对象。
5. 装饰模式中使用继承的关键是想达到装饰者和被装饰对象的类型匹配,而不是获得其行为。
6. 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。在实际项目中可以根据需要为装饰者添加新的行为,做到“半透明”装饰者。
7. 适配器模式的用意是改变对象的接口而不一定改变对象的性能,而装饰模式的用意是保持接口并增加对象的职责。
实现步骤:
1.装饰者类和被装饰者类必须实现同一个接口,或继承同一个类
2.在装饰者类中,必须要有被装饰者类的引用
3.在装饰者类中对需要增强的方法,进行增强
4.在装饰者类中对不需要增强的方法,调用原来的业务逻辑举个例子深入理解一下,
周霆
装饰模式为已有类动态附加额外的功能就像LOL、王者荣耀等类Dota游戏中,英雄升级一样。每次英雄升级都会附加一个额外技能点学习技能。具体的英雄就是ConcreteComponent,技能栏就是装饰器Decorator,每个技能就是ConcreteDecorator;
//Component 英雄接口 public interface Hero { //学习技能 void learnSkills(); } //ConcreteComponent 具体英雄盲僧 public class BlindMonk implements Hero { private String name; public BlindMonk(String name) { this.name = name; } @Override public void learnSkills() { System.out.println(name + "学习了以上技能!"); } } //Decorator 技能栏 public class Skills implements Hero{ //持有一个英雄对象接口 private Hero hero; public Skills(Hero hero) { this.hero = hero; } @Override public void learnSkills() { if(hero != null) hero.learnSkills(); } } //ConreteDecorator 技能:Q public class Skill_Q extends Skills{ private String skillName; public Skill_Q(Hero hero,String skillName) { super(hero); this.skillName = skillName; } @Override public void learnSkills() { System.out.println("学习了技能Q:" +skillName); super.learnSkills(); } } //ConreteDecorator 技能:W public class Skill_W extends Skills{ private String skillName; public Skill_W(Hero hero,String skillName) { super(hero); this.skillName = skillName; } @Override public void learnSkills() { System.out.println("学习了技能W:" + skillName); super.learnSkills(); } } //ConreteDecorator 技能:E public class Skill_E extends Skills{ private String skillName; public Skill_E(Hero hero,String skillName) { super(hero); this.skillName = skillName; } @Override public void learnSkills() { System.out.println("学习了技能E:"+skillName); super.learnSkills(); } } //ConreteDecorator 技能:R public class Skill_R extends Skills{ private String skillName; public Skill_R(Hero hero,String skillName) { super(hero); this.skillName = skillName; } @Override public void learnSkills() { System.out.println("学习了技能R:" +skillName ); super.learnSkills(); } } //客户端:召唤师 public class Player { public static void main(String[] args) { //选择英雄 Hero hero = new BlindMonk("李青"); Skills skills = new Skills(hero); Skills r = new Skill_R(skills,"猛龙摆尾"); Skills e = new Skill_E(r,"天雷破/摧筋断骨"); Skills w = new Skill_W(e,"金钟罩/铁布衫"); Skills q = new Skill_Q(w,"天音波/回音击"); //学习技能 q.learnSkills(); } }
public interface Hero { //学习技能 void learnSkills(); } //ConcreteComponent 具体英雄盲僧 public class BlindMonk implements Hero { private String name; public BlindMonk(String name) { this.name = name; } @Override public void learnSkills() { System.out.println(name + "学习了以上技能!"); } } //Decorator 技能栏 public class Skills implements Hero{ //持有一个英雄对象接口 private Hero hero; public Skills(Hero hero) { this.hero = hero; } @Override public void learnSkills() { if(hero != null) hero.learnSkills(); } } //ConreteDecorator 技能:Q public class Skill_Q extends Skills{ private String skillName; public Skill_Q(Hero hero,String skillName) { super(hero); this.skillName = skillName; } @Override public void learnSkills() { System.out.println("学习了技能Q:" +skillName); super.learnSkills(); } } //ConreteDecorator 技能:W public class Skill_W extends Skills{ private String skillName; public Skill_W(Hero hero,String skillName) { super(hero); this.skillName = skillName; } @Override public void learnSkills() { System.out.println("学习了技能W:" + skillName); super.learnSkills(); } } //ConreteDecorator 技能:E public class Skill_E extends Skills{ private String skillName; public Skill_E(Hero hero,String skillName) { super(hero); this.skillName = skillName; } @Override public void learnSkills() { System.out.println("学习了技能E:"+skillName); super.learnSkills(); } } //ConreteDecorator 技能:R public class Skill_R extends Skills{ private String skillName; public Skill_R(Hero hero,String skillName) { super(hero); this.skillName = skillName; } @Override public void learnSkills() { System.out.println("学习了技能R:" +skillName ); super.learnSkills(); } } //客户端:召唤师 public class Player { public static void main(String[] args) { //选择英雄 Hero hero = new BlindMonk("李青"); Skills skills = new Skills(hero); Skills r = new Skill_R(skills,"猛龙摆尾"); Skills e = new Skill_E(r,"天雷破/摧筋断骨"); Skills w = new Skill_W(e,"金钟罩/铁布衫"); Skills q = new Skill_Q(w,"天音波/回音击"); //学习技能 q.learnSkills(); } }
输出:
学习了技能Q:天音波/回音击 学习了技能W:金钟罩/铁布衫 学习了技能E:天雷破/摧筋断骨 学习了技能R:猛龙摆尾 李青学习了以上技能!
Q:天音波/回音击 学习了技能W:金钟罩/铁布衫 学习了技能E:天雷破/摧筋断骨 学习了技能R:猛龙摆尾 李青学习了以上技能!
下面以web中常见的编码问题为例,进一步提升:
需求:
处理GET请求参数编码问题,需要在Filter中放行时,把request对象给“调包”了,也就是让目标Servlet使用我们“调包”之后的request对象。这说明我们需要保证“调包”之后的request对象中所有方法都要与“调包”之前一样可以使用,并且getParameter()方法还要有能力返回转码之后的参数。这可能让你想起了“继承”,但是这里不能用继承,而是“装饰者模式(Decorator Pattern)”!
解决方法:
对request对象进行增强的条件,刚好符合装饰者模式的特点!因为我们不知道request对象的具体类型,但我们知道request是HttpServletRequest接口的实现类。这说明我们写一个类EncodingRequest,去实现HttpServletRequest接口,然后再把原来的request传递给EncodingRequest类!在EncodingRequest中对HttpServletRequest接口中的所有方法的实现都是通过代理原来的request对象来完成的,只有对getParameter()方法添加了增强代码!
JavaEE已经给我们提供了一个HttpServletRequestWrapper类,它就是HttpServletRequest的包装类,但它没做任何的增强!你可能会说,写一个装饰类,但不做增强,其目的是什么呢?使用这个装饰类的对象,和使用原有的request有什么分别呢?HttpServletRequestWrapper类虽然是HttpServletRequest的装饰类,但它不是用来直接使用的,而是用来让我们去继承的!当我们想写一个装饰类时,还要对所有不需要增强的方法做一次实现是很心烦的事情,但如果你去继承HttpServletRequestWrapper类,那么就只需要重写需要增强的方法即可了。
EncodingRequest代码:
public class MyRequest1 extends HttpServletRequestWrapper{ public MyRequest1(HttpServletRequest request) { super(request); } //增强 @Override public String getParameter(String name) { //获取乱码的内容 String value = super.getParameter(name); try { value = new String(value.getBytes("iso-8859-1"),"utf-8"); return value; } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return super.getParameter(name); } }
这里是将get请求的request对象对getParameter()方法增强了,直接将“iso-8859-1”的编码改为“utf-8”的编码。
========== 动态代理=============
java代理有jdk动态代理、cglib代理,这里只说下jdk动态代理,jdk动态代理主要使用的是java反射机制(既java.lang.reflect包)。
代理类模式理解起来会比装饰者设计模式更加难,因为这个涉及到反射的原理。学习代理模式最终是学习AOP(面向切面编程),它与装饰者模式有点相似,它比装饰者模式还要灵活!
原理是(歌手、经纪人做例子):
l 建立一个公共的接口,比如:歌手public interface Singer;
l 用具体的类实现接口,比如:周杰伦,他是歌手所以实现Singer这个类,class MySinger implements Singer,重写singer方法.
l 建立代理类,这里也就是经纪人,他需要实现InvocationHandler接口,并重写invoke方法
l 这样当有什么事情,要找周杰伦(具体类)唱歌的时候,就必须先到经纪人(代理类)那里处理,代理人在决定要不要与你见面(该方法要不要执行),找到经纪人方法invoke,经纪人方法invoke来找周杰伦的singer方法
动态代理:程序运行时,使用JDK提供工具类(Proxy),动态创建一个类,此类一般用于代理。
代理:你 --代理(增强) -- 厂商
代理类:目标类:被代理的
动态代理使用前提:必须有接口
Object proxyObj = Proxy.newProxyInstance(参数1,参数2,参数3);
参数1:ClassLoader,负责将动态创建类,加载到内存。当前类.class.getClassLoader();
参数2:Class[] interfaces ,代理类需要实现的所有接口(确定方法),被代理类实例.getClass().getInterfaces();
参数3:InvocationHandler,请求处理类,代理类不具有任何功能,代理类的每一个方法执行时,调用处理类invoke方法。
voke(Objectproxy ,Method ,Object[] args)
参数1:代理实例
参数2:当前执行的方法
参数3:方法实际参数。
动态代理案例:模拟Collections工具类的静态方法
需求:
模拟Collections.unmodifiableList(list);
传递List,返回List
调用List方法的时候,通过我的代理类中的方法invoke,
允许你使用get size ,不允许使用add remove代码实现:
@SuppressWarnings("all") public class ProxyDemo02 { @Test public void demo01()throws Exception{ List
list = new ArrayList (); list.add("itcast"); list.add("java"); System.out.println(list.size()); list = proxyList(list); //list.add("1"); System.out.println(list.get(0)); } /* * 模拟集合工具类Collections的方法unmodifiableList * 传递List,返回List */ public static List proxyList(List list)throws Exception{ //调用代理的工具类Proxy,传递相应的参数 List list2 =(List ) Proxy.newProxyInstance( ProxyDemo02.class.getClassLoader(), list.getClass().getInterfaces(), new MyInvocation(list)); return list2; } }
package cn.itcast.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.List; /* * InvocationHandler接口的实现类 * 重写方法 invoke * * ProxyDemo02中,只要你使用List集合的方法 * 都会运行这个类的invoke方法 * */ public class MyInvocation implements InvocationHandler{ private List
list; public MyInvocation(List list){ this.list = list; } public Object invoke(Object proxy, Method method, Object[] args)throws Exception { //对象method进行判断,判断方法名是什么 String name = method.getName(); if("add".equals(name)) //throw new UnsupportedOperationException(message)不支持功能异常 throw new RuntimeException("add NO"); if("remove".equals(name)) throw new RuntimeException("remove NO"); if("set".equals(name)) throw new RuntimeException("set NO"); //调用的List的方法,是get 让他运行, method类的方法invoke return method.invoke(list, args); } }
总结:
1、继承或者实现接口:特点是被增强对象不能变,增强的内容不能变。
2、装饰着模式:特点是被增强对象可变,但增强内容不可变。
3、动态代理:特点是被增强对象可变,增强内容可变。