黑马程序员技术交流社区

标题: 【goto房宝彬】抽象类及接口在设计时考虑的如何选择问题 [打印本页]

作者: 刘蕴学    时间: 2012-3-25 03:18
标题: 【goto房宝彬】抽象类及接口在设计时考虑的如何选择问题
本帖最后由 了无尘 于 2012-3-25 03:42 编辑

关于抽象类和接口的相关视频里有,这就不说了,老房问了一个特别无语的问题,用我的话说就是钻牛角尖,但是这也涉及到了设计层面的犯愁,所以我还是找找了资料看看了,下边简单的给大家说说吧。
老房的问题是,如果抽象类里边没有任何抽象的成员,这样有什么实际的价值。

首先,我们假设需要一个电脑类Computer类,电脑有开机关机,那么这里我们选择抽象类还是接口呢?
  1. package org.it;


  2. public class MyComputer
  3. {
  4.        
  5. }

  6. abstract class Computer
  7. {
  8.         abstract void open();
  9.         abstract void close();
  10. }

  11. interface Computer
  12. {
  13.         void open();
  14.         void close();
  15. }
复制代码
这里就涉及了第一个问题,当你设计时,开和关是两个功能,而这两个功能都是独立的,但是他们又属于电脑,这是电脑的开关,所以应该是属于电脑的功能。但是换个角度,开关也不见得是电脑独有的,电视也有开关。而不论是抽象类,还是接口似乎都能达到目的。我们这么来弄
  1. package org.it;


  2. public class MyComputer extends Computer
  3. {
  4.        
  5. }

  6. abstract class Computer implements PowerSwitch
  7. {
  8.        
  9. }

  10. interface  PowerSwitch
  11. {
  12.         void open();
  13.         void close();
  14. }
复制代码
这里电源开关是一个接口,任何东西实现他都能具有开关功能,所以我们采用一个抽象类Computer实现它,这样我的电脑就也具有了这个电源开关的功能。这样看起来更合逻辑,因为电源开关可以被比如电视类实现然后继续派生知道长虹 tcl的具体的实体类等等,而不是长虹电视还要继承Computer类来获得电源开关这个功能,难道世界乱套了?
这里涉及到第二个问题,电脑分很多种,比如华硕的,比如联想的,比如等等。
  1. package org.it;


  2. class AsusComputer extends Computer
  3. {

  4.         @Override
  5.         public void open()
  6.         {
  7.        
  8.         }

  9.         @Override
  10.         public void close()
  11.         {
  12.        
  13.         }
  14. }

  15. class LenovoComputer extends Computer
  16. {

  17.         @Override
  18.         public void open()
  19.         {
  20.        
  21.         }

  22.         @Override
  23.         public void close()
  24.         {
  25.        
  26.         }
  27. }

  28. abstract class Computer implements PowerSwitch
  29. {
  30.        
  31. }

  32. interface  PowerSwitch
  33. {
  34.         void open();
  35.         void close();
  36. }
复制代码
写到这真就是蛋疼死了,为什么呢,因为你马上就要被疯狂的代码给累死,假设我们要根据不同的品牌做不一样的电源开源,比如有的带电压保险,比如有的可能还会同时打开显示器,在比如。。。我靠,如果有100个品牌,我们得写多少代码,那不是苦逼死了。我们可以整理下思路,好好想想怎么能让工作轻松些,这似乎是个不错的主意。
  1. class AsusComputer extends Computer
  2. {

  3.         @Override
  4.         public void open()
  5.         {
  6.                 //给电脑接通电源
  7.                 //检查电压,如果超标则断开电源
  8.         }

  9.         @Override
  10.         public void close()
  11.         {
  12.                 //关闭电脑电源
  13.         }
  14. }

  15. class LenovoComputer extends Computer
  16. {

  17.         @Override
  18.         public void open()
  19.         {
  20.                 //给电脑接通电源
  21.                 //给显示器接通电源
  22.         }

  23.         @Override
  24.         public void close()
  25.         {
  26.                 //关闭电脑电源
  27.         }
  28. }
复制代码
这里没写具体代码,都是拿注释表示一下,嗯,应该都能明白是什么意思,我们可以仔细看下,其实不管什么牌子型号的电脑,肯定打开电脑电源和关闭电脑电源都是肯定有的。那么抽象的思想是什么?是不是把那些共有的抽离出去?那么代码为什么就不行?貌似谁也没说过对吧,so,我们这样
  1. class AsusComputer extends Computer
  2. {

  3.         @Override
  4.         public void open()
  5.         {
  6.                 //检查电压,如果超标则断开电源
  7.                 super.open();
  8.         }
  9. }

  10. class LenovoComputer extends Computer
  11. {

  12.         @Override
  13.         public void open()
  14.         {
  15.                 //给显示器接通电源
  16.                 super.open();
  17.         }
  18. }

  19. abstract class Computer implements PowerSwitch
  20. {
  21.         public void open()
  22.         {
  23.                 //给电脑接通电源
  24.         }
  25.        
  26.         public void close()
  27.         {
  28.                 //关闭电脑电源
  29.         }
  30. }
复制代码
从代码我们开的出来,所有相同的代码我们全部都抽离到了Computer类里边,谢天谢地,终于不用写100次的打开电脑电源和关闭电源的代码了,话说就算你复制粘贴的话200遍这也不是啥事情吧。那么现在有个问题,既然我们已经这么抽离出来了,是不是所有的电脑都具备了电源开关?我们是否还是有必要让Computer类实现PowerSwitch类呢,这里就看我们是否有其他需求,比如说我还有电视要用这个功能,那么我们可以把他留下用来理解设计意图,但我们没有电视这个类,所以我们可以把这个实现接口去掉,因为这个Computer已经具有了这个电源开关的功能。
还有一个原因等下说,我们在考虑下,假设华硕的电脑有摄像头,联想的没有,这种情况下怎么办?我们肯定不能写在Computer类里,对吧,因为你写了,那么所有的电脑就都有摄像头了,这不合规矩。那么只能用接口来实现了,谁实现了谁就有摄像头。
  1. package org.it;


  2. class AsusComputer extends Computer implements Camera
  3. {

  4.         @Override
  5.         public void open()
  6.         {
  7.                 //检查电压,如果超标则断开电源
  8.                 super.open();
  9.         }
  10.        
  11.         @Override
  12.         public void Photograph()
  13.         {
  14.                 //进行拍照
  15.         }
  16. }

  17. class LenovoComputer extends Computer
  18. {

  19.         @Override
  20.         public void open()
  21.         {
  22.                 //给显示器接通电源
  23.                 super.open();
  24.         }
  25. }

  26. abstract class Computer
  27. {
  28.         public void open()
  29.         {
  30.                 //给电脑接通电源
  31.         }
  32.        
  33.         public void close()
  34.         {
  35.                 //关闭电脑电源
  36.         }
  37. }
  38. interface Camera
  39. {
  40.         void Photograph();
  41. }
复制代码
到这里我相信大家应该差不多也明白了什么情况做什么选择,以及抽象类的非抽象方法的应用价值。
简单的来说就是,所有的子类所必须共有的成员以及方法,以及方法的实现代码都要抽离到抽象类里,而子类不必要的方法功能可以通过接口来实现,这在理解上有本质的区别。
抽象类的实体方法可以用来简化编写,便于维护,所以我们还是应该尽量的去考虑抽象类的派生类具体情况来抽离代码进行重构,但有一点java是单继承的,所以那些不能依赖于此的功能抽象应该选择接口。
抽象类的非抽象方法实现还可以在必要时给予派生类一些基本功能,嗯,这个是亮点,比起接口的空的方法还必须要实现一个空的看起来爽多了。
接口有利于更加抽象便捷的理解设计意图,但是不允许实体方法实现这个是个弊端,会造成实现该接口的大量派生类产生非常多的重复代码,这不便于维护。
重构这个东西,很多人理解成在设计时尽量考虑的很周全,这样有时候会浪费时间,为什么呢,代码->重构->模式  这种套路在实际开发中见的更多些,但提前做一些预期总会简便很多。

由于我的层次比较低,关于这些的更高级应用思想我也不知道,呵呵

话说老房你在问这么蛋疼的问题,我真心的爆你菊花。。。
作者: 田斌    时间: 2012-3-25 03:34
华丽丽的思想,mark
作者: 李成    时间: 2012-3-25 04:13
赞一个!~ 大晚上写这么多给力!~ 看完我也累了 睡觉了!
作者: 袁野    时间: 2012-3-25 06:58
呵呵 学习了




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2