概念
Prototype 原型模式
以某个对象作为原型,实现某些方法,能够让外界直接调用该方法来达到复制对象的效果,比如:
Java 的
Cloneable接口;ArraysList的构造方法可以传入一个集合来复制;Kotlin 中数据类的
copy()方法;
解决的问题
当需要复制一个对象时, 需要遍历对象中的所有字段,并将字段赋值给新对象的字段;
但是如果存在私有字段,外部无法访问,就会存在问题,如下代码:
class User {
String name;
int age;
private int sex;
public User(String name, int age, boolean male) {
this.name = name;
this.age = age;
this.sex = male ? 0 : 1;
}
}
public class Main {
public static void main(String[] args) {
User existed = new User("Li-an", 20, true);
User user = new User(existed.name, existed.age, /* existed.age 无法访问 */);
}
}当传入对象的类型是某种抽象类或者接口时,无法得知其确切的类型,也就无法得知其对应的字段;
因此需要额外的类型判断,比如 instanceof 判断后再强转过来处理;
class Person { }
class User extends Person {
String name;
int age;
int sex;
public User(String name, int age, boolean male) {
this.name = name;
this.age = age;
this.sex = male ? 0 : 1;
}
}
public class Main {
public static void main(String[] args) {
Person existed = new User("Li-an", 20, true);
if (existed instanceof User) {
User user = new User(((User)existed).name, ((User)existed).age, ((User)existed).sex == 0);
}
}
}原型模式将克隆过程委派给被克隆的实际对象。(支持多态)支持克隆的对象即为 原型
模式为所有支持克隆的对象声明了一个通用接口
clone或者copy(), 该接口能够克隆并返回对象, 同时又无需将代码和对象所属类耦合。通常情况下, 接口中仅包含一个用于克隆的方法。
为什么不实现一个构造方法,传入已有对象然后对应赋值?这样能够只需要调用构造方法就能实现克隆了。
上面那种多态的情况,传过来的只是接口或者抽象类类型,要调用对应类的构造方法,就需要判断各个类型,当只有少数类时还好,如果类的数量多起来,就需要写很多冗余代码;
实现
定义接口
声明一个接口,内部只有一个抽象方法用于复制对象;
原型对象类只需要实现该接口并实现对应的方法,返回新的对象
interface Copyable {
Copyable copy();
}
class User implements Copyable {
private String name;
private int age;
private int sex;
public User(String name, int age, boolean male) {
this.name = name;
this.age = age;
this.sex = male ? 0 : 1;
}
@Override
public Copyable copy() {
return new User(
name, age, sex == 0
);
}
}
public class Main {
public static void main(String[] args) {
User existed = new User("Li-an", 20, true);
if (existed instanceof Copyable) {
User copy = (User) existed.copy();
}
}
}这个和 Java 的 Cloneable 接口很像,但是需要注意:
Cloneable默认实现是 浅拷贝,只是返回的对象与被克隆对象的引用不同(内部字段的引用仍然相等)当需要实现 深拷贝 时,就需要重写其
cObject#lone()的克隆方法,同时对于其字段也需要实现Cloneable接口来执行克隆操作(递归进行)
构造方法
如果不需要多个类同时实现原型,似乎用接口还复杂了,此时可以重载一个构造方法,负责复制传入的对象:
class User {
private String name;
private int age;
private int sex;
public User(User value) {
this.name = value.name;
this.age = value.age;
this.sex = value.sex;
}
public User(String name, int age, boolean male) {
this.name = name;
this.age = age;
this.sex = male ? 0 : 1;
}
}
public class Main {
public static void main(String[] args) {
User existed = new User("Li-an", 20, true);
User copy = new User(existed);
}
}Kotlin数据类
Kotlin 的 data class 声明的数据类,编译器会自动生成一个 copy 方法,该方法有多个重载,支持完整复制和修改部分数据的复制;
但是这种方法不适合 Java,因为 Java 的函数参数不能设置默认值
data class User(
val name: String,
val age: Int
)
fun main() {
val user = User("Li", 12)
val copy = user.copy()
println(user === copy)
}深拷贝&浅拷贝
Java 中深拷贝有两种选择:实现 Cloneable 接口 或者 序列化/反序列化
但是无论是哪种方式,深拷贝都会比浅拷贝更耗费资源;
一般来说,原型模式的复制最好使用 深拷贝,毕竟新对象不要影响旧对象;
但是如果深拷贝的性能影响较大时,可以选择深拷贝和浅拷贝结合:
对于不会修改的对象,使用浅拷贝;
对于会修改的对象,使用深拷贝