Java设计模式设计模式

《设计模式》原型模式

2019-08-08  本文已影响1人  敏捷Studio

定义

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

介绍

UML类图

原型模式UML类图

角色说明:

实现

1、Prototype(抽象原型类)

2、创建具体原型类,实现Cloneable接口:

// 具体原型类,卡片类,实现Cloneable接口,Cloneable只是标识接口
public class Card implements Cloneable {
  // 卡号
  private int num;
  // 卡规格
  private Spec spec = new Spec();

  public Card() {
    System.out.println("Card 执行构造函数");
  }

  public void setNum(int num) {
    this.num = num;
  }

  public void setSpec(int length, int width) {
    spec.setLength(length);
    spec.setWidth(width);
  }

  @Override
  public String toString() {
    return "Card{" +
            "num=" + num +
            ", spec=" + spec +
            '}';
  }

  // 重写clone()方法,clone()方法不是Cloneable接口里面的,而是Object里面的
  @Override
  protected Card clone() throws CloneNotSupportedException {
    System.out.println("clone时不执行构造函数");
    return (Card) super.clone();
  }
}

// 规格类,有长和宽这两个属性
public class Spec {
  private int width;
  private int length;

  public void setLength(int length) {
    this.length = length;
  }

  public void setWidth(int width) {
    this.width = width;
  }

  @Override
  public String toString() {
    return "Spec{" +
            "width=" + width +
            ", length=" + length +
            '}';
  }
}

3、创建客户端类,即要使用原型模式的地方:

public class Client {
  public void test() throws CloneNotSupportedException {
    Card card1 = new Card();
    card1.setNum(9527);
    card1.setSpec(10, 20);
    System.out.println(card1.toString());
    System.out.println("----------------------");

    Card card2 = card1.clone();
    System.out.println(card2.toString());
    System.out.println("----------------------");
  }
}

4、输出结果:

Card 执行构造函数
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
clone时不执行构造函数
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------

5、说明:

但是,如果执行下面的代码:
Card card1 = new Card();
card1.setNum(9527);
card1.setSpec(10, 20);
System.out.println(card1.toString());
System.out.println("----------------------");

Card card2 = card1.clone();
System.out.println(card2.toString());
System.out.println("----------------------");

card2.setNum(7259);
System.out.println(card1.toString());
System.out.println(card2.toString());
System.out.println("----------------------");

card2.setSpec(30, 40);
System.out.println(card1.toString());
System.out.println(card2.toString());
System.out.println("----------------------");

其输出结果为:

  Card 执行构造函数
  Card{num=9527, spec=Spec{width=20, length=10}}
  ----------------------
  clone时不执行构造函数
  Card{num=9527, spec=Spec{width=20, length=10}}
  ----------------------
  Card{num=9527, spec=Spec{width=20, length=10}}
  Card{num=7259, spec=Spec{width=20, length=10}}
  ----------------------
  Card{num=9527, spec=Spec{width=40, length=30}}
  Card{num=7259, spec=Spec{width=40, length=30}}
  ----------------------

我们会发现,修改了拷贝对象的引用类型(即Spec字段)时,原来的对象的值也跟着改变了;但是修改基本对象(num字段)时,原来的对象的值却不会改变。这就涉及到深拷贝和浅拷贝了。

深拷贝和浅拷贝

\color{red}{浅拷贝}

上面的例子实际上就是一个浅拷贝,如下图所示:

浅拷贝

由于num是基本数据类型,因此直接将整数值拷贝过来就行。但是spec是Spec类型的, 它只是一个引用,指向一个真正的Spec对象,那么对它的拷贝有两种方式:

\color{red}{深拷贝}

根据原Card对象中的spec指向的对象创建一个新的相同的对象,将这个新对象的引用赋给新拷贝的Card对象的spec字段。这种拷贝方式就叫深拷贝,如下图所示:

深拷贝

原型模式改造

那么,我们对上面原型模式的例子进行改造,使其实现深拷贝,这就需要在Card的clone方法中,将源对象引用的Spec对象也clone一份。

public static class Card implements Cloneable {
  private int num;
  private Spec spec = new Spec();

  public Card() {
    System.out.println("Card 执行构造函数");
  }

  public void setNum(int num) {
    this.num = num;
  }

  public void setSpec(int length, int width) {
    spec.setLength(length);
    spec.setWidth(width);
  }

  @Override
  public String toString() {
    return "Card{" +
            "num=" + num +
            ", spec=" + spec +
            '}';
  }

  @Override
   protected Card clone() throws CloneNotSupportedException {
      System.out.println("clone时不执行构造函数");
      Card card = (Card) super.clone();
      // 对spec对象也调用clone,实现深拷贝
      card.spec = (Spec) spec.clone();
      return card;
  }
}

// Spec也实现Cloneable接口
public static class Spec implements Cloneable {
  private int width;
  private int length;

  public void setLength(int length) {
    this.length = length;
  }

  public void setWidth(int width) {
    this.width = width;
  }

  @Override
  public String toString() {
    return "Spec{" +
            "width=" + width +
            ", length=" + length +
            '}';
  }

  // 重写Spec的clone方法
  @Override
  protected Spec clone() throws CloneNotSupportedException {
    return (Spec) super.clone();
  }
}

测试代码:

Card card1 = new Card();
card1.setNum(9527);
card1.setSpec(10, 20);
System.out.println(card1.toString());
System.out.println("----------------------");

Card card2 = card1.clone();
System.out.println(card2.toString());
System.out.println("----------------------");

card2.setNum(7259);
System.out.println(card1.toString());
System.out.println(card2.toString());
System.out.println("----------------------");

card2.setSpec(30, 40);
System.out.println(card1.toString());
System.out.println(card2.toString());
System.out.println("----------------------");

其输出结果为:

Card 执行构造函数
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
clone时不执行构造函数
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
Card{num=9527, spec=Spec{width=20, length=10}}
Card{num=7259, spec=Spec{width=20, length=10}}
----------------------
Card{num=9527, spec=Spec{width=20, length=10}}
Card{num=7259, spec=Spec{width=40, length=30}}
----------------------

由此可见,card1和card2内的spec引用指向了不同的Spec对象, 也就是说在clone Card对象的同时,也拷贝了它所引用的Spec对象, 进行了深拷贝。

应用场景

优缺点

优点

缺点

Android中的源码分析

Android中的Intent就实现了Cloneable接口,但是clone()方法中却是通过new来创建对象的。

public class Intent implements Parcelable, Cloneable {
  // 其他代码略
  @Override
  public Object clone() {
     // 这里没有调用super.clone()来实现拷贝,而是直接通过new来创建
    return new Intent(this);
  }

  public Intent(Intent o) {
    this.mAction = o.mAction;
    this.mData = o.mData;
    this.mType = o.mType;
    this.mPackage = o.mPackage;
    this.mComponent = o.mComponent;
    this.mFlags = o.mFlags;
    this.mContentUserHint = o.mContentUserHint;
    if (o.mCategories != null) {
      this.mCategories = new ArraySet<String>(o.mCategories);
    }
    if (o.mExtras != null) {
      this.mExtras = new Bundle(o.mExtras);
    }
    if (o.mSourceBounds != null) {
      this.mSourceBounds = new Rect(o.mSourceBounds);
    }
    if (o.mSelector != null) {
      this.mSelector = new Intent(o.mSelector);
    }
    if (o.mClipData != null) {
      this.mClipData = new ClipData(o.mClipData);
    }
  }
}

总结 :

上一篇 下一篇

猜你喜欢

热点阅读