Thinking in Java 第七章------复用类(2)

来源:互联网 发布:海马模拟器for mac 编辑:程序博客网 时间:2024/05/18 06:21

Thinking in Java 第七章——复用类(1)
Thinking in Java 第七章——复用类(2)
Thinking in Java 第七章——复用类(3)

三、代理

Java没有提供对代理的直接支持.这是继承组合的中庸之道。因为我们将一个成员对象置于所要构造的类中(就像组合),但与此同时我们在新类中暴露了该对象的所有方法(就像继承)。
例如,太空船需要一个控制模块:

public class SpaceShipControls {  void up(int velocity) {}  void down(int velocity) {}  void left(int velocity) {}  void right(int velocity) {}  void forward(int velocity) {}  void back(int velocity) {}  void turboBoost() {}} ///:~

构造太空船的一种方式是继承:

public class SpaceShip extends SpaceShipControls {  private String name;  public SpaceShip(String name) { this.name = name; }  public String toString() { return name; }  public static void main(String[] args) {  SpaceShip protector = new SpaceShip("NSEA Protector");  protector.forward(100);  }} ///:~

然而SpaceShip并非真正的SpaceShipControls类型,即便你可以告诉SpaceShip向前运动forward()。更准确的讲,SpaceShip包含SpaceShipControls ,与此同时,SpaceShipControls 中的所有方法在SpaceShip中都暴露了出来。代理解决了这个难题:

public class SpaceShipDelegation {  private String name;  private SpaceShipControls controls =    new SpaceShipControls();  public SpaceShipDelegation(String name) {    this.name = name;  }  // Delegated methods:  public void back(int velocity) {    controls.back(velocity);  }  public void down(int velocity) {    controls.down(velocity);  }  public void forward(int velocity) {    controls.forward(velocity);  }  public void left(int velocity) {    controls.left(velocity);  }  public void right(int velocity) {    controls.right(velocity);  }  public void turboBoost() {    controls.turboBoost();  }  public void up(int velocity) {    controls.up(velocity);  }  public static void main(String[] args) {    SpaceShipDelegation protector =      new SpaceShipDelegation("NSEA Protector");    protector.forward(100);  }} ///:~

将方法转递给底层的controls,而其接口由此也就与使用继承得到的接口相同了。但是我们使用代理时,可以拥有更多的控制力,因为我们可以选择只提供在成员对象中方法的子集。


四、结合使用组合和继承

给出一个例子:

import static net.mindview.util.Print.*;class Plate {  Plate(int i) {    print("Plate constructor");  }}class DinnerPlate extends Plate {  DinnerPlate(int i) {    super(i);    print("DinnerPlate constructor");  }}   class Utensil {  Utensil(int i) {    print("Utensil constructor");  }}class Spoon extends Utensil {  Spoon(int i) {    super(i);    print("Spoon constructor");  }}class Fork extends Utensil {  Fork(int i) {    super(i);    print("Fork constructor");  }}   class Knife extends Utensil {  Knife(int i) {    super(i);    print("Knife constructor");  }}// A cultural way of doing something:class Custom {  Custom(int i) {    print("Custom constructor");  }}   public class PlaceSetting extends Custom {  private Spoon sp;  private Fork frk;  private Knife kn;  private DinnerPlate pl;  public PlaceSetting(int i) {    super(i + 1);    sp = new Spoon(i + 2);    frk = new Fork(i + 3);    kn = new Knife(i + 4);    pl = new DinnerPlate(i + 5);    print("PlaceSetting constructor");  }  public static void main(String[] args) {    PlaceSetting x = new PlaceSetting(9);  }} /* Output:Custom constructorUtensil constructorSpoon constructorUtensil constructorFork constructorUtensil constructorKnife constructorPlate constructorDinnerPlate constructorPlaceSetting constructor*///:~

虽然编译器强制你去初始化基类,并且要求你要在构造器起始处就这么做,但是它并不监督你必须将成员对象也初始化。

4.1确保正确的清理

Java中没C++中析构函数的概念。析构函数是一种在对象被销毁时可以被自动调用的函数。其原因可能是因为在Java中,我们的习惯只是忘掉而不是销毁对象,并且让垃圾回收其在必要时释放其内存。
通常这样做事好事,但是有时候,类可能需要在其生命周期内执行一些必须的清理活动。因为垃圾回收器不知道何时会被调用,或者它是够将被调用,因此如果你想要某个类清理一些东西,就必须显示的编写一个特殊方法来做这件事,并要确保客户端程序猿知道必须调用这一方法。

例如:

import static net.mindview.util.Print.*;class Shape {  Shape(int i) { print("Shape constructor"); }  void dispose() { print("Shape dispose"); }}class Circle extends Shape {  Circle(int i) {    super(i);    print("Drawing Circle");  }  void dispose() {    print("Erasing Circle");    super.dispose();//!!!!!!  }}class Triangle extends Shape {  Triangle(int i) {    super(i);    print("Drawing Triangle");  }  void dispose() {    print("Erasing Triangle");    super.dispose();//!!!!!!  }}class Line extends Shape {  private int start, end;  Line(int start, int end) {    super(start);    this.start = start;    this.end = end;    print("Drawing Line: " + start + ", " + end);  }  void dispose() {    print("Erasing Line: " + start + ", " + end);    super.dispose();//!!!!!!  }}public class CADSystem extends Shape {  private Circle c;  private Triangle t;  private Line[] lines = new Line[3];  public CADSystem(int i) {    super(i + 1);    for(int j = 0; j < lines.length; j++)      lines[j] = new Line(j, j*j);    c = new Circle(1);    t = new Triangle(1);    print("Combined constructor");  }  public void dispose() {    print("CADSystem.dispose()");    // The order of cleanup is the reverse    // of the order of initialization:    t.dispose();    c.dispose();    for(int i = lines.length - 1; i >= 0; i--)      lines[i].dispose();    super.dispose(); //!!!!!!  }  public static void main(String[] args) {    CADSystem x = new CADSystem(47);    try {      // Code and exception handling...    } finally {      x.dispose();    }  }} /* Output:Shape constructorShape constructorDrawing Line: 0, 0Shape constructorDrawing Line: 1, 1Shape constructorDrawing Line: 2, 4Shape constructorDrawing CircleShape constructorDrawing TriangleCombined constructorCADSystem.dispose()Erasing TriangleShape disposeErasing CircleShape disposeErasing Line: 2, 4Shape disposeErasing Line: 1, 1Shape disposeErasing Line: 0, 0Shape disposeShape dispose*///:~
  • 此系统中的一切都是某种Shape,每个类都覆写Shapedispose()方法,并运用super来调用该方法的基类版本。

  • 无论try块是怎么样退出的,finally子句中的代码总是要被执行的。这里的finally代表的是“无论发生什么事情,一定要为x调用dispose()方法”。

  • 在清理方法中还需要注意对基类清理方法和成员对象清理方法的调用顺序:首先,执行类的所有特定的清理动作,其顺序同生成书序相反。然后,就如示范那样,调用基类的清理方法。

4.2名称屏蔽

如果Java的基类拥有某个已经被多次重载的方法名称,那么在导出类中重新定义该方法名称并不会屏蔽其在基类中的任何版本。因此无论是在该层基类或者它的基类中对方法进行定义,重载机制都可以正常的工作。

import static net.mindview.util.Print.*;class Homer {  char doh(char c) {    print("doh(char)");    return 'd';  }  float doh(float f) {    print("doh(float)");    return 1.0f;  }}class Milhouse {}class Bart extends Homer {  void doh(Milhouse m) {    print("doh(Milhouse)");  }}public class Hide {  public static void main(String[] args) {    Bart b = new Bart();    b.doh(1);    b.doh('x');    b.doh(1.0f);    b.doh(new Milhouse());  }} /* Output:doh(float)doh(char)doh(float)doh(Milhouse)*///:~

可以看到,虽然在Bar引入了一个新的重载方法,但是在BarHomer的所有重载方法欧式可以使用的。
Java SE5中新增加了@override注解,它并不是关键字,但是可以把它当做关键字来使用。当腰覆写某个方法时候,就可以选择添加这个注解,在你不留心重载而非覆写该方法时,编译器就会生成错误信息,提示你。


五、在组合与继承之间选择

组合和继承都允许在新的类中放置子对象,组合是显式这么做的,而继承是隐式这么做的

  • 组合技术通常用于想在新类中使用现有类的功能,而非它的接口这种情形。即,在新类中嵌入某个对象,让其实现所需要的功能,但新类的用户看到的只是新类定义的接口,而非所嵌入对象的接口。

  • 在继承的时候,使用某个现有类,并开发一个它的特殊版本。通常这意味着你再使用一个通用类,并为了某种特殊化需要而将其特殊化。

  • “is-a”(是一个)关系用继承来表达; “has-a”(有一个)关系用组合来表达。

  • 到底是该使用组合还是继承,一个最清晰的判断方法就是问一问自己,是够需要从新类向基类进行向上转型。如果必须是,则继承是必要的。但如果不需要,则应当好好考虑自己是够需要继承。

  • 在理想世界中,仅靠关键字private就已经足够了,但是在实际项目中,经常会想要将某些事物尽可能对这个世界隐藏起来,但是任然允许导出类的成员访问他们。关键字protected就是其这个作用。它指明,就类用户而言,这是private 的,但对于任何继承于此类的导出类或者他任何位于同一个包中类来说,他确实可以访问的。(protected提供了包访问全权限。)

0 0