原型模式(Prototype Pattern) 是一种创建型设计模式,用于通过复制现有对象的原型来创建新的对象。这种模式的主要优点是可以在运行时动态地创建新的对象,而无需依赖于类的实例化。
动机
在某些情况下,我们需要在运行时动态地创建对象,但是这些对象的创建成本较高,或者需要考虑对象的生命周期等因素。为了满足这些要求,我们可以使用原型模式来复制现有对象,从而避免直接创建新对象的开销。
结构
原型模式的结构包括以下几个角色:
Prototype(原型):定义一个接口或抽象类,用于声明克隆方法。
ConcretePrototype(具体原型):实现 Prototype 接口或继承抽象原型,并提供具体的克隆逻辑。
实现
原型设计模式主要的目的就是复制现有的对象,对象的拷贝分为 浅拷贝
和 深拷贝
。
浅拷贝:浅拷贝(Shallow Copy)是指创建一个新对象,这个对象有原始对象属性值的一份精确拷贝。如果属性是基本数据类型,那么拷贝的就是基本数据类型的值;如果属性是引用类型,那么拷贝的就是引用的地址。因此,如果在原始对象和拷贝对象中对引用类型的属性进行更改,这两个对象之间的互不影响。
深拷贝:深拷贝(Deep Copy)是指创建一个新对象,这个对象有原始对象属性值的完整副本。如果属性是基本数据类型,那么拷贝的就是基本数据类型的值;如果属性是引用类型,那么拷贝的就是引用的对象的一个全新的副本。因此,如果在原始对象和拷贝对象中对引用类型的属性进行更改,这两个对象之间互不影响。
浅拷贝的实现
以下是几个使用浅拷贝的场景:
原型模式:在创建一个新对象时,如果该对象和已有对象的属性相同,可以使用浅拷贝来复制已有对象的属性,而不必重新创建一个新对象。
缓存数据:当需要缓存某些数据时,可以使用浅拷贝来创建缓存对象。如果原始对象不再使用,可以直接将其赋值为 null,而不必担心缓存对象的引用被同时置为 null。
复制属性:当需要将一个对象的属性值复制到另一个对象时,可以使用浅拷贝。例如,将一个对象的属性值复制到一个 DTO(数据传输对象)中,以传递给其他系统或服务。
浅拷贝实现 - 直接赋值
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);
}
评论区