Java中的原型模式 - clone()方法

12

所以,我一直在阅读关于设计模式的内容,其中原型模式让我困惑。我相信使用它的一个目的是避免使用new运算符。然后我看了这个例子:

http://sourcemaking.com/design_patterns/prototype/java/1

首先,他们的原型实现了一个clone()方法,这很奇怪。维基百科也说我需要通过子类实现一个纯虚方法clone(为什么?)。Java已经提供了这样一个方法,完全能够实现我们需要的功能(即创建对象的副本而不是从头开始实例化它),难道不是吗?其次,clone方法调用了new运算符!这个例子肯定是错的,对吧?(那我应该去其他地方学习设计模式呢,哈哈)。能否有人告诉我这个更正是否正确?

static class Tom implements Cloneable implements Xyz {
    public Xyz    cloan()    {
      return Tom.clone(); //instead of new I use clone() from Interface Cloneable
    }
    public String toString() {
      return "ttt";
    }
  } 

感激任何澄清。


3
我使用Cloneable接口中的clone()方法。但是,Cloneable接口本身并没有clone()方法。 - newacct
5个回答

10
原型模式的理念是创建一个蓝图/模板,从中可以生成实例。它不仅仅是为了“避免在Java中使用new”。
如果您在Java中实现原型模式,那么确实可以覆盖Object类中现有的clone()方法,无需创建新方法。(还需要实现Clonable接口,否则会抛出异常)
举个例子:
// Student class implements Clonable
Student rookieStudentPrototype = new Student();
rookieStudentPrototype.setStatus("Rookie");
rookieStudentPrototype.setYear(1);

// By using prototype pattern here we don't need to re-set status and
// year, only the name. Status and year already copied by clone
Student tom = rookieStudentPrototype.clone();
tom.setName("Tom");

Student sarah = rookieStudentPrototype.clone();
sarah.setName("Sarah");

1
利用Cloneable接口中现有的clone()方法。但是,Cloneable接口中并没有clone()方法。 - newacct
如果你到处使用克隆方法,很可能会遇到一些麻烦问题。在现实生活中,你必须决定是要进行深度克隆还是浅层克隆。那么如何克隆List实现或二维数组呢?以上只是一个非常简单的例子。通常更好的方法是使用复制构造函数来高效处理这些情况。在《Effective Java》一书中,Josh Bloch表示Cloneable接口存在严重问题。 - ingyhere

4
设计模式就是以可复制的方式表示软件编写的方法。实际上,有不同的语法方法来实现相同的功能。
因此,原型模式只是一种使用主控副本来实现某些覆盖功能的方法。在Java中有几种方法可以做到这一点(我认为其他语言也是如此)。以下是一种使用“new”关键字的方法,基于使用接口作为与实现具体类的合同。然后,单个方法将采用接口的具体实现并执行相同操作:
// software contract
interface Shape { 
   public void draw();
} 
// concrete implementations
class Line implements Shape {
   public void draw() {
      System.out.println("line");
   }
}
class Square implements Shape {
   public void draw() {
      System.out.println("square");
   }
}
...
class Painting {
   public static void main (String[] args) {
      Shape s1 = new Line ();
      Shape s2 = new Square ();
      ...
      paint (s1);
      paint (s2);
      ...
   }
   // single method executes against the software contract as a prototype
   static void paint (Shape s) {
      s.draw ();
   }
}

您可以在http://www.javacamp.org/designPattern/prototype.html阅读更多相关IT技术的内容,或者查看主要设计模式网站。该网站提供完整的参考信息。

1
乍一看,这个实现更像我所期望的。感谢您指出来。我知道实现模式的具体细节可能会有所不同,但我仍然觉得我链接的那个(以及可能是维基百科?)有些问题。 - Leandro Nogueira Couto

3
你提供的链接示例是正确的,而且你的代码也没问题。
return Tom.clone();

由于clone()不是一个静态方法,因此代码无法编译。

克隆并不是为了避免使用new运算符,而是创建一个新的实例,该实例具有与被克隆对象相同的状态(成员字段的值)。因此,clone()不是静态方法,而是实例方法,可以创建一个新实例(使用 new 也没有问题),其镜像了调用clone()的对象的状态。

只是你的示例类(例如Tom)太简单了(没有状态),以至于clone()方法所做的全部工作就是实例化一个新的实例。如果它具有更复杂的状态(例如一个对象的ArrayList),则clone()方法还必须对ArrayList进行深层复制。

为了详细说明你的其中一个示例类,假设Tom有一些实例状态。现在,clone()还必须确保返回的副本与当前状态匹配。

static class Tom implements Xyz {

    private String name;

    public Tom() {
      this.name = "Tom"; // some state
    }

    public Xyz clone()    {
      Tom t = new Tom();
      t.setName(getName()); // copy current state
      return t;
    }

   public String toString() {
      return getName();
    }

   public String getName() {
      return name;
    }

   public void setName(String name) {
      this.name = name;
    }
}

你的意思是,如果这个类有一个ArrayList(或任何其他属性),我的克隆(或在我这种情况下的cloan())方法会创建一个“new Tom()”,然后将原型中存储的属性值复制到新对象上?如果是这样,我明白了,问题实际上是这个例子没有很好地突出原型的有用性。谢谢! - Leandro Nogueira Couto
2
是的,这就是我的意思。我还添加了一个示例来突出这一点。 - Ravi K Thapliyal
克隆方法,当调用super.clone()方法时,它将返回相同的实例,除非该方法被覆盖并且您进行手动深度复制或使用任何现有库来实现此目的。 - pguzman

0
你也可以使用 Spring 框架提供的 org.springframework.beans.BeanUtils 中的 BeanUtils.copyProperties 方法来实现相同的功能;

0

原型实际上“不会”保存对new运算符的调用。它只是通过调用所谓的clone来使非敏感属性的浅拷贝。例如,

1)您有一个名为UserAccount的对象,其中包含主用户和关联用户详细信息

2)UserAccount还有它的PK,称为userAccountId

当您将所有UserAccount对象放入集合中时,当然,您希望userAccountId不同。但是,您仍然必须为每个链接调用new UserAccount。否则,您最终会修改一个对象100次,期望得到100个结果。此外,如果您将此UserAccount作为组合(而不是聚合),则根据属性的敏感性,您可能还需要在其上调用new

例如,如果UserAccount具有Person对象(如果“Person”具有自己的组合),则必须调用new以确保它们的引用设置正确。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接