Thinking in Java chapter8 类重用

目录

1 简介

Java 的一个重要的特性就是可以方便的实现类重用。重用代码的概念在编程语言里早有体现:把常用的相对独立的功能以函数的形式保存,以备以后调用而不是重新编写。在 Java 语言中,重用代码的概念通过类重用得到了进一步的延伸。

今天我们学习两种代码重用的技巧: compositioninheritance 。我们简单的把 composition 翻译成为 组合,把 inheritance 翻译为继承。大多时候我都倾向于保持使用 compositioninheritance ,这样一来虽然避免技术词汇翻译带来的不统一,但是却带来了语言的不中不洋。考虑到我们以技术讨论为主,我觉得技术文献存在不中不洋没有什么不妥。

2 composition

我们之前就接触过 composition 这种代码重用方式。简而言之, composition 就是在一个类中生成另一个类的对象。

我们用一个例子来说明这个问题:

 1: //: reusing/SprinklerSystem.java
 2: // Composition for code reuse.
 3: 
 4: class WaterSource {
 5:   private String s;
 6:   WaterSource() {
 7:     System.out.println("WaterSource()");
 8:     s = "Constructed";
 9:   }
10:   public String toString() { return s; }
11: }
12: 
13: public class SprinklerSystem {
14:   private String valve1, valve2, valve3, valve4;
15:   private WaterSource source = new WaterSource();
16:   private int i;
17:   private float f;
18:   public String toString() {
19:     return
20:       "valve1 = " + valve1 + " " +
21:       "valve2 = " + valve2 + " " +
22:       "valve3 = " + valve3 + " " +
23:       "valve4 = " + valve4 + "\n" +
24:       "i = " + i + " " + "f = " + f + " " +
25:       "source = " + source;
26:   }
27:   public static void main(String[] args) {
28:     SprinklerSystem sprinklers = new SprinklerSystem();
29:     System.out.println(sprinklers);
30:   }
31: } /* Output:

在上面的例子中类 SprinklerSystem 具有几个私有变量,除了 privimitive 类型外还有一个 WaterSource 类型的类索引。这个类索引指向另一个类 WaterSource 。这就是 composition

在上面的例子中,我们还可以挖掘更多的东西。首先 SprinklerSystemWaterSource 都定义了一个方法 toString 。当编译器需要一个 String 输入,但是给定的输入确实一个 object 时,这个方法就会被调用。比如,在上面的例子中, SprinklerSystem 类中有一语句:

"source = " + source;

编译器发现,你想把一个 WaterSource 对象与一个 String 相加。但是,我们知道只有 String 类型的才能相加。这个时候编译器就会得到一个消息:“调用 toStringsource 变成 String ”。然后把 toString 的输出和 "source = " 相加。

任何时候,如果你想要实现这种字符串和对象的相加,都需要在类定义中定义方法 toString

上面一段代码的输出是:

WaterSource()
valve1 = null valve2 = null valve3 = null valve4 = null
i = 0 f = 0.0 source = Constructed

我们看到字符串初始化为 null ,浮点数和整数初始化为 0.

3 inheritance

inheritance 是面向对象语言重要组成部分。 实际上在 Java 中,你无时无刻不在使用继承。即使不是显示的实用,你也在隐式的实用。Java 的所有对象都之间或者间接的继承于一个类 Object 。 在 Java 中使用 extends 来扩展原来的类(我们称之为基类)。

接下来我们通过一个例子来了解 inheritance 的语法。

 1: //: reusing/Detergent.java
 2: // Inheritance syntax & properties.
 3: package reusing;
 4: import static net.mindview.util.Print.*;
 5: 
 6: class Cleanser {
 7:     private String s = "Cleanser comes first";
 8:     private String t = " Cleanser comes second";
 9:     private String u = " Cleanser comes third";
10:     public void append(String a) { s += t;s+=a; }
11:     public void dilute() { append(" dilute()"); }
12:     public void apply() { append(" apply()"); }
13:     public void scrub() { append(" scrub()"); }
14:     public String toString() { return s; }
15:     public static void main(String[] args) {
16:         Cleanser x = new Cleanser();
17:         x.dilute(); x.apply(); x.scrub();
18:         print(x);
19:     }
20: }
21: 
22: public class Detergent extends Cleanser {
23:     // Change a method:
24:     public void scrub() {
25:         append(" Detergent.scrub()");
26:         super.scrub(); // Call base-class version
27:     }
28:     // Add methods to the interface:
29:     public void foam() { append(" foam()"); }
30:     // Test the new class:
31:     public static void main(String[] args) {
32:         Detergent x = new Detergent();
33:         x.dilute();
34:         x.apply();
35:         x.scrub();
36:         x.foam();
37:         print(x);
38:         print("Testing base class:");
39:         Cleanser.main(args);
40:     }
41: } /* Output:
42:      Cleanser dilute() apply() Detergent.scrub() scrub() foam()
43:      Testing base class:
44:      Cleanser dilute() apply() scrub()
45:   *///:~

在这个例子中 Cleanser 是基类, Detergent 是对基类的扩展。注意关键词 extends

上面的例子中有几点需要我们特别注意。首先在 Cleanser append()String 类型通过操作符 += 来实现连接,这对操作符 += 的重载。

第二,在 CleanserDetergent 中都有 main 函数。这种在每个类中放一个 main 的做法有助于测试每一个类的功能。如果在一个 java 程序中有很多的类,意味着可能有多个 main 函数。只有命令行调用的那个类能够执行。在上面的代码中,只有 Detergent.main() 会被调用。

第三,在 Detergent.main() 中显示的调用了 Cleanser.main() 。传给 Cleanser.main() 的参数,和传给 Detergent.main() 的参数是一样的。

Cleanser 中的所有方法都是 public 的。如果对这些函数不指定 public 或者 private 这样的约束,这些函数的默认访问方式是 package access .也就是说这些函数只允许 package 的成员访问,也就是说在这个 package 中,任何一个类都可以访问这些成员函数。 Detergent 类在访问 Cleanser 类的方法过程中,即使 Cleanser 类中的方法没有指定 public 也不要紧,因为可以通过 package access 访问。但是假设如果其他 package 中的类(继承自 Detergent )想要访问 Detergent 类中的方法,它只能访问 Detergent 中带有 public 标识的方法 。一个通用的规则是,把一个类中的数据私有化(打上 private 标签 ),把方法公有化(打上 public 标签)。

Detergent 类中,重新定义了 scrub 函数(),注意到在基类 Cleanser 中也定义了 scrub 。那么在 Detergent 访问的 scrub 函数是在 Detergent 类中定义的。如果此时我想访问基类中的 scrub 函数怎么办?有个关键词 super .就像在 Detergentscrub 函数中那样 super.scrub() . 显然,通过使用 inheritance ,在继承类中,我们不仅可以使用基类中的方法,也可以重新定义相同名字的方法,并通过 super 来访问基类中定义的方法。