电脑故障

位置:IT落伍者 >> 电脑故障 >> 浏览文章

代码复用的规则


发布日期:2020/1/14
 

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

对接口编程

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

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

优先使用对象组合而不是类继承

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

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

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

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

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

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

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

相关的设计模式有BridgeCompositeDecoratorObserverStrategy等

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

第一个例子我们使用基于继承的框架可以看到它很难维护和扩展

abstract class AbstractExampleDocument

{

// skip some code

public void output(Example structure)

{

if( null != structure )

{

thisformat( structure );

}

}

protected void format(Example structure);

}

第二个例子我们使用基于对象组合技术的框架每个对象的任务都清楚的分离开来我们可以替换扩展格式类而不用考虑其它的任何事情

class DefaultExampleDocument

{

// skip some code

public void output(Example structure)

{

ExampleFormatter formatter =

(ExampleFormatter) managerlookup(RolesFORMATTER);

if( null != structure )

{

formatterformat(structure);

}

}

}

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

将可变的部分和不可变的部分分离

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

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

减少方法的长度

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

三十秒原则

如果另一个程序员无法在三十秒之内了解你的函数做了什么(What)如何做(How)以及为什么要这样做(Why)那就说明你的代码是难以维护的必须得到提高

一屏原则

如果一个函数的代码长度超过一个屏幕那么或许这个函数太长了应该拆分成更小的子函数

一行代码尽量简短并且保证一行代码只做一件事

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

消除case / if语句

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

addAttribute(String attName String attValue); //增加属性

则彻底消除了所有的测试当前的标签类型的判断语句每个对象通过 ClassforName(aElementattributesgetAttribute(classname))newInstence(); 动态创建

减少参数个数

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

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

类层次的最高层应该是抽象类

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

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

尽量减少对变量的直接访问

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

子类应该特性化完成特殊功能

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

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

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

public interface ITransceiver{

public abstract send(String msg);

}

public class Transceiver implements ITransceiver {

public send(String msg){

Systemoutprintln(msg);

}

}

//使用继承方式的实现

public class Pool extends Transceiver{

private List pool = new Vector();

public void add(Transceiver aTransceiver){

pooladd(aTransceiver);

}

public Transceiver get(int index){

poolget(index);

}

}

//使用组合方式的实现

public class Pool {

private List pool = new Vector();

上一篇:Quartz调度框架应用总结

下一篇:用Maven生成JDEE工程文件