🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支持一下,感谢🤗!
🌟了解抽象工厂模式请看: (三)趣学设计模式 之 抽象工厂模式!
一、 啥是原型模式?
原型模式,说白了,就是“山寨”! 🤣 你有一个宝贝,不想自己辛辛苦苦再做一个,就找个复印机(克隆方法),咔嚓一下,复制一个一模一样的出来!
- 对象的创建成本很高: 就像造火箭 🚀,太费劲了,不如复制一个!
- 需要创建大量相似对象: 就像克隆羊 🐑,要一大堆一样的羊,一个个生太慢了!
- 希望隐藏对象的创建细节: 就像做菜 🍳,你只想吃,不想知道怎么做的!
二、 为什么要用原型模式?
用原型模式,好处多多:
- 省钱省力: 不用重复造轮子 🚗,直接复制,省时省力!
- 灵活多变: 想复制啥就复制啥,不用提前定好,很灵活!
- 简单易懂: 不用管对象怎么创建的,直接复制就行,代码更简单!
- 避免麻烦: 不用创建一堆乱七八糟的子类,省心!
三、 原型模式怎么实现?
原型模式主要有两种“山寨”方法:
- 浅拷贝(Shallow Copy)
- 深拷贝(Deep Copy)
实现原型模式的关键点
- Cloneable 接口: 就像一个“允许复制”的标签 🏷️,贴上这个标签,才能被复印!
- Object 类的 clone() 方法: 就像复印机的基本功能,只能复印表面信息(浅拷贝)!
- 重写 clone() 方法: 就像升级复印机,让它可以复印所有信息(深拷贝)!
1.浅拷贝(Shallow Copy):只复制表面! 🤏
浅拷贝就像复印身份证 🪪,你复印了一张身份证,上面的名字、地址都一样,但是里面的芯片还是原来的那个。如果原来的身份证信息变了,复印件也会跟着变!
// 1. 定义原型接口 (Cloneable 是 Java 内置的接口)
interface Prototype extends Cloneable {
Prototype clone(); // 克隆方法
}
// 2. 定义具体原型类
class Sheep implements Prototype {
private String name;
private int age;
private Address address; // 引用类型属性
public Sheep(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(String city) {
this.address.setCity(city);
}
@Override
public Sheep clone() {
try {
// 浅拷贝:直接调用 Object 类的 clone() 方法
return (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("克隆失败! 😭");
return null;
}
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", address=" + address +
'}';
}
}
// 地址类 (引用类型)
class Address {
private String city;
public Address(String city) {
this.city = city;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
'}';
}
}
// 3. 客户端使用
public class Client {
public static void main(String[] args) {
// 创建原型对象
Address address = new Address("北京");
Sheep originalSheep = new Sheep("多莉", 3, address);
// 克隆原型对象
Sheep clonedSheep = originalSheep.clone();
// 打印原始对象和克隆对象
System.out.println("原始羊: " + originalSheep);
System.out.println("克隆羊: " + clonedSheep);
// 修改原始对象的引用类型属性
originalSheep.setAddress("上海");
// 再次打印原始对象和克隆对象
System.out.println("修改后的原始羊: " + originalSheep);
System.out.println("修改后的克隆羊: " + clonedSheep);
}
}
代码解释:
Prototype
:原型接口,定义了克隆方法。Sheep
:具体原型类,实现了Prototype
接口,表示羊。clone()
:克隆方法,用于创建对象的副本。Address
:地址类,作为Sheep
类的引用类型属性。
输出结果:
原始羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}
克隆羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}
修改后的原始羊: Sheep{name='多莉', age=3, address=Address{city='上海'}}
修改后的克隆羊: Sheep{name='多莉', age=3, address=Address{city='上海'}}
分析:
可以看到,修改原始羊的地址后,克隆羊的地址也跟着改变了!这是因为浅拷贝只复制了地址的“指针”,原始羊和克隆羊指向的是同一个地址,所以一个变了,另一个也跟着变!
2. 深拷贝(Deep Copy):彻底复制! 💯
深拷贝就像克隆一只完整的羊 🐑,你克隆了一只羊,它有自己的名字、年龄和地址,和原来的羊完全没有关系。即使原来的羊死了,克隆羊还是活蹦乱跳的!
import java.io.*;
class Sheep implements Prototype, Serializable { // 注意实现 Serializable 接口
private String name;
private int age;
private Address address;
public Sheep(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public Sheep clone() {
try {
// 使用序列化和反序列化实现深拷贝
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Sheep) ois.readObject();
} catch (Exception e) {
System.out.println("克隆失败! 😭");
return null;
}
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", address=" + address +
'}';
}
}
class Address implements Serializable { // 注意实现 Serializable 接口
private String city;
public Address(String city) {
this.city = city;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
'}';
}
}
public class Client {
public static void main(String[] args) {
// 创建原型对象
Address address = new Address("北京");
Sheep originalSheep = new Sheep("多莉", 3, address);
// 克隆原型对象
Sheep clonedSheep = originalSheep.clone();
// 打印原始对象和克隆对象
System.out.println("原始羊: " + originalSheep);
System.out.println("克隆羊: " + clonedSheep);
// 修改原始对象的引用类型属性
address.setCity("上海");
// 再次打印原始对象和克隆对象
System.out.println("修改后的原始羊: " + originalSheep);
System.out.println("修改后的克隆羊: " + clonedSheep);
}
}
注意: 使用序列化和反序列化实现深拷贝,需要确保对象及其所有属性都实现了 Serializable
接口。
输出结果:
原始羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}
克隆羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}
修改后的原始羊: Sheep{name='多莉', age=3, address=Address{city='上海'}}
修改后的克隆羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}
分析:
可以看到,修改原始羊的地址后,克隆羊的地址没有改变!这是因为深拷贝复制了地址的所有信息,原始羊和克隆羊拥有不同的地址,所以一个变了,另一个不会受到影响!
四、 原型模式的应用场景
- 游戏开发: 游戏里的怪物 👾,不用一个个设计,复制几个改改属性就行!
- 文档编辑器: 文档的模板 📄,复制一份改改,就能生成新的文档!
- 图形编辑器: 图形对象 🖼️,复制一份改改颜色、大小,就能得到新的图形!
- 配置管理: 软件的配置 ⚙️,复制一份改改,就能适应不同的环境!
- 报表生成: 报表的模板 📊,复制一份改改数据,就能生成不同的报表!
五、 原型模式的优点和缺点
优点:
- 省钱省力: 不用重新创建对象,直接复制,就像复制粘贴一样简单,大大提高了效率!
- 隐藏秘密: 客户端不用知道对象是怎么创建的,只需要知道怎么复制就行,保护了对象的内部结构!
- 灵活应变: 可以在运行时动态地复制对象,想复制啥就复制啥,非常灵活!
- 减少麻烦: 不用创建一堆子类,避免了代码的膨胀,让代码更简洁!
缺点:
- 复制很麻烦: 特别是深拷贝,要复制对象的所有信息,就像搬家一样,很麻烦!
- 破坏隐私: 为了复制对象,可能需要访问对象的私有属性,就像偷看别人的日记一样,破坏了对象的封装性!
- 需要贴标签: 在 Java 中,需要实现
Cloneable
接口才能被复制,就像需要贴上“允许复制”的标签,有点麻烦! - 可能出错: 有时候复制操作可能不会执行构造函数,就像克隆羊没有灵魂一样,可能会导致对象的状态不正确!