创建型设计模式-4.原型设计模式
原型设计模式(Prototype Design Pattern)是一种创建型设计模式,旨在通过复制现有对象来创建新对象,而不是通过使用构造函数进行创建。它允许我们通过克隆(复制)现有对象的实例来创建新的对象,而无需显式地依赖于特定类的构造函数。
主要分为:浅拷贝和深拷贝
1、浅拷贝
浅拷贝是原型设计模式中的一种复制方式,它复制对象内的所有基本数据类型和引用数据类型的地址。这意味着在浅拷贝中,原始对象和复制对象将共享相同的引用数据类型的实例。
当执行浅拷贝时,如果对象内有引用类型的成员变量,那么复制的对象将包含对原始对象引用数据类型成员变量的引用。这意味着两个对象的引用类型成员变量指向相同的对象,任何对引用类型的修改将会影响到两个对象。
好的!让我们使用Java来举一个例子,使用浅拷贝来创建歌曲和歌单对象。
首先,我们定义一个歌曲(Song)类:
public class Song {
private String title;
private String artist;
public Song(String title, String artist) {
this.title = title;
this.artist = artist;
}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
public void setTitle(String title) {
this.title = title;
}
public void setArtist(String artist) {
this.artist = artist;
}
@Override
public String toString() {
return "Song: " + title + " - " + artist;
}
}
接下来,我们定义一个歌单(Playlist)类,它包含了多个歌曲对象:
public class PlayList {
private String name;
private List<Song> songs;
public PlayList(String name) {
this.name = name;
this.songs = new ArrayList<>();
}
public String getName() {
return name;
}
public List<Song> getSongs() {
return songs;
}
public void addSong(Song song) {
songs.add(song);
}
public void removeSong(Song song) {
songs.remove(song);
}
public PlayList shallowCopy(String newName) {
PlayList copy = new PlayList(newName);
copy.songs = new ArrayList<>(songs);
return copy;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("PlayList: ").append(name).append("\n");
for (Song song : songs) {
sb.append("- ").append(song).append("\n");
}
return sb.toString();
}
public static void main(String[] args) {
PlayList playList = new PlayList("歌单-全是redvelet的歌");
playList.addSong(new Song("feel my rhythm", "redvelet"));
playList.addSong(new Song("psycho", "redvelet"));
playList.addSong(new Song("bad boy", "redvelet"));
//创建一个新歌单,歌单包含原歌单playList1的所有歌曲,自己在新增歌曲
PlayList newPlayList = playList.shallowCopy("歌单-kpop");
System.out.println("playList = " + playList);
System.out.println("newPlayList = " + newPlayList);
//修改新歌单内的歌曲bad boy为人员为wendy,旧歌单的原信息会改变吗?
Song song = newPlayList.getSongs().get(2);
song.setArtist("wendy");
System.out.println("修改新歌单内的歌曲bad boy");
System.out.println("playList = " + playList);
System.out.println("newPlayList = " + newPlayList);
}
}
在Playlist
类中,我们添加了一个shallowCopy()
方法来执行浅拷贝。该方法会创建一个新的Playlist
对象,并复制歌单中的歌曲列表。注意,我们使用了new ArrayList<>(songs)
来复制歌曲列表的引用。
通过实现cloneable接口实现浅拷贝:
public class PlayListByCloneableInterface implements Cloneable, Serializable {
private String name;
private List<Song> songs;
public PlayListByCloneableInterface(String name) {
this.name = name;
this.songs = new ArrayList<>();
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public List<Song> getSongs() {
return songs;
}
public void addSong(Song song) {
songs.add(song);
}
public void removeSong(Song song) {
songs.remove(song);
}
public PlayListByCloneableInterface shallowCopy(String newName) {
PlayListByCloneableInterface copy = new PlayListByCloneableInterface(newName);
copy.songs = new ArrayList<>(songs);
return copy;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("PlayList: ").append(name).append("\n");
for (Song song : songs) {
sb.append("- ").append(song).append("\n");
}
return sb.toString();
}
public static void main(String[] args) throws CloneNotSupportedException {
PlayListByCloneableInterface playList = new PlayListByCloneableInterface("歌单-全是redvelet的歌");
playList.addSong(new Song("feel my rhythm", "redvelet"));
playList.addSong(new Song("psycho", "redvelet"));
playList.addSong(new Song("bad boy", "redvelet"));
//创建一个新歌单,歌单包含原歌单playList1的所有歌曲,自己在新增歌曲
PlayListByCloneableInterface newPlayList = (PlayListByCloneableInterface) playList.clone();
newPlayList.setName("歌单-全是kpop的歌");
System.out.println("playList = " + playList);
System.out.println("newPlayList = " + newPlayList);
//修改新歌单内的歌曲bad boy为人员为wendy,旧歌单的原信息会改变吗?
Song song = newPlayList.getSongs().get(2);
song.setArtist("wendy");
System.out.println("修改新歌单内的歌曲bad boy");
System.out.println("playList = " + playList);
System.out.println("newPlayList = " + newPlayList);
}
}
2、深拷贝
深拷贝是指在复制对象时,不仅复制对象本身,还复制对象所引用的所有子对象,使得复制后的对象与原始对象完全独立,互不影响。
在进行深拷贝时,需要递归地复制对象及其子对象,确保每个子对象都是独立的副本,而不是共享引用。
深拷贝可以解决对象拷贝过程中可能出现的共享引用和副作用问题。它确保了复制对象与原始对象之间的数据隔离,使得修改复制后的对象不会影响原始对象,从而提高代码的可靠性和安全性。
在Java中,可以通过几种方式实现深拷贝:
- 递归复制:对于复杂对象,通过递归遍历对象的每个属性,并针对引用类型的属性进行深度复制。
- 序列化和反序列化:将对象序列化为字节流,然后再反序列化为新的对象。这种方式需要确保对象及其所有子对象都实现了
Serializable
接口。
递归复制:
class Product implements Cloneable {
private String name;
private double price;
private int stock;
// 省略构造函数、getter和setter方法
@Override
public Product clone() {
try {
return (Product) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
// 促销规则
class PromotionRule implements Cloneable {
private String type;
private double discount;
private Product product;
// 省略构造函数、getter和setter方法
@Override
protected PromotionRule clone() {
try {
PromotionRule promotionRule = (PromotionRule) super.clone()
Product product = (Product)product.clone();promotionRule.setProduct(product);
return promotionRule;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
// 促销活动
class PromotionEvent implements Cloneable {
private String name;
private Date startDate;
private Date endDate;
private List<PromotionRule> rules;
// 省略构造函数、getter和setter方法
// 在促销活动中的clone方法需要克隆里边所有的非基础数据类型
@Override
protected PromotionEvent clone() {
try {
PromotionEvent clonedEvent = (PromotionEvent) super.clone();
clonedEvent.startDate = (Date) startDate.clone();
clonedEvent.endDate = (Date) endDate.clone();
clonedEvent.rules = new ArrayList<>();
for (PromotionRule rule : rules) {
clonedEvent.rules.add(rule.clone());
}
return clonedEvent;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
现在,我们已经为每个实体类实现了深拷贝方法。假设我们需要为不同的商品创建相似的促销活动,我们可以使用深拷贝来实现:
public class Main {
public static void main(String[] args) {
// 创建原始促销活动
PromotionEvent originalEvent = createSamplePromotionEvent();
// 创建新的促销活动
PromotionEvent newEvent = originalEvent.clone();
newEvent.setName("新的促销活动");
// 现在newEvent是originalEvent的一个深拷贝副本,我们可以对它进行修改而不会影响originalEvent
// 修改新促销活动的日期
newEvent.setStartDate(addDays(newEvent.getStartDate(), 7));
newEvent.setEndDate(addDays(newEvent.getEndDate(), 7));
// 修改新促销活动的部分规则
List<PromotionRule> newRules = newEvent.getRules();
newRules.get(0).setDiscount(newRules.get(0).getDiscount() * 1.1);
// 现在,我们已经成功地复制了一个与原始活动相似但具有不同日期和部分规则的新促销活动。
// 可以将新活动应用于其他商品,而原始活动保持不变。
}
private static PromotionEvent createSamplePromotionEvent() {
// 创建示例促销活动
List<PromotionRule> rules = Arrays.asList(
new PromotionRule("折扣", 0.9),
new PromotionRule("满减", 50)
);
PromotionEvent event = new PromotionEvent(
"原始促销活动",
new Date(),
addDays(new Date(), 7),
rules
);
return event;
}
private static Date addDays(Date date, int days) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);calendar.add(Calendar.DATE, days);
return calendar.getTime();
}
}
序列化方式:前提需要实现Serializable接口
深拷贝的通用做法就是使用对象想对原型对象进行序列化,再对序列化后的二进制流 执行反序列化操作,就可以得到一个完完全全相同的对象,这种序列化的方式有很多,比如先转为json,在转成内存模型的对象,也是可以的。
@Test
public void deepCopyTest() throws Exception{
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);
}
应用场景:
-
在订单管理系统中,深拷贝可以用于创建新订单并复制现有订单的商品和客户信息。假设我们有一个名为
Order
的类,其中包含订单信息和关联的商品和客户对象。我们可以通过深拷贝来创建新订单,并复制原始订单中的商品和客户信息,但需要重新填写新订单的其他信息。这样,新订单和原始订单是相互独立的对象,对新订单的修改不会影响原始订单。就比如每个月需要进一批货,但是大部分货的信息都一样,只是日期不一样,就可以使用深拷贝。 -
在前端开发中,重置按钮通常用于将表单或页面恢复到初始状态。当加载表单或页面时,可以进行深拷贝,复制一份初始内容作为参考。当用户点击重置按钮时,可以将深拷贝的对象作为引用,重新将其内容设置到表单或页面中,从而实现重置操作。通过深拷贝并使用引用转换,可以避免对原始对象的修改,确保每次重置都回到初始状态。