`
junli0411
  • 浏览: 136177 次
  • 来自: ...
社区版块
存档分类
最新评论

面向对象之代码复用规则

阅读更多
 代码复用是绝大多数程序员所期望的,也是OO的目标之一。总结我多年的编码经验,为了使代码能够最大程度上复用,应该特别注意以下几个方面。

1、 对接口编程
 
  "对接口编程"是面向对象设计(OOD)的 第一个基本原则。它的含义是:使用接口和同类型的组件通讯,即,对于所有完成相同功能的组件,应该抽象出一个接口,它们都实现该接口。具体到JAVA中, 可以是接口(interface),或者是抽象类(abstract class),所有完成相同功能的组件都实现该接口,或者从该抽象类继承。我们的客户代码只应该和该接口通讯,这样,当我们需要用其它组件完成任务时,只 需要替换该接口的实现,而我们代码的其它部分不需要改变!

  当现有的组件不能满足要求时,我们可以创建新的组件,实现该接口,或者,直接对现有的组件进行扩展,由子类去完成扩展的功能。

2、 优先使用对象组合,而不是类继承
 
  "优先使用对象组合,而不是类继承"是面向对象设计的第二个原则。并不是说继承不重要,而是因为每个学习OOP的人都知道OO的基本特性之一就是继承,以至于继承已经被滥用了,而对象组合技术往往被忽视了。下面分析继承和组合的优缺点:

  类继承允许你根据其他类的实现来定义一个类的实现。这种通过生成子类的复用通常被称为白箱复用(white-box reuse)。术语"白箱"是相对可视性而言:在继承方式中,父类的内部细节对子类可见。

  对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组合对象来获得。对象组合要求对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为被组合的对象的内部细节是不可见的。对象只以"黑箱"的形式出现。

  继承和组合各有优缺点。类继承是在编译时刻静态定义的,且可直接使用,类继承可以较方便地改变父类的实现。但是类继 承也有一些不足之处。首先,因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现。更糟的是,父类通常至少定义了子类的部分行为,父类的 任何改变都可能影响子类的行为。如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复 用性。

  对象组合是通过获得对其他对象的引用而在运行时刻动态定义的。由于组合要求对象具有良好定义的接口,而且,对象只能 通过接口访问,所以我们并不破坏封装性;只要类型一致,运行时刻还可以用一个对象来替代另一个对象;更进一步,因为对象的实现是基于接口写的,所以实现上 存在较少的依赖关系。

  优先使用对象组合有助于你保持每个类被封装,并且只集中完成单个任务。这样类和类继承层次会保持较小规模,并且不太 可能增长为不可控制的庞然大物(这正是滥用继承的后果)。另一方面,基于对象组合的设计会有更多的对象(但只有较少的类),且系统的行为将依赖于对象间的 关系而不是被定义在某个类中。

  注意:理想情况下,我们不用为获得复用而去创建新的组件,只需要使用对象组合技术,通过组装已有的组件就能获得需要 的功能。但是事实很少如此,因为可用的组件集合并不丰富。使用继承的复用使得创建新的组件要比组装已有的组件来得容易。这样,继承和对象组合常一起使用。 然而,正如前面所说,千万不要滥用继承而忽视了对象组合技术。

  相关的设计模式有:Bridge、Composite、Decorator、Observer、Strategy等。

  下面的例子演示了这个规则,它的前提是:我们对同一个数据结构,需要以任意的格式输出。

  第一个例子,我们使用基于继承的框架,可以看到,它很难维护和扩展。
  abstract class AbstractExampleDocument
  {
   // skip some code ...
    public void output(Example structure)
    {
     if( null != structure )
      {
       this.format( structure );
      }
    }
    protected void format(Example structure);
  }

 


  第二个例子,我们使用基于对象组合技术的框架,每个对象的任务都清楚的分离开来,我们可以替换、扩展格式类,而不用考虑其它的任何事情。
  class DefaultExampleDocument
  {
   // skip some code ...
   public void output(Example structure)
   {
     ExampleFormatter formatter =
     (ExampleFormatter) manager.lookup(Roles.FORMATTER);
     if( null != structure )
     {
       formatter.format(structure);
     }
   }
  }


  这里,用到了类似于"抽象工厂"的组件创建模式,它将组件的创建过程交给manager来完成;ExampleFormatter是所有格式的抽象父类;

3、 将可变的部分和不可变的部分分离
 
  "将可变的部分和不可 变的部分分离"是面向对象设计的第三个原则。如果使用继承的复用技术,我们可以在抽象基类中定义好不可变的部分,而由其子类去具体实现可变的部分,不可变 的部分不需要重复定义,而且便于维护。如果使用对象组合的复用技术,我们可以定义好不可变的部分,而可变的部分可以由不同的组件实现,根据需要,在运行时 动态配置。这样,我们就有更多的时间关注可变的部分。

  对于对象组合技术而言,每个组件只完成相对较小的功能,相互之间耦合比较松散,复用率较高,通过组合,就能获得新的功能。

4、 减少方法的长度
 
  通常,我们的方法应该只有尽量少的几行,太长的方法会难以理解,而且,如果方法太长,则应该重新设计。对此,可以总结为以下原则:

三十秒原则:
  如果另一个程序员无法在三十秒之内了解你的函数做了什么(What),如何做(How)以及为什么要这样做(Why),那就说明你的代码是难以维护的,必须得到提高;
一屏原则:
如果一个函数的代码长度超过一个屏幕,那么或许这个函数太长了,应该拆分成更小的子函数;
一行代码尽量简短,并且保证一行代码只做一件事

那种看似技巧性的冗长代码只会增加代码维护的难度。

5、 消除case / if语句
 
  要尽量避免在代码中出现判 断语句,来测试一个对象是否某个特定类的实例。通常,如果你需要这么做,那么,重新设计可能会有所帮助。我在工作中遇到这样的一个问题:我们在使用 JAVA做XML解析时,对每个标签映射了一个JAVA类,采用SAX(简单的XML接口API:Simple API for XML)模型。结果,代码中反复出现了大量的判断语句,来测试当前的标签类型。为此,我们重新设计了DTD(文档类型定义:Document Type Definition),为每个标签增加了一个固定的属性:classname,而且重新设计了每个标签映射的JAVA类的接口,统一了每个对象的操作: addElement(Element aElement); //增加子元素
addAttribute(String attName, String attValue); //增加属性;

则彻底消除了所有的测试当前的标签类型的判断语句。每个对象通过 Class.forName(aElement.attributes.getAttribute("classname")).newInstence(); 动态创建,

6、 减少参数个数
 
  有大量参数需要传递的方法,通常很难阅读。我们可以将所有参数封装到一个对象中来完成对象的传递,这也有利于错误跟踪。

许多程序员因为,太多层的对象包装对系统效率有影响。是的,但是,和它带来的好处相比,我们宁愿做包装。毕竟,"封装"也是OO的基本特性之一,而且,"每个对象完成尽量少(而且简单)的功能",也是OO的一个基本原则。

7、 类层次的最高层应该是抽象类。
 
  在许多情况下,提供一个抽象基类有利做特性化扩展。由于在抽象基类中,大部分的功能和行为已经定义好,使我们更容易理解接口设计者的意图是什么。

由于JAVA不允许"多继承",从一个抽象基类继承,就无法再从其它基类继承了。所以,提供一个抽象接口(interface)是个好主意,一个类可以实现多个接口,从而模拟实现了"多继承",为类的设计提供了更大的灵活性。

8、 尽量减少对变量的直接访问。
 
  对数据的封装原则应该规范化,不要把一个类的属性暴露给其它类,而是应该通过访问方法去保护他们,这有利于避免产生波纹效应。如果某个属性的名字改变,你只需要修改它的访问方法,而不是修改所有相关的代码。

9、 子类应该特性化,完成特殊功能。
 
  如果一个子类只是使一个组件变成组件管理器,而不是实现接口功能,或者,重载某个功能,那么,就应该使用一个外部的容器类,而不是创建一个子类。

  建议:类层次结构图,不要太深;

  例如:下面的接口定义了组件的功能:发送消息;类Transceiver实现了该接口;而其子类Pool只是管理多个Transceiver对象,而没有提供自己的接口实现。建议使用组合方式,而不是继承!


  public interface ITransceiver{
    public abstract send(String msg);
  }

  public class Transceiver implements ITransceiver {
    public send(String msg){
      System.out.println(msg);
    }
  }

//使用继承方式的实现
  public class Pool extends Transceiver{
    private List pool = new Vector();
    public void add(Transceiver aTransceiver){
      pool.add(aTransceiver);
    }
    public Transceiver get(int index){
      pool.get(index);
    }
  }

//使用组合方式的实现
  public class Pool {
    private List pool = new Vector();
    public void add(Transceiver aTransceiver){
      pool.add(aTransceiver);
    }
    public Transceiver get(int index){
      pool.get(index);
    }
  }

 

10、 拆分过大的类。
 
  如果一个类有太多的方法(超过50个),那么它可能要做的工作太多,我们应该试着将它的功能拆分到不同的类中,类似于规则四。

11、 作用截然不同的对象应该拆分。
 
  在构建的过程中,你有时会遇到这样的问题:对同样的数据,有不同的视图。某些属性描述的是数据结构怎样生成,而某些属性描述的是数据结构本身。最好将这两个视图拆分到不同的类中,从类名上就可以区分出不同视图的作用。类的域、方法也应该有同样的考虑!

12、 尽量减少对参数的隐含传递。
 
  两个方法处理类内部同一个数据(域),并不意味着它们就是对该数据(域)做处理。许多时候,该数据(域)应该作为方法的参输入数,而不是直接存取,在工具类的设计中尤其应该注意。例如:
  public class Test{
    private List pool = new Vector();
    public void testAdd(String str){
      pool.add(str);
    }
    public Object testGet(int index){
      pool.get(index);
    }
  }

 

  两个方法都对List对象pool做了操作,但是,实际上,我们可能只是想对List接口的不同实现Vector、ArrayList等做存取测试。所以,代码应该这样写:
  public class Test{
    private List pool = new Vector();
    public void testAdd(List pool, String str){
      pool.add(str);
    }
    public Object testGet(List pool, int index){
      pool.get(index);
    }
  }

 

参考资料
设计模式
apache 小组的jakarta 项目的Avalon子项目(http://jakarta.apache.org/),是关于代码复用的,Commons子项目(http://jakarta.apache.org/commons/index.html)也有部分内容。

Thinking in Java

(转载文章请保留出处:北天JAVA技术网(www.java114.com))

分享到:
评论

相关推荐

    编写高质量代码-Web前端开发修炼之道.azw3

    5.4.3 用面向对象方式重写代码 5.5 其他问题 5.5.1 prototype和内置类 5.5.2 标签的自定义属性 5.5.3 标签的内联事件和event对象 5.5.4 利用事件冒泡机制 5.5.5 改变DOM样式的三种方式 附录A 写在规则前面的...

    【04-面向对象(上)】

    •理解封装:封装是面向对象的三大特征之一。 • 封装包含两方面含义:  –合理隐藏。  –合理暴露。 本文原创作者:pipi-changing 本文原创出处:http://www.cnblogs.com/pipi-changing/ 使用访问控制...

    从模式讲到设计模式再到面向对象设计模式

    “面向对象设计模式”是可复用面向对象软件的基础。三者的基本描述如下: 1、每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案。 2、设计模式描述了软件开发过程中某一类常见问题的一般性...

    编译原理(第2版)课件

    14.2.2 面向对象语言的有效类、延迟类及延迟成员 14.2.3 面向对象语言的类属类 14.2.4 面向对象语言的继承类 14.3 多态实例变量、多态引用的类型检查及绑定 14.3.1 实例变量和多态引用 14.3.2 静态类型检查及动态...

    Ruby的教程.txt

    函数与模块:学习如何定义和使用函数,了解模块的概念和用法,实现代码的复用和组织。 二、面向对象编程 类与对象:理解Ruby中的类和对象的概念,学习如何定义类、创建对象以及使用对象的属性和方法。 继承与...

    精通MFC (光盘) 源代码

    第1章 面向对象编程 1.1 面向对象的基本概念 1.1.1 类和对象 1.1.2 封装、多态和继承 1.1.3 消息 1.2 面向对象的建模技术UML 1.2.1 类图 1.2.2 交互图 1.2.3 用例图 1.3 面向对象的C++语言 1.3.1 C++对象...

    JAVA上百实例源码以及开源项目源代码

    16个目标文件 内容索引:Java源码,初学实例,二进制,文件复制 Java二进制IO类与文件复制操作实例,好像是一本书的例子,源代码有的是独立运行的,与同目录下的其它代码文件互不联系,这些代码面向初级、中级Java程序员...

    计算机语言分类.docx

    发展: 集成、可视的开发环境——结构化高级语言(在更抽象的层次上表达意图)——面向对象程序设计(支持此技术的程序设计语言,eiffel,c++,java) 趋势: 面向对象:提供简单的类机制以及动态的接口模型。...

    JAVA上百实例源码以及开源项目

     Java二进制IO类与文件复制操作实例,好像是一本书的例子,源代码有的是独立运行的,与同目录下的其它代码文件互不联系,这些代码面向初级、中级Java程序员。 Java访问权限控制源代码 1个目标文件 摘要:Java源码,...

    深入剖析C#继承机制

    北京火龙果软件工程技术中心内容导航一、继承... 继承是面向对象程序设计的主要特征之一,它可以让您重用代码,可以节省程序设计的时间。继承就是在类之间建立一种相交关系,使得新定义的派生类的实例可以继承已有的

    C#微软培训资料

    第九章 面向对象的程序设计 .101 9.1 面向对象的基本概念.101 9.2 对象的模型技术 .103 9.3 面向对象的分析 .105 9.4 面向对象的设计 .107 9.5 小 结 .110 第十章 类 .112 10.1 类 的 声 明 .112 ...

    Python语言程序设计PPT课件.zip

    面向过程的程序设计方法难以保证程序的安全性和代码的可重用性,而面向对象的程序设计方法能够更好地提高大型程序的质量和开发效率,增强程序的安全性和提高代码的可重用性。学习本章,重在理解面向对象程序设计思想...

    Python语言程序设计习题答案.zip

    面向过程的程序设计方法难以保证程序的安全性和代码的可重用性,而面向对象的程序设计方法能够更好地提高大型程序的质量和开发效率,增强程序的安全性和提高代码的可重用性。学习本章,重在理解面向对象程序设计思想...

    基于Java的猜拳小游戏设计.doc

    Java采用面向对象技术,所有的Java程序都是对象,通过封装性实现了模块化和信息隐 藏,通过继承性实现了代码的复用,使得用户可以根据自己的需要创建自己的类库。 Java最强大的是网络方面的应用,本课程设计不涉及...

    亮剑.NET深入体验与实战精要2

    1.4 .NET的面向对象之门 27 1.4.1 继承——“子承父业” 28 1.4.2 委托——“任务书” 35 1.4.3 事件——“年终分红” 42 1.4.4 反射——“解剖” 49 1.5 .NET开发几把小刀 52 1.5.1 using之多变身 52 1.5.2 @符号的...

    亮剑.NET深入体验与实战精要3

    1.4 .NET的面向对象之门 27 1.4.1 继承——“子承父业” 28 1.4.2 委托——“任务书” 35 1.4.3 事件——“年终分红” 42 1.4.4 反射——“解剖” 49 1.5 .NET开发几把小刀 52 1.5.1 using之多变身 52 1.5.2 @符号的...

    Java 语言基础 —— 非常符合中国人习惯的Java基础教程手册

    面向对象编程的基础 要了解面向对象编程(OOP)的基本概念,需要理解 OOP 的三个主要概念,它们撑起 了整个 OOP 的框架。这三个概念是:封装、继承性和多态性。除此以外,还需了解对象、 类、消息、接口、及抽象等...

    Python第一阶段笔记汇总.docx

    第二章 面向对象 Object Oriented 68 一 概述 68 (一)面向过程 68 (二)面向对象 68 二 类和对象 69 (一)语法 70 (二)实例成员 72 (三)类成员 75 (四)静态方法 76 (总结)类和对象 77 三 三大特征 78 (总结...

    java开源包8

    JSEditor 是 Eclipse 下编辑 JavaScript 源码的插件,提供语法高亮以及一些通用的面向对象方法。 Java数据库连接池 BoneCP BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接...

Global site tag (gtag.js) - Google Analytics