a我考网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 190|回复: 2

[JAVA] 2011年计算机等考二级JAVA学习精华整理(81)

[复制链接]
发表于 2012-7-31 22:04:26 | 显示全部楼层 |阅读模式
 4.1 代码复用的规则  代码复用是绝大多数程序员所期望的,也是OO的目标之一。总结我多年的编码经验,为了使代码能够最大程度上复用,应该特别注意以下几个方面。
% |% @, k; C8 }1 {7 m$ B  对接口编程
; a  a/ d+ ]0 j9 [* E: p  "对接口编程"是面向对象设计(OOD)的第一个基本原则。它的含义是:使用接口和同类型的组件通讯,即,对于所有完成相同功能的组件,应该抽象出一个接口,它们都实现该接口。具体到JAVA中,可以是接口(interface),或者是抽象类(abstract class),所有完成相同功能的组件都实现该接口,或者从该抽象类继承。我们的客户代码只应该和该接口通讯,这样,当我们需要用其它组件完成任务时,只需要替换该接口的实现,而我们代码的其它部分不需要改变!
1 W4 B8 A# a# H( Z& j+ n  当现有的组件不能满足要求时,我们可以创建新的组件,实现该接口,或者,直接对现有的组件进行扩展,由子类去完成扩展的功能。
1 }( t! x; ~$ g( F2 T8 R9 ?  优先使用对象组合,而不是类继承/ X4 x, _: U" n- R+ e: d
  "优先使用对象组合,而不是类继承"是面向对象设计的第二个原则。并不是说继承不重要,而是因为每个学习OOP的人都知道OO的基本特性之一就是继承,以至于继承已经被滥用了,而对象组合技术往往被忽视了。下面分析继承和组合的优缺点:
; ~8 M, H5 F' b4 L  类继承允许你根据其他类的实现来定义一个类的实现。这种通过生成子类的复用通常被称为白箱复用(white-box reuse)。术语"白箱"是相对可视性而言:在继承方式中,父类的内部细节对子类可见。
) j: ~0 B/ F/ N  对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组合对象来获得。对象组合要求对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为被组合的对象的内部细节是不可见的。对象只以"黑箱"的形式出现。
4 a8 I; u2 f; K/ h  继承和组合各有优缺点。类继承是在编译时刻静态定义的,且可直接使用,类继承可以较方便地改变父类的实现。但是类继承也有一些不足之处。首先,因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现。更糟的是,父类通常至少定义了子类的部分行为,父类的任何改变都可能影响子类的行为。如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。
0 D, A, p% ^1 t2 ^6 f0 i  对象组合是通过获得对其他对象的引用而在运行时刻动态定义的。由于组合要求对象具有良好定义的接口,而且,对象只能通过接口访问,所以我们并不破坏封装性;只要类型一致,运行时刻还可以用一个对象来替代另一个对象;更进一步,因为对象的实现是基于接口写的,所以实现上存在较少的依赖关系。
. J& r" j7 C% I) s  优先使用对象组合有助于你保持每个类被封装,并且只集中完成单个任务。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物(这正是滥用继承的后果)。另一方面,基于对象组合的设计会有更多的对象(但只有较少的类),且系统的行为将依赖于对象间的关系而不是被定义在某个类中。4 w1 I, s/ |* @: x. I% q. r
  注意:理想情况下,我们不用为获得复用而去创建新的组件,只需要使用对象组合技术,通过组装已有的组件就能获得需要的功能。但是事实很少如此,因为可用的组件集合并不丰富。使用继承的复用使得创建新的组件要比组装已有的组件来得容易。这样,继承和对象组合常一起使用。然而,正如前面所说,千万不要滥用继承而忽视了对象组合技术。
回复

使用道具 举报

 楼主| 发表于 2012-7-31 22:04:27 | 显示全部楼层

2011年计算机等考二级JAVA学习精华整理(81)

 相关的设计模式有: Bridge、Composite、Decorator、Observer、Strategy等。  下面的例子演示了这个规则,它的前提是:我们对同一个数据结构,需要以任意的格式输出。7 m- {6 i: D3 {' |
  第一个例子,我们使用基于继承的框架,可以看到,它很难维护和扩展。  l3 A" G, l; X7 C+ J
  abstract class AbstractExampleDocument
5 ^3 o8 d0 k  p  {- J; n: A9 I' Z0 z& _" a
  // skip some code ...3 e% ~' B$ Z0 t4 Q, Z
  public void output(Example structure)
5 L* {8 e4 x5 \( C  {
# X/ o! V3 \7 T9 X. Z) _& F8 q6 W  if( null != structure )% f4 P& X- J1 A
  {
  e$ O' c3 s% v7 Z" u" |) j" E% B0 l  this.format( structure );8 v! {  k% m0 ]( c" N- {! ]6 b
  }0 w$ J# r5 d* q+ m. q# i1 J$ t
  }
8 ]& P* M6 f* }, _  protected void format(Example structure);: N% J5 x" r1 P% T* P  P, y
  }
! U+ S6 _* O/ Y9 b' Y  第二个例子,我们使用基于对象组合技术的框架,每个对象的任务都清楚的分离开来,我们可以替换、扩展格式类,而不用考虑其它的任何事情。9 T( w( T) V( D
  class DefaultExampleDocument  X' V+ X  C1 V$ v4 h3 f
  {
, w& Y* d4 X1 x# W6 N7 S  // skip some code ...! p$ W1 V. H: o8 y. [+ ]
  public void output(Example structure)4 [/ L4 D" C- o4 |# E2 Z9 U
  {
0 ^. k+ P, f3 ^5 l3 D3 ~  H) }( u  ~  ExampleFormatter formatter =
0 H+ L3 K% s' S/ y" U  (ExampleFormatter) manager.lookup(Roles.FORMATTER);
' c" C/ J! }, \" S! x" f2 q  if( null != structure )5 T4 D, |/ [4 s$ {
  {+ p! s9 Q+ R4 |( y% a# }# [% s( [
  formatter.format(structure);
4 g4 o5 b% B$ X: e  }
. L8 e/ `0 Q8 g2 l9 n' E  }
  I. Q/ V3 z/ k  }
) `* O4 d0 A* f( S; [( j  这里,用到了类似于"抽象工厂"的组件创建模式,它将组件的创建过程交给manager来完成;ExampleFormatter是所有格式的抽象父类;! P) w+ z2 m- p& V2 ]0 b
  将可变的部分和不可变的部分分离9 [9 f; o3 I6 X4 X/ D1 j2 j
  "将可变的部分和不可变的部分分离"是面向对象设计的第三个原则。如果使用继承的复用技术,我们可以在抽象基类中定义好不可变的部分,而由其子类去具体实现可变的部分,不可变的部分不需要重复定义,而且便于维护。如果使用对象组合的复用技术,我们可以定义好不可变的部分,而可变的部分可以由不同的组件实现,根据需要,在运行时动态配置。这样,我们就有更多的时间关注可变的部分。
- T& F6 o4 {2 A7 Q  对于对象组合技术而言,每个组件只完成相对较小的功能,相互之间耦合比较松散,复用率较高,通过组合,就能获得新的功能。
- f  i% ]" K( |" t  减少方法的长度& C1 }5 J$ e3 T3 B/ z  U. l
  通常,我们的方法应该只有尽量少的几行,太长的方法会难以理解,而且,如果方法太长,则应该重新设计。对此,可以总结为以下原则:1 C% {  b+ p( `* O0 i' S
  ☆ 三十秒原则:如果另一个程序员无法在三十秒之内了解你的函数做了什么(What),如何做(How)以及为什么要这样做(Why),那就说明你的代码是难以维护的,必须得到提高;* C3 l! a5 U' x
  ☆ 一屏原则:如果一个函数的代码长度超过一个屏幕,那么或许这个函数太长了,应该拆分成更小的子函数;8 |: M) U$ f$ k" n" z
  ☆ 一行代码尽量简短,并且保证一行代码只做一件事:那种看似技巧性的冗长代码只会增加代码维护的难度。
, Z& E0 L/ C9 `9 q; m, y  消除case / if语句
& P4 h7 G5 i2 |: |  要尽量避免在代码中出现判断语句,来测试一个对象是否某个特定类的实例。通常,如果你需要这么做,那么,重新设计可能会有所帮助。我在工作中遇到这样的一个问题:我们在使用JAVA做XML解析时,对每个标签映射了一个JAVA类,采用SAX(简单的XML接口API:Simple API for XML)模型。结果,代码中反复出现了大量的判断语句,来测试当前的标签类型。为此,我们重新设计了DTD(文档类型定义:Document Type Definition),为每个标签增加了一个固定的属性:classname,而且重新设计了每个标签映射的JAVA类的接口,统一了每个对象的操作:+ U, K: w. s. O( }. Z
  addElement(Element aElement); //增加子元素
5 b2 d$ W3 P$ M! h8 L3 u  addAttribute(String attName, String attValue); //增加属性;
2 o0 L& J9 o: E4 N3 c" T  则彻底消除了所有的测试当前的标签类型的判断语句。每个对象通过 Class.forName(aElement.attributes.getAttribute("classname")).newInstence(); 动态创建,
* \" K% N, ]/ Y. s9 B* Z  减少参数个数
  D% b; p4 X: S4 b( p/ b3 M3 g' v  有大量参数需要传递的方法,通常很难阅读。我们可以将所有参数封装到一个对象中来完成对象的传递,这也有利于错误跟踪。
4 Z5 a) B# s5 ^+ E# t7 u- s  许多程序员因为,太多层的对象包装对系统效率有影响。是的,但是,和它带来的好处相比,我们宁愿做包装。毕竟,"封装"也是OO的基本特性之一,而且,"每个对象完成尽量少(而且简单)的功能",也是OO的一个基本原则。
& A/ f" x) [! z8 P* p  类层次的最高层应该是抽象类! E$ m/ b% s1 c6 @6 T  Y
  在许多情况下,提供一个抽象基类有利做特性化扩展。由于在抽象基类中,大部分的功能和行为已经定义好,使我们更容易理解接口设计者的意图是什么。6 c+ k  t% f) i/ z8 h$ X
  由于JAVA不允许"多继承",从一个抽象基类继承,就无法再从其它基类继承了。所以,提供一个抽象接口(interface)是个好主意,一个类可以实现多个接口,从而模拟实现了"多继承",为类的设计提供了更大的灵活性。
& x+ r4 F; A2 J  J5 a% z  尽量减少对变量的直接访问; l5 [6 N( A* O7 K6 ^% T
  对数据的封装原则应该规范化,不要把一个类的属性暴露给其它类,而是应该通过访问方法去保护他们,这有利于避免产生波纹效应。如果某个属性的名字改变,你只需要修改它的访问方法,而不是修改所有相关的代码。5 G( f/ w, P& E" x! S
  子类应该特性化,完成特殊功能# l# z& _. k) U( O7 s
  如果一个子类只是使一个组件变成组件管理器,而不是实现接口功能,或者,重载某个功能,那么,就应该使用一个外部的容器类,而不是创建一个子类。7 ]4 I7 W' D' K7 Y: K7 C
  建议:类层次结构图,不要太深;
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-7-31 22:04:28 | 显示全部楼层

2011年计算机等考二级JAVA学习精华整理(81)

 例如:下面的接口定义了组件的功能:发送消息;类Transceiver实现了该接口;而其子类Pool只是管理多个Transceiver对象,而没有提供自己的接口实现。建议使用组合方式,而不是继承!  public interface ITransceiver{0 Z, [) @* N7 ^
  public abstract send(String msg);
) s/ W4 |  z0 A0 J" E  }+ B  Z  n  S3 e2 E# N
  public class Transceiver implements ITransceiver {
9 A5 K' |6 e$ C9 F  public send(String msg){; b  a/ j' ~& I' H. x; z
  System.out.println(msg);- l" m! A5 \! k- U1 B& ^" e" y
  }- g$ `( X; ?* Q2 ^6 f! P
  }
8 C9 ?$ t6 M- p2 J, h& z) r- j  //使用继承方式的实现
5 v* P- O( D  F" ~3 X  public class Pool extends Transceiver{/ l6 X* Z3 ~! q
  private List pool = new Vector();+ K( }% H% Y$ |
  public void add(Transceiver aTransceiver){
& g; [3 c! H: Q/ _1 t! ]( }  w# H  pool.add(aTransceiver);  g" G- v0 Q* o& D; `+ {1 s" U8 Q+ H
  }
  y' ?% P: A! r" F" x! `  public Transceiver get(int index){. m- f& v( R& f# q: ]
  pool.get(index);& b* V; ]1 `2 g6 y
  }2 f: o, D4 k. l- |3 T. p+ b
  }# }( t! `4 O. }% }
  //使用组合方式的实现
* V7 ^' i$ v* B" m  public class Pool {
. z4 z& g8 g3 l4 O2 H/ x9 u  private List pool = new Vector();
7 \1 _: l% \$ w3 o, Y8 q  public void add(Transceiver aTransceiver){4 k, L7 p% o5 n7 s
  pool.add(aTransceiver);7 q4 |( p2 D. C# M7 I, z) H
  }. m6 ]# n* S7 v6 B1 Q1 C# V
  public Transceiver get(int index){
6 }, Q- p1 E' Y( `( E9 G  W  pool.get(index);
# H5 X% o, o# E- p9 s, v+ Q  }
) j; |) x! ?$ K+ F2 D/ s6 A  }# `1 {6 i) }. |0 G0 j
  拆分过大的类6 y+ B+ W6 [! Z" P2 n- o$ n# g3 p) N; m
  如果一个类有太多的方法(超过50个),那么它可能要做的工作太多,我们应该试着将它的功能拆分到不同的类中,类似于规则四。; T; r7 {# ]" Q9 L5 @5 Q2 B
  作用截然不同的对象应该拆分7 x9 K7 [& y7 S6 |  ]
  在构建的过程中,你有时会遇到这样的问题:对同样的数据,有不同的视图。某些属性描述的是数据结构怎样生成,而某些属性描述的是数据结构本身。最好将这两个视图拆分到不同的类中,从类名上就可以区分出不同视图的作用。) e, {5 a2 l0 h
  类的域、方法也应该有同样的考虑!
1 G  s( M6 x$ I) E" L5 L  尽量减少对参数的隐含传递- ~: g, H* q& _2 X; ], {# A
  两个方法处理类内部同一个数据(域),并不意味着它们就是对该数据(域)做处理。许多时候,该数据(域)应该作为方法的参输入数,而不是直接存取,在工具类的设计中尤其应该注意。例如:
+ i  M! Y" q  q  public class Test{
3 |# B8 e. z3 N  private List pool = new Vector();4 Y5 n3 t2 L$ q2 k3 j  i( @
  public void testAdd(String str){* P# O6 T$ x9 K! \0 Y! U
  pool.add(str);7 p2 }8 b( u7 a
  }
2 M7 ], w! e" F5 r9 L2 o  public Object testGet(int index){
) k5 m4 g2 w1 j3 `$ Q( ~5 Q' ^& m  pool.get(index);6 o4 ^9 {$ }3 o& B0 N! C9 o6 X
  }
3 }  f7 f7 B5 p! M: v6 s  }
! D# o3 _: |* f. w  两个方法都对List对象pool做了操作,但是,实际上,我们可能只是想对List接口的不同实现Vector、ArrayList等做存取测试。所以,代码应该这样写:
- A  \3 P3 V+ M  w* U  public class Test{& {# w+ x6 A3 l+ P# x) |7 ]
  private List pool = new Vector();! B, M* Q% D) l" n
  public void testAdd(List pool, String str){8 Z; M' B0 W6 }) H, z6 I& w
  pool.add(str);' ?- x8 w8 h/ |* J8 _1 t& s! k% @& J
  }
* K6 L0 u# r$ C2 ^; I& Z  public Object testGet(List pool, int index){- `# W8 o- h, ^, o  m& _8 I
  pool.get(index);
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Woexam.Com ( 湘ICP备18023104号 )

GMT+8, 2024-5-3 03:12 , Processed in 0.202033 second(s), 25 queries .

Powered by Discuz! X3.4 Licensed

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表