`
renjieguixiong5
  • 浏览: 74505 次
  • 性别: Icon_minigender_2
  • 来自: 北京
社区版块
存档分类
最新评论

代码复用的规则

阅读更多

代码复用是绝大多数程序员所期望的,也是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);
}
}

作者:李炜
北京杰合伟业软件公司产品技术部经理
2001 年 7 月
本文来自《IBM DeveloperWorks》

分享到:
评论

相关推荐

    第5章 函数和代码复用.pdf

    ### 第5章 函数和代码复用 #### 知识点概述 本章节主要围绕着函数的概念、定义、使用以及代码复用的方式展开讨论。针对Delphi环境下的Python语言程序设计,深入剖析了如何利用函数实现更高效、灵活的编程实践。 #...

    面向Linux的内核级代码复用攻击检测技术.pdf

    该方法首先基于代码复用攻击的原理和正常程序的控制流构建CFI(Control Flow Integrity)约束规则。控制流完整性是一种用于防止代码被恶意篡改的技术,它确保程序执行按照预期的控制流进行。接着,文章提出了一种...

    第5周函数和代码复用.docx

    在Python编程中,函数和代码复用是核心概念,它们有助于编写简洁、高效且易于维护的代码。以下是根据提供的文件内容解析出的相关知识点: 1. **递归函数**: - 递归函数是一种在其定义中调用自己的函数。 - **基...

    Yii2扩展开发与代码复用:以资源包为例

    Yii2扩展开发与代码复用是当前流行的PHP开发框架Yii2的一个重要实践领域,特别是在资源包的开发和复用方面,本文将深入探讨如何利用资源包来管理和维护前端资源,以及如何通过扩展的形式实现代码的复用。 在Yii2...

    PHP 实现代码复用的一个方法 traits新特性_.docx

    在 PHP 语言中,代码复用是一个重要的编程原则,它有助于提高代码的效率和可维护性。自 PHP 5.4.0 版本起,PHP 引入了一种新的代码复用机制,称为 Traits。Traits 是一种为了解决单继承限制而设计的特性,它允许...

    测验5: 函数和代码复用 (第5周)

    本测验主要围绕函数和代码复用的主题展开,涵盖了一些基本的函数使用规则和递归的概念。 1. **函数的作用**: - **复用代码**:函数的主要优点之一就是可以避免重复代码,提升开发效率。 - **降低编程复杂度**:...

    rulz可复用的JUnit规则

    【标题】"rulz可复用的JUnit规则" 是一个专门为Java开发人员设计的测试框架扩展,它提供了丰富的JUnit规则库,使得单元测试更加灵活和可维护。JUnit是Java领域广泛使用的自动化测试框架,而rulz则进一步提升了其功能...

    stm32 GPIO复用表

    在实际应用中,通常通过STM32CubeMX这类工具或直接在代码中设置寄存器来配置这些复用功能,以达到设计要求。正确使用这些高级特性,可以在硬件资源有限的情况下,扩展微控制器的应用范围,提高系统的灵活性和性能。

    代码欣赏代码欣赏代码欣赏

    8. **代码复用与封装**:查看代码中是否有良好的复用机制,如函数、类的封装,以及是否有效地利用了继承和多态性。 9. **版本控制**:如果代码是从版本控制系统(如Git)导出的,我们可以分析提交历史,了解代码的...

    精品网不规则焦点图广告代码.rar

    9. **模块化编程**:可能采用了模块化设计,如ES6的import/export,便于代码管理和复用。 10. **错误处理**:适当的错误检测和处理,确保代码的健壮性。 学习并理解这个代码,对于提升前端开发者在动态效果和用户...

    书写和存储可复用的Fortran90Style代码准则.docx

    ### 书写和存储可复用的Fortran90Style代码准则 #### 1. 引言 本准则旨在为欧洲气象组织提供一个统一的Fortran 90编程框架,以促进不同组织间代码的交流与复用。通过建立一套标准化的文档规范与代码编写准则,不仅...

    基于构件的软件复用技术研究与应用实践实用文档doc.doc

    代码复用是最基础的层次,通过直接使用或修改已有的源代码或目标代码。设计复用涉及复用设计模式和模板,减少了对实现环境的依赖。分析复用则更进一步,复用的是对问题域的理解和解决方案,抽象程度更高。 复用技术...

    中国电信面向5G前传的无源彩光粗波分复用设备技术要求.pdf

    在缩略语和术语方面,标准中详细界定了面向5G前传的无源彩光粗波分复用设备的组成部分和光模块的应用代码,包括网络位置、设备管理、光模块分类以及应用代码的详细描述。 技术要求部分,包括了极限要求、推荐工作...

    计算机源代码编写规范(VB编码规范).rar

    以上只是VB编码规范的一部分要点,实践中还会涉及更多细节,如代码复用、异常处理策略、代码审查等。遵循这些规范,可以使VB代码更易于理解,更健壮,同时也为团队合作提供了统一的标准。通过不断学习和实践,开发者...

    PHP Trait代码复用类与多继承实现方法详解

    这样,开发者可以更灵活地组织代码,使得代码复用和扩展更加方便。 Trait的使用方法非常简单。首先定义一个或多个Trait,每个Trait可以包含属性和方法,就像一个普通的类一样。然后在需要使用Trait的类中,通过use...

    PHP 实现代码复用的一个方法 traits新特性

    Traits是一种特殊的代码复用机制,旨在减少单继承语言的限制,它允许开发人员在一个类中使用多个特性(traits),而这些特性则包含可供重用的方法集合。 ### Traits基础 Traits可以被看作是包含方法的“模块”,...

Global site tag (gtag.js) - Google Analytics