XiaoLin's Blog

Xiao Lin

原型设计模式学习笔记

14
2024-02-26

原型模式(Prototype Pattern) 是一种创建型设计模式,用于通过复制现有对象的原型来创建新的对象。这种模式的主要优点是可以在运行时动态地创建新的对象,而无需依赖于类的实例化。

动机

在某些情况下,我们需要在运行时动态地创建对象,但是这些对象的创建成本较高,或者需要考虑对象的生命周期等因素。为了满足这些要求,我们可以使用原型模式来复制现有对象,从而避免直接创建新对象的开销。

结构

原型模式的结构包括以下几个角色:

Prototype(原型):定义一个接口或抽象类,用于声明克隆方法。
ConcretePrototype(具体原型):实现 Prototype 接口或继承抽象原型,并提供具体的克隆逻辑。

实现

原型设计模式主要的目的就是复制现有的对象,对象的拷贝分为 浅拷贝深拷贝

浅拷贝:浅拷贝(Shallow Copy)是指创建一个新对象,这个对象有原始对象属性值的一份精确拷贝。如果属性是基本数据类型,那么拷贝的就是基本数据类型的值;如果属性是引用类型,那么拷贝的就是引用的地址。因此,如果在原始对象和拷贝对象中对引用类型的属性进行更改,这两个对象之间的互不影响。

深拷贝:深拷贝(Deep Copy)是指创建一个新对象,这个对象有原始对象属性值的完整副本。如果属性是基本数据类型,那么拷贝的就是基本数据类型的值;如果属性是引用类型,那么拷贝的就是引用的对象的一个全新的副本。因此,如果在原始对象和拷贝对象中对引用类型的属性进行更改,这两个对象之间互不影响。

浅拷贝的实现

以下是几个使用浅拷贝的场景:

原型模式:在创建一个新对象时,如果该对象和已有对象的属性相同,可以使用浅拷贝来复制已有对象的属性,而不必重新创建一个新对象。
缓存数据:当需要缓存某些数据时,可以使用浅拷贝来创建缓存对象。如果原始对象不再使用,可以直接将其赋值为 null,而不必担心缓存对象的引用被同时置为 null。
复制属性:当需要将一个对象的属性值复制到另一个对象时,可以使用浅拷贝。例如,将一个对象的属性值复制到一个 DTO(数据传输对象)中,以传递给其他系统或服务。

image.png

浅拷贝实现 - 直接赋值


import java.util.ArrayList;
import java.util.List;

class Song {
    String title;
    String artist;

    Song(String title, String artist) {
        this.title = title;
        this.artist = artist;
    }
}


@Data
public class Playlist {
    private Long id;
    private String name;
    private List<Song> songs = new ArrayList<>();

    public Playlist() {
    }

    public void add(Song song){
        songs.add(song);
    }

    public Playlist(Playlist sourcePlayList) {
        this.id = sourcePlayList.getId();
        this.name = sourcePlayList.getName();
        this.songs = sourcePlayList.getSongs();
    }

    public static void main(String[] args) {
        Playlist playlist = new Playlist();
        playlist.setId(1L);
        playlist.setName("杰伦");
        playlist.add(new Song("稻香","杰伦"));
        playlist.add(new Song("迷迭香","杰伦"));
        playlist.add(new Song("七里香","杰伦"));

        // 浅拷贝后的最喜爱的专辑
        Playlist favouriteList = new Playlist(playlist);
        favouriteList.add(new Song("曹操","林俊杰"));
        System.out.println(favouriteList);


    }
}

浅拷贝实现 - 实现 Cloneable 接口

在 java 中,有提供一个 Cloneable 接口用于实现对象拷贝的支持
Cloneable:在 Java 中,Cloneable 是一个标记接口,用于表示一个类的对象是可以被克隆的。通过实现 Cloneable 接口,类表明它允许克隆其对象。然后,可以通过调用 clone ()方法来创建对象的副本。

所以我们的对象在实现了 Cloneable 接口后,通过对象调用 clone() 方法即可完成浅拷贝

package xyz.xiaolinz.demo.prototype.shallowcopy;

import java.io.Serializable;

/**
 * demo2 - 浅拷贝实现,实现Cloneable接口,重写clone方法
 *
 * <p>1. 实现Cloneable接口 2. 重写clone方法 3. 重写clone方法时,调用父类的clone方法 4. 重写clone方法时,将返回值强转为当前类
 *
 * @author huangmuhong
 * @date 2023/8/8
 */
public class PlayListCloneable extends PlayList implements Cloneable, Serializable {

  public static void main(String[] args){
    PlayListCloneable playListCloneable = new PlayListCloneable();

    try {
      PlayListCloneable clone = (PlayListCloneable) playListCloneable.clone();
      System.out.println(playListCloneable == clone);
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
  }

  @Override
  protected Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
}

package xyz.xiaolinz.demo.prototype.shallowcopy;
/**
 * @author huangmuhong
 * @date 2023/8/8
 */
public class Song {
  private final String title;

  private final String artist;

  public Song(String title, String artist) {
    this.title = title;
    this.artist = artist;
  }
}

package xyz.xiaolinz.demo.prototype.shallowcopy;

import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 *
 * demo1 - 浅拷贝实现,通过直接赋值实现
 *
 * @author huangmuhong
 * @date 2023/8/8
 */
@Data
@NoArgsConstructor
public class PlayList {

  private Long id;

  private String name;

  private List<Song> songs = new ArrayList<>();

  public PlayList(PlayList playList) {
    this.id = playList.id;
    this.name = playList.name;
    this.songs = playList.songs;
  }

  public static void main(String[] args){
    final var playList = new PlayList();
    playList.setId(1L);
    playList.setName("My Favorite");
    playList.addSong(new Song("song1", "artist1"));
    playList.addSong(new Song("song2", "artist2"));
    playList.addSong(new Song("song3", "artist3"));
    System.out.println(playList);

    // 浅拷贝
    final var playListCopy = new PlayList(playList);
    playListCopy.setName("My Favorite Copy");
    System.out.println(playListCopy);
  }

  public void addSong(Song song) {
    songs.add(song);
  }
}

深拷贝的实现

深拷贝的实现,通常有两个思路,一个是递归克隆,一个是使用序列化的手段。

深拷贝的实现 - 递归克隆

在需要克隆以及起组合、依赖的类都实现 Cloneable 接口,并在调用方的 clone 方法递归的循环去调用组合和依赖对象的 clone 方法


package xyz.xiaolinz.demo.prototype.deepcopy;

import lombok.Data;

/**
 * 产品
 *
 * @author huangmuhong
 * @date 2023/08/08
 */
@Data
public class Product implements Cloneable {
  private String name;

  private Double price;

  private Integer stock;

  @Override
  protected Product clone() throws CloneNotSupportedException {
    return ((Product) super.clone());
  }
}


package xyz.xiaolinz.demo.prototype.deepcopy;

import lombok.Data;

/**
 * 促销规则
 *
 * @author huangmuhong
 * @date 2023/08/08
 * @see Cloneable
 */
@Data
public class PromotionRule implements Cloneable {
  private String type;

  private Double discount;

  private Product product;

  @Override
  protected PromotionRule clone() throws CloneNotSupportedException {
    final var promotionRule = (PromotionRule) super.clone();

    // 在这里,我们需要将product也进行深拷贝
    promotionRule.setProduct(product.clone());
    return promotionRule;
  }
}

package xyz.xiaolinz.demo.prototype.deepcopy;

import java.util.Date;
import java.util.List;
import lombok.Data;

/**
 * 促销活动
 * 
 *
 * @author huangmuhong
 * @date 2023/08/08
 */
@Data
public class PromotionEvent implements Cloneable {
  private String name;

  private Date startDate;

  private Date endDate;

  private List<PromotionRule> rules;

  @Override
  protected PromotionEvent clone() throws CloneNotSupportedException {
    final var clone = ((PromotionEvent) super.clone());

    // Date 默认实现了 Cloneable,可以直接调用以实现拷贝
    clone.setStartDate((Date) startDate.clone());
    clone.setEndDate((Date) endDate.clone());

    // rules 需要递归拷贝
    if (rules != null) {
      for (int i = 0; i < rules.size(); i++) {
        rules.add(i,rules.get(i).clone());
      }
    }
    return clone;
  }
}

浅拷贝的实现 - 序列化方式

深拷贝的通用做法就是使用对象想对原型对象进行序列化,再对序列化后的二进制流执行反序列化操作,就可以得到一个完完全全相同的对象,这种序列化的方式有很多比如先转为 json,在转成内存模型的对象,也是可以的。

@Test
public void deepCopyTest2() throws CloneNotSupportedException, IOException, ClassNotFoundException {
    User user = new User(12, "zhangsan");
    user.setDog(new Dog(2));

    // 将对象写到字节数组当中
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
    objectOutputStream.writeObject(user);
    // 获取字节数组
    byte[] bytes = outputStream.toByteArray();
    // 用输入流读出来
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
    ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
    Object object = objectInputStream.readObject();
    User user1 = (User) object;

    user.setAge(44);
    user.getDog().setAge(11);
    System.out.println(user);
    System.out.println(user1);

}