网站首页
JSP空间
动态资讯
开源项目
技术文档
资源下载
J2EE资源
客户论坛
在线支付
 
  技术文档>>JAVA>>新手入门>>基础入门>查看文档  
  敏捷开发的必要技巧:慎用继承     
  文章作者:未知  文章来源:水木森林  
  查看:74次  录入:管理员--2007-11-17  
  示例

  这是一个会议管理系统。用来管理各种各样的会议参与者信息。数据库里面有个表participants,里面的每条记录表示一个参会者。因为经常会发生用户误删掉某个参会者的信息。所以现在,用户删除时,并不会真的删除那参会者的信息,而只是将该记录的删除标记设为true。24小时以后,系统会自动将这条记录删除。但是在这24小时以内,如果用户改变主意了,系统还可以将这条记录还原,将删除标记设置为false。

  请认真的读下面的代码:

    
public class dbtable {                                                                         
       protected connection conn;                                                                  
       protected tablename;                                                                        
       public dbtable(string tablename) {                                                          
           this.tablename = tablename;                                                            
           this.conn = ...;                                                                        
       }                                                                                          
       public void clear() {                                                                      
           preparedstatement st = conn.preparestatement("delete from "+tablename);                
           try {                                                                                  
               st.executeupdate();                                                                
           }finally{                                                                              
               st.close();                                                                        
           }                                                                                      
       }                                                                                          
       public int getcount() {                                                                    
           preparedstatement st = conn.preparestatement("select count(*) from"+tablename); 
           try {                                                                                  
               resultset rs = st.executequery();                                                  
               rs.next();                                                                          
               return rs.getint(1);                                                                
           }finally{                                                                              
               st.close();                                                                        
           }                                                                                      
       }                                                                                          
    }                              
    
    public class participantsindb extends dbtable {                                                
       public participantsindb() {                                                                
           super("participants");                                                                  
       }                                                                                          
       public void addparticipant(participant part) {                                              
           ...                                                                                    
       }                                                                                          

       public void deleteparticipant(string participantid) {
           setdeleteflag(participantid, true);
       }                      
       public void restoreparticipant(string participantid) {
           setdeleteflag(participantid, false);
       }                      
       private void setdeleteflag(string participantid, boolean b) {
           ...                
       }                      
       public void reallydelete() {
           preparedstatement st = conn.preparestatement(
                             "delete from "+
                             tablename+
                             " where deleteflag=true");
           try {              
               st.executeupdate();
           }finally{          
               st.close();    
           }                  
       }                      
       public int countparticipants() {
           preparedstatement st = conn.preparestatement(
                             "select count(*) from "+
                             tablename+
                             " where deleteflag=false");
           try {              
               resultset rs = st.executequery();
               rs.next();    
               return rs.getint(1);
           }finally{          
               st.close();    
           }                  
       }                      
    }
                        

  注意到,countparticipants这个方法只计算那些deleteflags为false的记录。也就是,被删除的那些参会者不被计算在内。

  上面的代码看起来还不错,但却有一个很严重的问题。什么问题?先看看下面的代码:

    participantsindb partsindb = ...;
    participant kent = new participant(...);
    participant paul = new participant(...);
    partsindb.clear();        
    partsindb.addparticipant(kent);
    partsindb.addparticipant(paul);
    partsindb.deleteparticipant(kent.getid());
    system.out.println("there are "+partsindb.getcount()+ "participants");



  最后一行代码,会打印出"there are 1 participants"这样信息,对不?错!它打印的是"there are 2 participants"!因为最后一行调用的是dbtable里面的这个方法getcount,而不是participantsindb的countparticipants。getcount一点都不知道删除标记这回事,它只是简单的计算记录数量,并不知道要计算那些真正有效的参会者(就是删除标记为false的)。

继承了一些不合适(或者没用的)的功能

  participantsindb继承了来自dbtable的方法,比如clear和getcount。对于participantsindb来讲,clear这个方法的确是有用的:清空所有的参会者。但getcount就造成了一点点小意外了:通过participantsindb调用getcount这个方法时,是取得participants这个表里面所有的记录,不管删除标记是true还是false的。而实际上,没人想知道这个数据。即使有人想知道,这个方法也不应该叫做getcount,因为这名字很容易就会跟“计算所有的(有效)参会者数量”联系在一起。

  因此,participantsindb是不是真的应该继承这个方法getcount呢?或者我们应该怎么做比较恰当呢?

它们之间是否真的有继承关系?

  当我们继承了一些我们不想要的东西,我们应该再三的想想:它们之间是不是真的有继承关系?participantsindb必须是一个dbtable吗?participantsindb希不希望别人知道它是一个dbtable?

  实际上,participantsindb描述的是系统中所有的参会者的集合,该系统可以是个单数据库的,也可以是多数据库的,也就是说,这个类可以代表一个数据库里的一个participants表,也可以代表两个数据库各自的两个participants表的总和。


  如果还不清楚的话,我们就这样举例吧,比如,现在我们已经有了2000个参会者,在两个数据库中存放,其中数据库a的participants表里面存放了1000个参会者,数据库b的participants这个表存放了1000个参会者。dbtable顶多只能描述一个数据库里面的一张表,也就是1000个参会者,而participants则可以完全的描述这2000年参会者的信息。前面可以当作数据库的数据表在系统中的代表,而后者表示的应该包含更多业务逻辑的一个域对象。(原谅这边我只能用域对象这样的词来断开这样的混淆。)

  因此,我们可以判断,participantsindb跟dbtable之间不应该有什么继承的关系。participantsindb不能继承dbtable这个类了。于是,现在participantsindb也没有getcount这个方法了。可是participantsindb还需要dbtable类里面的其他方法啊,那怎么办?所以现在我们让participantsindb里面引用了一个dbtable:                                  
                                                                          

    public class dbtable {                                                                         
       private connection conn;                                                                    
       private string tablename;                                                                  
       public dbtable(string tablename) {                                                          
           this.tablename = tablename;                                                            
           this.conn = ...;                                                                        
       }                                                                                          
       public void clear() {                                                                      
           preparedstatement st = conn.preparestatement("delete from "+tablename);                
           try {                                                                                  
               st.executeupdate();                                                                
           }finally{                                                                              
               st.close();                                                                        
           }                                                                                      
       }                                                                                          
       public int getcount() {                                                                    
           preparedstatement st = conn.preparestatement("select count(*) from "+tablename);
           try {                                                                                  
               resultset rs = st.executequery();                                                  
               rs.next();                                                                          
               return rs.getint(1);
          }finally{            
               st.close();      
          }                    
       }                      
       public string gettablename() {
          return tablename;    
       }                      
       public connection getconn() {
          return conn;        
       }                      
   }                          
  
   public class participantsindb {
       private dbtable table;  
       public participantsindb() {
          table = new dbtable("participants");
       }                      
       public void addparticipant(participant part) {
          ...                  
       }                      
       public void deleteparticipant(string participantid) {
          setdeleteflag(participantid, true);
       }                      
       public void restoreparticipant(string participantid) {
          setdeleteflag(participantid, false);
       }                      
       private void setdeleteflag(string participantid, boolean b) {
          ...                  
       }                      
       public void reallydelete() {
          preparedstatement st = table.getconn().preparestatement(
                              "delete from "+
                              table.gettablename()+
                              " where deleteflag=true");
          try {                
              st.executeupdate();
          }finally{            
              st.close();      
          }                    
       }                      
       public void clear() {  
          table.clear();      
       }                      
       public int countparticipants() {
          preparedstatement st = table.getconn().preparestatement(
                              "select count(*) from "+
                              table.gettablename()+
                              " where deleteflag=false");
          try {                
              resultset rs = st.executequery();
              rs.next();      
              return rs.getint(1);
          }finally{            
              st.close();      
          }                    
       }                      
   }
                          

    participantsindb不再继承dbtable。代替的,它里面有一个属性引用了一个dbtable对象,然后调用这个dbtable的clear, getconn, gettablename 等等方法。

代理(delegation)    
  
  其实我们这边可以看一下participantsindb的clear方法,这个方法除了直接调用dbtable的clear方法以外,什么也没做。或者说,participantsindb只是做为一个中间介让外界调用dbtable的方法,我们管这样传递调用的中间介叫“代理(delegation)”。            

  现在,之前有bug的那部分代码就编译不过了:

    participantsindb partsindb = ...;                                                              
    participant kent = new participant(...);                                                      
    participant paul = new participant(...);                                                      
    partsindb.clear();                                                                            
    partsindb.addparticipant(kent);                                                                
    partsindb.addparticipant(paul);                                                                
    partsindb.deleteparticipant(kent.getid());                                                    
    //编译出错:因为在participantsindb里面已经没有getcount这个方法了!
    system.out.println("there are "+partsindb.getcount()+ "participants");        

                

  总结一下:首先,我们发现,participantsindb 和 dbtablein之间没有继承关系。然后我们就将“代理”来取代它们的继承。“代理”的优点就是,我们可以控制dbtable的哪些方法可以“公布(就是设为public)”(比如clear方法)。如果我们用了继承的话,我们就没得选择,dbtable里面的所有public方法都要对外公布!


抽取出父类中没必要的功能

  现在,我们来看一下另一个例子。假定一个component代表一个gui对象,比如按钮或者文本框之类的。请认真阅读下面的代码:

    abstract class component {                                                                     
       boolean isvisible;                                                                          
       int posxincontainer;                                                                        
       int posyincontainer;                                                                        
       int width;                                                                                  
       int height;                                                                                
       ...                                                                                        
       abstract void paint(graphics graphics);                                                    
       void setwidth(int newwidth) {                                                              
           ...                                                                                    
       }                                                                                          
       void setheight(int newheight) {                                                            
           ...                                                                                    
       }                                                                                          
    }                
    
    class button extends component {                                                              
       actionlistener listeners[];                                                                
       ...                                                                                        
       void paint(graphics graphics) {                                                            
           ...                                                                                    
       }                                                                                          
    }                                                                                              

    class container {
       component components[];
       void add(component component) {
           ...
       }    
    }
      

  假定你现在要写一个时钟clock组件。它是一个有时分针在转动的圆形的钟,每次更新时针跟分针的位置来显示当前的时间。因为这也是一个gui组件,所以我们同样让它继承自component类:

    class clockcomponent extends component {
       ...  
       void paint(graphics graphics) {
           //根据时间绘制当前的钟表图形
       }    
    }    
  

  现在我们有一个问题了:这个组件应该是个圆形的,但是它现在却继承了component的width跟height属性,也继承了setwidth 和 setheight这些方法。而这些东西对一个圆形的东西是没有意义的。

  当我们让一个类继承另一个类时,我们需要再三的想想:它们之间是否有继承关系?clockcomponent是一个component吗?它跟其他的compoent(比如button)是一样的吗?

  跟participantsindb的那个案例相反的是,我们不得不承认clockcomponent确实也是一个component,否则它就不能像其他的组件那样放在一个container中。因此,我们只能让它继承component类(而不是用“代理”)。

  它既要继承component,又不要width, height, setwidth 和 setheight这些,我们只好将这四样东西从component里面拿走。而事实上,它也应该拿走。因为已经证明了,并不是所有的组件都需要这四样东西(至少clockcomponent不需要)。

如果一个父类描述的东西不是所有的子类共有的,那这个父类的设计肯定不是一个好的设计。

我们有充分的理由将这些移走。

  只是,如果我们从component移走了这四样东西,那原来的那些类,比如button就没了这四样东西,而它确实又需要这些的(我们假定按钮是方形的)。

  一个可行的方案是,创建一个rectangularcomponent类,里面有width,height,setwidth和setheight这四样。然后让button继承自这个类:

    abstract class component {
       boolean isvisible;
       int posxincontainer;
       int posyincontainer;
       ...  
       abstract void paint(graphics graphics);
    }      

    abstract class rectangularcomponent extends component {
       int width;
       int height;                                                                                
       void setwidth(int newwidth) {                                                              
           ...                                                                                    
       }                                                                                          
       void setheight(int newheight) {                                                            
           ...                                                                                    
       }                                                                                          
    }    
    
    class button extends rectangularcomponent {                                                    
       actionlistener listeners[];                                                                
       ...                                                                                        
       void paint(graphics graphics) {                                                            
           ...                                                                                    
       }                                                                                          
    }              
    
    class clockcomponent extends component {                                                      
       ...                                                                                        
       void paint(graphics graphics) {                                                            
           //根据时间绘制当前的钟表图形                                                      
       }                                                                                          
    }
                                                                                            

  这并不是唯一可行的方法。另一个可行的方法是,创建一个rectangulardimension,这个类持有这四个功能,然后让button去代理这个类:

    abstract class component {                                                                     
       boolean isvisible;                                                                          
       int posxincontainer;                                                                        
       int posyincontainer;                                                                        
       ...                                                                                        
       abstract void paint(graphics graphics);                                                    
    }                
    
    class rectangulardimension {                                                                  
       int width;                                                                                  
       int height;                                                                                
       void setwidth(int newwidth) {                                                              
           ...                                                                                    
       }                                                                                          
       void setheight(int newheight) {                                                            
           ...                                                                                    
       }                                                                                          
    }      
    
    class button extends component {                                                              
       actionlistener listeners[];                                                                
       rectangulardimension dim;                                                                  
       ...                                                                                        
       void paint(graphics graphics) {                                                            
           ...                                                                                    
       }                                                                                          
       void setwidth(int newwidth) {                                                              
           dim.setwidth(newwidth);                                                                
       }                                                                                          
       void setheight(int newheight) {                                                            
           dim.setheight(newheight);                                                              
       }                                                                                          
    }          
    
    class clockcomponent extends component {                                                      
       ...                                                                                        
       void paint(graphics graphics) {
           //根据时间绘制当前的钟表图形
       }    
    }  
    

总结    

  当我们想要让一个类继承自另一个类时,我们一定要再三的检查:子类会不会继承了一些它不需要的功能(属性或者方法)?如果是的话,我们就得认真再想想:它们之间有没有真正的继承关系?如果没有的话,就用代理。如果有的话,将这些不用的功能从基类转移到另外一个合适的地方去。

引述

  里斯科夫替换原则(lsp)表述:subtype must be substitutable for their base types. 子类应该能够代替父类的功能。或者直接点说,我们应该做到,将所有使用父类的地方改成使用子类后,对结果一点影响都没有。或者更直白一点吧,请尽量不要用重载,重载是个很坏很坏的主意!更多的信息可以去:

http://www.objectmentor.com/resources/articles/lsp.pdf.
http://c2.com/cgi/wiki?liskovsubstitutionprinciple.

design by contract是个跟lsp有关的东西。它表述说,我们应该测试我们所有的假设。更多信息:

http://c2.com/cgi/wiki?designbycontract.

系列的pdf下载:
敏捷开发的必要技巧:http://www.blogjava.net/files/wingel/%e6%95%8f%e6%8d%b7%e5%bc%80%e5%8f%91%e7%9a%84%e5%bf%85%e8%a6%81%e6%8a%80%e5%b7%a7%e5%ae%8c%e6%95%b4%e7%89%88.rar

 
 
上一篇: junit 的使用经验总结    下一篇: log4j的使用总结!(用于收藏救急用)
  相关文档
toexponential 方法 11-16
java web start介绍 11-17
struts的动态表单的应用 11-17
导入:采用jsi封装、集成第三方类库 11-17
j2ee全实例教程 11-17
java mail api及其应用 —— 一个邮件列表服务器的实现 (三).. 11-17
tomcat的sql server数据源的配置 11-17
nhibernate简介 - 什么是nhibernate 11-17
在jar包中动态载入第三方jar class问题 11-17
linux下java awt 中中文的处理 11-17
3d编程指南第四部分:m3g内建碰撞、光照物理学和照相机视点 11-17
在java applet中如何显示另外一个html页面? 11-17
java socket编程(二)-2 11-17
基础入门:java学习路径七步走 11-17
eclipse插件管理小结 11-17
nhibernate源代码浅读 11-17
用jbuilder9 开发struts实例 11-17
用javamail写的邮件接收程序 11-16
利用javamail收取hotmail的退信 11-16
谈模式(singletonpattern)的变形 11-17
返回首页 | 关于我们 | J网章程 | JSP空间合租 | 客服中心 | 免责声明 | 常见问题 | 参观机房
本站主机空间代理至厦门市华众网络科技有限公司
《中华人民共和国增值电信业务经营许可证》
编号:闽B2-20050079
@2005-2008福建JSP技术网 版权所有 闽ICP备05000928号
技术电话:13616026886
邮箱:admin@fjjsp.com 站长QQ,点击这里给我发消息