JavaSE基础知识(十七)--Java复用代码之静态代理

news/2024/7/5 21:28:49

Java SE 是什么,包括哪些内容(十七)?

本文内容参考自Java8标准
再次感谢Java编程思想对本文的启发!
Java中复用代码的方式,除了之前博文中提到过的组合和继承之外,还有第三种:代理,而代理又分为静态代理动态代理两种,本篇博文只涉及静态代理的内容,下一篇博文将重点介绍动态代理
Java并没有提供对代理的直接支持,它是继承与组合的中庸之道,因为代理包括了两项内容:
⑴、将一个成员对象置于所要构建的类中。-----这点类似组合
⑵、在所要构建的类中暴露了这个成员对象所属类的所有成员方法。-----这点类似继承
下面将通过具体的代码举例来说明:
①、控制模块的类型代码:

// 控制模块的类型代码
   //类SpaceShipControls
   public class SpaceShipControls{
      //方法up(int velocity),带一个int类型的形式参数,表示加速的数值。
      void up(int velocity){}
      //方法down(int velocity),带一个int类型的形式参数,表示减速的数值。
      void down(int velocity){}
      //方法left(int velocity),带一个int类型的形式参数,表示左转的距离。
      void left(int velocity){}
      //方法right(int velocity),带一个int类型的形式参数,表示右转的距离。
      void right(int velocity){}
      //方法forward(int velocity),带一个int类型的形式参数,表示前进的距离。
      void forward(int velocity){}
      //方法back(int velocity),带一个int类型的形式参数,表示后退的距离。
      void back(int velocity){}
      //方法turboBoost(),不带形式参数,表示引擎增强操作
      void turboBoost(){}
   }

②、现在太空船需要一个控制模块:
如果你使用继承:

// 太空船继承控制模块
   //子类SpaceShip继承父类SpaceShipControls
   public SpaceShip extends SpaceShipControls{
      //子类新增的类变量name,表示太空船的名称
      private String name;
      //子类的构造方法,注意,由于父类中仅有默认的构造方法,
      //虽然这里是带形式参数的构造方法,但是不需要super关键字。
      public SpaceShip(String name){ this.name = name; }
      //toString()方法,返回太空船的名称
      public String toString(){ return name; }
      //程序执行入口main方法
      public static void main(String[] args){
         //创建子类SpaceShip的对象。同时给构造方法传入了实际参数
         //"NSEA Protector"
         SpaceShip protector = new SpaceShip("NSEA Protector");
         //调用方法void forward(int velocity){},
         //表示太空船前进。前进的距离是100.
         protector.forward(100);
      }
   }

虽然说,使用继承是没什么问题,但是严格来讲SpaceShip并不是SpaceShipControls类型(因为类SpaceShip代表太空船类型,类SpaceShipControls代表控制器类型,船类型去继承控制器类型,显然不是很合理!),所以,更准确地讲,应该是类SpaceShip包含类SpaceShipControls(因为每个太空船都需要一个控制器),下面,我们按照组合的思想来实现一下代码:
①、控制模块的类型代码:
同上,在此省略。
②、现在太空船需要一个控制模块:

// 太空船含有控制模块
   //类SpaceShipControls组合成类SpaceShip
   public SpaceShip {
      //类SpaceShip含有类SpaceShipControls的一个引用,并对它进行了初始化
      SpaceShipControls Controls = new SpaceShipControl();
      //类变量name,表示太空船的名称
      private String name;
      //构造方法
      public SpaceShip(String name){ this.name = name; }
      //toString()方法,返回太空船的名称
      public String toString(){ return name; }
      //程序执行入口main方法
      public static void main(String[] args){
         //创建类SpaceShip的对象。同时给构造方法传入了实际参数
         //"NSEA Protector"
         SpaceShip protector = new SpaceShip("NSEA Protector");
         //现在出现了问题,我想让太空船前进,这个时候你只能去调用
         //控制器类的forward(int velocity){}方法,那就是
         SpaceShip.Controls.forward(100);
         //首先不提这个代码变扭的问题,如果你的代码可以发布给别人使用,你的说明文档
         //怎么写?你可能需要告知使用你类库的客户端程序员:太空船本身是没有前进的
         //方法的,你想让太空船前进的话,要通过调用它的控制器类的前进方法。
         //如果是这样,你的类库将没有市场,因为逻辑有问题。
         //别人理解起来也比较费劲。 
      }
   }

通过以上内容,我们了解到目前太空船和控制器的关系,无论是利用继承还是组合,都不能很好地解决。
那么,根本性的问题在哪里呢?
实际上,类SpaceShip想重用的仅仅是类SpaceShipControls的所有接口,以及接口的实现代码,其它则不需要(也就是要实现:能让类SpaceShip包含类SpaceShipControls,于此同时类SpaceShipControls的所有方法都在类SpaceShip中暴露出来)!也就是说,类SpaceShip和类SpaceShipControls不属于同一类型,继承不合理(如果是大型的继承架构,混乱的继承关系可能导致架构混乱,系统难以维护),但是组合实现不了仅重用接口以及接口实现代码的目的(说白了,不能直接将组合类型的方法变为自己的方法),唯一的方法就是代理了(代理能将其它类的方法变为另一个类的方法)。
那么代理是一种什么形式呢?
代理更准确的描述是:代理能让类SpaceShip包含类SpaceShipControls,于此同时类SpaceShipControls的所有方法都在类SpaceShip中暴露了出来。
①、控制模块的类型代码:
同上,在此省略。
②、现在太空船需要一个控制模块(通过代理实现):

// 太空船含有控制模块
   //类SpaceShipControls组合成类SpaceShip
   public SpaceShip {
      //类SpaceShip含有类SpaceShipControls的一个引用,并对它进行了初始化
      SpaceShipControls Controls = new SpaceShipControl();
      //类变量name,表示太空船的名称
      private String name;
      //构造方法
      public SpaceShip(String name){ this.name = name; }
      //toString()方法,返回太空船的名称
      public String toString(){ return name; }
      //------------从这里开始,你会看到,类SpaceShip声明了部分与类
      //SpaceShipControl完全一致的接口,而实现更是拿来主义。
      //直接拿过来用了。
      //方法up(int velocity),带一个int类型的形式参数,表示加速的数值。
      void up(int velocity){
         //代码实现不再需要自己操心,直接调用同名的方法.
         //达到复用代码的目的。后面的方法是同一个道理。
         Controls.up(velocity);
      }
      //方法down(int velocity),带一个int类型的形式参数,表示减速的数值。
      void down(int velocity){
         //拿来主义式复用。
         Controls.down(velocity);
      }
      //方法left(int velocity),带一个int类型的形式参数,表示左转的距离。
      void left(int velocity){
         Controls.left(velocity);
      }
      //方法right(int velocity),带一个int类型的形式参数,表示右转的距离。
      void right(int velocity){
         Controls.right(velocity);
      }
      //方法forward(int velocity),带一个int类型的形式参数,表示前进的距离。
      void forward(int velocity){
         Controls.forward(velocity);
      }
      //方法back(int velocity),带一个int类型的形式参数,表示后退的距离。
      void back(int velocity){
         Controls.back(velocity);
      }
      //方法turboBoost(),不带形式参数,表示引擎增强操作
      void turboBoost(){
         Controls.turboBoost();
      }
      //程序执行入口main方法
      public static void main(String[] args){
         //创建类SpaceShip的对象。同时给构造方法传入了实际参数
         //"NSEA Protector"
         SpaceShip protector = new SpaceShip("NSEA Protector");
         //现在你想控制太空船前进,就很方便了
         //因为它本身已经有了前进的方法。
         protector.forward(100);
         //方法变成了太空船的方法,代码实现还是控制器的实现,
         //实际上还是控制器控制了太空船。
         //所以代理完美解决了太空船和控制的问题。
         //当然,代理的拓展空间就是太空船类还能继续代理其它的类。
      }
   }

通过上面的代码,你可以很清晰地了解,太空船的方法是如何传递给了底层的Controls对象,通过代理得到的接口由此也和通过继承得到的接口完全一致了。但是,在实际情况中,使用代理可以有更多的控制力,因为继承是默认获得了所有父类的方法,而代理可以有选择性地代理目标对象中的方法,可以是全部,也可以是部分。
Java我是通过自学的,起初对Java的基础知识了解得不是很全面的时候,刚好遇到了代理,和大多数的人一样,我也是通过百度搜索各种博文,但是,结果无一例外,很多人写的内容都是类B代理了类A,就像什么经销商代理厂家,什么这个代理了那个,举的实际的例子都振振有词,实际上我想阐明的观点是:当你真正使用了代理模式的时候,它与现实中实际的代理意义其实没什么关系,后期你代码中如果有可能涉及代理模式的时候,你使用的初衷肯定不可能出发于我想新写一个类去代理目标类,这难免会让人觉得你是为了代理而去代理
如果你还不能理解,那么我就再深入一点,现实中就拿经销商代理厂家来说,因为中国这个地域很广阔,一个厂家需要有发展,肯定需要一个庞大的销售网络,那么这个网络中的各个节点,实际上就是我们说的代理(代理又分省级代理,市级代理,区县代理等),他们的行为应该是完全反应了厂家的意志,代理厂家行使职权去扩展市场,去打开销售渠道,让销售额保持增长,最终实现上市的目的(比如近期的三只松鼠就上市了,华为是个特例,它就是不上市,你也没办法!),所以你会发现,这里所谓的代理,就是字面意思:代为打理,代为管理,代为经营等等
我们再来看一下Java中的所谓代理:回到上面的示例代码,其实,这个示例代码正是使用代理的一种很经典的情况!
控制器类表示的是对速度方向的控制:速度的加减,方向的前后左右,那么,为什么需要单独让它成为一个类呢?因为有很多的类似太空船的机器都需要一个专门的控制器类,Java是绝对崇尚代码复用,以及如何最大限度减轻系统后期维护工作量的,如果不单独成为一个类,那么必将造成同样的代码在多处分散使用,系统后期代码维护量可想而知,既然是一个单独的类了,在需要使用的时候,直接通过类创建对象即可。系统维护升级,也就只需要专注控制器这一个类的代码就行了,其他任何类的代码都不需要改动,非常的方便。
下面的内容是最关键的部分
上面已经说过了,控制器类是控制速度方向的,从它的方法的名称就能很明显地看出来,那么,问题来了,太空船是不是也要控制速度方向呢?

答案显然是的!

那么,太空船控制速度方向方法名称能不能和控制器类的一模一样呢?

Why not ?

所以,史上最强的巧合出现了:一个类需要使用另外一个类的同名的方法(将它的同名方法据为己有),但是继承不合适,组合达不到目的(不能将其它类的方法变成自己的方法),于是,这种形式就得到了一个蹩脚的名称:代理。看起来就像是一个类代理了另外的一个类。
最根本的原因是,你新写的类想表达一个概念(比如加速,减速,左转,右转,前进,后退等等),但是这概念在其它的类中已经有了最专业的表达(要表达速度和方向的控制你难道还能找出比控制器类中的方法名称更专业的表达了吗?),所以,你最好,最省事,最便捷的办法就是将那个方法名称直接拿过来使用(可以全部拿过来,也可以就只拿一部分)!
最纯粹的代理是你不但拿了方法名称来使用,连方法的实现你也直接拿过来使用了:
最纯粹的代理!
更高级的代理是,除了直接拿过方法实现来使用,还会增加一些其他的代码。以完善自己的功能!
所以,这下你对代理的认识有没有更清晰了一些呢?
如果还没理解,我再找几幅图片来说明:
2006年的时候,有一部电视剧很红,叫亮剑,主人公叫李云龙,剧里分别有这么几个镜头:
第一集:打坂田联队的时候,李云龙用的是迫击炮
迫击炮!
打中了!
一炮就干掉了!
我这里需要强调的内容有:迫击炮一炮就干掉了(好用)
第十四集:打山本特工队的时候,李云龙用的是意大利炮
意大利炮!
意大利炮!
一炮就干掉了!
我这里需要强调的内容有:意大利炮一炮就干掉了(好用)
第二十集:国共打内战的时候,共军用的是榴弹炮
榴弹炮!
我这里没什么需要强调的了。相信你已经看懂了。
第一集,李云龙"代理"的是小日本,迫击炮拿来就用,也不改装。
第十四集,李云龙"代理"的是意大利,意大利炮拿来就用,也不改装。
第二十集,共军"代理"的是美国,榴弹炮拿来就用,也不改装。
实际上,这里所谓的改装,就是在代理类的代码中增加其他的内容。
-------------------------------------------------------------------------------------------------
以上内容初步讲解了代理的样子,但是在实际中,代理是源自接口的(有关接口的知识,可以查阅我的有关博文,这里不再赘述)。
总共分四步:
⑴、接口(表示能拥有的行为,说白了,就是能干什么!):

// 接口代码
   //接口Action
   public interface Action{
        //行为一:
        void a();
        //行为二:
        viod b();
        //行为三:
        String c();
   }

⑵、被代理类(实现接口):

// 被代理类代码
   public class BeProxy implements Action{
     //实现行为一:
     public void a(){
        //打印字符串"a()"
        System.out.println("a()");
     };
     //实现行为二:
     public viod b(){
        //打印字符串"b()"
        System.out.println("b()");
     };
     //实现行为三:
     public String c(){
        return "c()";
     };
   }

⑶、代理类(代理类需要实现和被代理类一样的接口):

// 代理类代码
   public class Proxy implements Action{
      private BeProxy bp = new BeProxy();
      public Proxy(){
         System.out.println("Proxy()");
      }
     //代理实现行为一:
     public void a(){
        bp.a();
     };
     //代理实现行为二:
     public viod b(){
        bp.b();
     };
     //代理实现行为三:
     public String c(){
        return bp.c();
     };
   }

⑷、测试:
略,可以自行尝试。
使用代理模式的时候,一般都是使用代理类被代理类一般只是一个承上(接口)启下(代理类)的作用。
当然,你还能在代理类中新增一些代码,来增强代理类的功能(改装):
⑶、代理类(改装被代理类):

// 在代理类代码中增强功能
   public class Proxy implements Action{
      private BeProxy bp = new BeProxy();
      public Proxy(){
         System.out.println("Proxy()");
      }
      //代理实现行为一:
     public void a(){
        //添加代码增强功能...
        bp.a();
        //添加代码增强功能...
     };
     //代理实现行为二:
     public viod b(){
        //添加代码增强功能...
        bp.b();
        //添加代码增强功能...
     };
     //代理实现行为三:
     public String c(){
        //添加代码增强功能...
        return bp.c();
        //添加代码增强功能...
     };
   }

如果你之前有了解过代理,你肯定听说过:在某些动作之前执行一些内容,在某些动作之后执行一些内容。这是代理炒的最热的功能之一。
上面已经大概讲清楚了代理的整体框架内容,现在,我们来看一下,静态代理存在哪些问题
回到前文中,有关亮剑电视剧的截图
结合我谈到的代理三步模式(接口、被代理类、代理类):
如果一开始,你只有有关迫击炮的接口:
然后,
接口:迫击炮
被代理类:实现迫击炮的接口。
代理类:实现迫击炮的接口。
结论:李云龙只能使用迫击炮。
这时,李云龙突然缴获了一门意大利炮。
然后,
接口:迫击炮、意大利炮
被代理类:实现迫击炮、意大利炮的接口。
代理类:实现迫迫击炮、意大利炮的接口。
结论:李云龙可以使用迫击炮、意大利炮。
这时,李云龙突然又缴获了一门榴弹炮。
然后,
接口:迫击炮、意大利炮、榴弹炮
被代理类:实现迫击炮、意大利炮、榴弹炮的接口。
代理类:实现迫迫击炮、意大利炮、榴弹炮的接口。
结论:李云龙可以使用迫击炮、意大利炮、榴弹炮。
这时,李云龙突然又缴获了一门火箭炮。
然后,
接口:迫击炮、意大利炮、榴弹炮、火箭炮
被代理类:实现迫击炮、意大利炮、榴弹炮、火箭炮的接口。
代理类:实现迫迫击炮、意大利炮、榴弹炮、火箭炮的接口。
结论:李云龙可以使用迫击炮、意大利炮、榴弹炮、火箭炮。
这时,李云龙突然又缴获了一门火箭炮。
然后,
接口:迫击炮、意大利炮、榴弹炮、火箭炮、加农炮
被代理类:实现迫击炮、意大利炮、榴弹炮、火箭炮、加农炮的接口。
代理类:实现迫迫击炮、意大利炮、榴弹炮、火箭炮、加农炮的接口。
结论:李云龙可以使用迫击炮、意大利炮、榴弹炮、火箭炮、加农炮。



炮…
代码修改起来,维护起来的难度可想而知。
所以,静态代理模式只适合、加农炮的使用。
你需要更进一步了解的是:动态代理模式
下一篇博文,我将详细描述动态代理模式。你会发现它针对静态代理模式的优势到底在哪里。
PS:时间有限,有关Java SE的内容会持续更新!今天就先写这么多,如果有疑问或者有兴趣,可以加QQ:2649160693,并注明CSDN,我会就博文中有疑义的问题做出解答。同时希望博文中不正确的地方各位加以指正!


http://www.niftyadmin.cn/n/1982547.html

相关文章

JavaSE基础知识(十七)--Java复用代码之动态代理

Java SE 是什么,包括哪些内容(十七)? 本文内容参考自Java8标准 再次感谢Java编程思想对本文的启发! 上一篇博文中详细说明了静态代理的内容,也指出了静态代理只适合小范围的使用(使用和维护都很麻烦),真正强大的是动态…

JavaSE基础知识(十七)--Java动态代理中InvocationHandler中Object类型参数proxy的作用

Java SE 是什么,包括哪些内容(十七)? 本文内容参考自Java8标准 再次感谢Java编程思想对本文的启发! 上一篇博文中详细说明了动态代理的内容,但是在说到调用处理器InvocationHandler的时候,有一个Object类型的参数prox…

JavaSE基础知识(十七)--Java复用代码之结合使用组合与继承(正确的初始化与清理)

Java SE 是什么,包括哪些内容(十七)? 本文内容参考自Java8标准 再次感谢Java编程思想对本文的启发! 在我们的日常编程工作中,同时使用组合与继承是很常见的事情,下面通过一个例子来说明: PS:同…

基于HTML5实现3D热图Heatmap应用

为什么80%的码农都做不了架构师?>>> Heatmap热图通过众多数据点信息,汇聚成直观可视化颜色效果,热图已广泛被应用于气象预报、医疗成像、机房温度监控等行业,甚至应用于竞技体育领域的数据分析。 http://www.hightopo…

JavaSE基础知识(十七)--Java复用代码之在组合与继承之间选择

Java SE 是什么,包括哪些内容(十七)? 本文内容参考自Java8标准 再次感谢Java编程思想对本文的启发! 通过前面的博文,我们了解到,组合与继承都允许在新的类中放置子对象,组合是显式地这么做,继承…

使用Hexo搭建自己的博客

2019独角兽企业重金招聘Python工程师标准>>> 参考文章 使用GitHub和Hexo搭建免费静态Blog http://wsgzao.github.io/post/hexo-guide 我的主题 https://github.com/ppoffice/hexo-theme-icarus hexo博客换主题--icarus http://hexo.trity.cc/2015/08/24/hexo%…

JavaSE基础知识(十七)--Java复用代码之关键字protected的详细描述

Java SE 是什么,包括哪些内容(十七)? 本文内容参考自Java8标准 再次感谢Java编程思想对本文的启发! 在博文"JavaSE基础知识(十六)–Java的访问权限控制关键字(public、protected、private)"中略微提及了关键字protected&#xff0…

.xyz域名总量TOP10:西部数码第四 排名升1位

IDC评述网(idcps.com)10月16日报道:根据ntldstats.com发布的最新数据显示,截止至2015年10月15日17时,国内外.xyz域名总量十强排名情况,相比上期9月28日,有所变动。西部数码、阿里云(…