返回介绍

12.3.1 副本构建器

发布于 2024-10-15 23:56:26 字数 4723 浏览 0 评论 0 收藏 0

克隆看起来要求进行非常复杂的设置,似乎还该有另一种替代方案。一个办法是制作特殊的构建器,令其负责复制一个对象。在 C++中,这叫作“副本构建器”。刚开始的时候,这好象是一种非常显然的解决方案(如果你是 C++程序员,这个方法就更显亲切)。下面是一个实际的例子:

//: CopyConstructor.java
// A constructor for copying an object
// of the same type, as an attempt to create
// a local copy.

class FruitQualities {
  private int weight;
  private int color;
  private int firmness;
  private int ripeness;
  private int smell;
  // etc.
  FruitQualities() { // Default constructor
    // do something meaningful...
  }
  // Other constructors:
  // ...
  // Copy constructor:
  FruitQualities(FruitQualities f) {
    weight = f.weight;
    color = f.color;
    firmness = f.firmness;
    ripeness = f.ripeness;
    smell = f.smell;
    // etc.
  }
}

class Seed {
  // Members...
  Seed() { /* Default constructor */ }
  Seed(Seed s) { /* Copy constructor */ }
}

class Fruit {
  private FruitQualities fq;
  private int seeds;
  private Seed[] s;
  Fruit(FruitQualities q, int seedCount) { 
    fq = q;
    seeds = seedCount;
    s = new Seed[seeds];
    for(int i = 0; i < seeds; i++)
      s[i] = new Seed();
  }
  // Other constructors:
  // ...
  // Copy constructor:
  Fruit(Fruit f) {
    fq = new FruitQualities(f.fq);
    seeds = f.seeds;
    // Call all Seed copy-constructors:
    for(int i = 0; i < seeds; i++)
      s[i] = new Seed(f.s[i]);
    // Other copy-construction activities...
  }
  // To allow derived constructors (or other 
  // methods) to put in different qualities:
  protected void addQualities(FruitQualities q) {
    fq = q;
  }
  protected FruitQualities getQualities() {
    return fq;
  }
}

class Tomato extends Fruit {
  Tomato() {
    super(new FruitQualities(), 100);
  }
  Tomato(Tomato t) { // Copy-constructor
    super(t); // Upcast for base copy-constructor
    // Other copy-construction activities...
  }
}

class ZebraQualities extends FruitQualities {
  private int stripedness;
  ZebraQualities() { // Default constructor
    // do something meaningful...
  }
  ZebraQualities(ZebraQualities z) {
    super(z);
    stripedness = z.stripedness;
  }
}

class GreenZebra extends Tomato {
  GreenZebra() {
    addQualities(new ZebraQualities());
  }
  GreenZebra(GreenZebra g) {
    super(g); // Calls Tomato(Tomato)
    // Restore the right qualities:
    addQualities(new ZebraQualities());
  }
  void evaluate() {
    ZebraQualities zq = 
      (ZebraQualities)getQualities();
    // Do something with the qualities
    // ...
  }
}

public class CopyConstructor {
  public static void ripen(Tomato t) {
    // Use the "copy constructor":
    t = new Tomato(t); 
    System.out.println("In ripen, t is a " +
      t.getClass().getName());
  }
  public static void slice(Fruit f) {
    f = new Fruit(f); // Hmmm... will this work?
    System.out.println("In slice, f is a " +
      f.getClass().getName());
  }
  public static void main(String[] args) {
    Tomato tomato = new Tomato();
    ripen(tomato); // OK
    slice(tomato); // OOPS!
    GreenZebra g = new GreenZebra();
    ripen(g); // OOPS!
    slice(g); // OOPS!
    g.evaluate();
  }
} ///:~

这个例子第一眼看上去显得有点奇怪。不同水果的质量肯定有所区别,但为什么只是把代表那些质量的数据成员直接置入 Fruit(水果)类?有两方面可能的原因。第一个是我们可能想简便地插入或修改质量。注意 Fruit 有一个 protected(受到保护的)addQualities() 方法,它允许衍生类来进行这些插入或修改操作(大家或许会认为最合乎逻辑的做法是在 Fruit 中使用一个 protected 构建器,用它获取 FruitQualities 参数,但构建器不能继承,所以不可在第二级或级数更深的类中使用它)。通过将水果的质量置入一个独立的类,可以得到更大的灵活性,其中包括可以在特定 Fruit 对象的存在期间中途更改质量。

之所以将 FruitQualities 设为一个独立的对象,另一个原因是考虑到我们有时希望添加新的质量,或者通过继承与多形性改变行为。注意对 GreenZebra 来说(这实际是西红柿的一类——我已栽种成功,它们简直令人难以置信),构建器会调用 addQualities(),并为其传递一个 ZebraQualities 对象。该对象是从 FruitQualities 衍生出来的,所以能与基础类中的 FruitQualities 句柄联系在一起。当然,一旦 GreenZebra 使用 FruitQualities,就必须将其下溯造型成为正确的类型(就象 evaluate() 中展示的那样),但它肯定知道类型是 ZebraQualities。

大家也看到有一个 Seed(种子)类,Fruit(大家都知道,水果含有自己的种子)包含了一个 Seed 数组。

最后,注意每个类都有一个副本构建器,而且每个副本构建器都必须关心为基础类和成员对象调用副本构建器的问题,从而获得“深层复制”的效果。对副本构建器的测试是在 CopyConstructor 类内进行的。方法 ripen() 需要获取一个 Tomato 参数,并对其执行副本构建工作,以便复制对象:

t = new Tomato(t);

而 slice() 需要获取一个更常规的 Fruit 对象,而且对它进行复制:

f = new Fruit(f);

它们都在 main() 中伴随不同种类的 Fruit 进行测试。下面是输出结果:

In ripen, t is a Tomato
In slice, f is a Fruit
In ripen, t is a Tomato
In slice, f is a Fruit

从中可以看出一个问题。在 slice() 内部对 Tomato 进行了副本构建工作以后,结果便不再是一个 Tomato 对象,而只是一个 Fruit。它已丢失了作为一个 Tomato(西红柿)的所有特征。此外,如果采用一个 GreenZebra,ripen() 和 slice() 会把它分别转换成一个 Tomato 和一个 Fruit。所以非常不幸,假如想制作对象的一个本地副本,Java 中的副本构建器便不是特别适合我们。

1. 为什么在 C++的作用比在 Java 中大?

副本构建器是 C++的一个基本构成部分,因为它能自动产生对象的一个本地副本。但前面的例子确实证明了它不适合在 Java 中使用,为什么呢?在 Java 中,我们操控的一切东西都是句柄,而在 C++中,却可以使用类似于句柄的东西,也能直接传递对象。这时便要用到 C++的副本构建器:只要想获得一个对象,并按值传递它,就可以复制对象。所以它在 C++里能很好地工作,但应注意这套机制在 Java 里是很不通的,所以不要用它。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文