1.业务需求
大家好,我是菠菜啊,前俩天有点忙,今天继续更新了。今天给大家介绍克隆对象——原型模式。老规矩,在介绍这期之前,我们先来看看这样的需求:《西游记》中每次孙悟空拔出一撮猴毛吹一下,变出一大批猴子加入战斗,他到底是怎么变的?如果我们帮他实现这个功能,代码怎么设计?
2.代码实现
首先先说第一个问题,怎么变的我也不知道。
但是第二个问题,可以尝试一下。
实现初步思路:
我们新建一个猴子类,并且实例化多个猴子对象不就行了,太简单了。
Monkey类:
//猴子
public class Monkey {
private String name;
private String sex;
private int age;
private Weapon weapon;
public Monkey(String name, String sex, int age, Weapon weapon) {
this.name = name;
this.sex = sex;
this.age = age;
this.weapon = weapon;
}
public Weapon getWeapon() {
return weapon;
}
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
@Override
public String toString() {
return "Monkey{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
", weapon=" + weapon +
'}';
}
}
Weapon类:
//武器
public class Weapon {
private String name;
private String color;
public Weapon(String name, String color) {
this.name = name;
this.color = color;
}
@Override
public String toString() {
return "Weapon{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
Client类:
public class Client {
public static void main(String[] args) {
Weapon weapon=new Weapon("金箍棒","金色");
Monkey monkey=new Monkey("孙悟空","公",20,weapon);
Weapon weapon2=new Weapon(monkey.getWeapon().getName(),monkey.getWeapon().getColor());
Monkey monkey2=new Monkey("猴小弟",monkey.getSex(),monkey.getAge(),weapon2);
Weapon weapon3=new Weapon(monkey.getWeapon().getName(),monkey.getWeapon().getColor());
Monkey monkey3=new Monkey("猴小小弟",monkey.getSex(),monkey.getAge(),weapon3);
System.out.println(monkey);
System.out.println(monkey2);
System.out.println(monkey3);
}
}
思考:上述代码比较简单,功能是实现了,但是在克隆猴哥的时候,我们要将新建一个相同类的对象。 然后, 我还要必须遍历原始对象的所有成员变量, 并将成员变量值复制到新对象中。这也太麻烦了,如果属性是上千上万个,那么猴哥还没变出猴子,师傅就被妖怪给吃了。 而且并非所有对象都能通过这种方式进行复制, 因为有些对象可能拥有私有成员变量, 它们在对象本身以外是不可见的。而且克隆对象,要知道该对象类的所有依赖类才行,这样设计也太不符合迪米特法则了(详细见***《设计模式——设计原则介绍》***一文)。
3.方案改进
Java中提供了一个Cloneable接口,其中有一个clone()方法,我们只要实现这个方法就行了。
实现代码结构图:
Monkey接口:
//猴子
public class Monkey implements Cloneable{
//......
@Override
protected Monkey clone() throws CloneNotSupportedException {
return (Monkey)super.clone();
}
}
Client类:
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Weapon weapon=new Weapon("金箍棒","金色");
Monkey monkey=new Monkey("孙悟空","公",20,weapon);
/* Weapon weapon2=new Weapon(monkey.getWeapon().getName(),monkey.getWeapon().getColor());
Monkey monkey2=new Monkey("猴小弟",monkey.getSex(),monkey.getAge(),weapon2);
Weapon weapon3=new Weapon(monkey.getWeapon().getName(),monkey.getWeapon().getColor());
Monkey monkey3=new Monkey("猴小小弟",monkey.getSex(),monkey.getAge(),weapon3);*/
Monkey monkey2=monkey.clone();
monkey2.setName("猴小弟");
Monkey monkey3=monkey.clone();
monkey3.setName("猴小小弟");
System.out.println(monkey);
System.out.println(monkey2);
System.out.println(monkey3);
}
}
思考:这样我们就可以快速克隆对象,并且不需要知道对象创建的细节,又大大提高了性能,我们把这种设计模式叫做原型模式(Prototype )。上述代码还是有问题,可以继续往下看。
拓展:浅克隆和深克隆
我们把上述代码稍微修改一下,看的就明显了。
Client修改后:
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Weapon weapon=new Weapon("金箍棒","金色");
Monkey monkey=new Monkey("孙悟空","公",20,weapon);
Monkey monkey2=monkey.clone();
monkey2.setName("猴小弟");
Monkey monkey3=monkey.clone();
monkey3.setName("猴小小弟");
System.out.println("修改武器前:"+monkey);
System.out.println("修改武器前:"+monkey2);
System.out.println("修改武器前:"+monkey3);
//修改各自的武器装备
monkey.getWeapon().setColor("红色");
monkey2.getWeapon().setColor("白色");
monkey3.getWeapon().setColor("绿色");
System.out.println("++++++修改武器+++++");
System.out.println("修改武器后:"+monkey);
System.out.println("修改武器后:"+monkey2);
System.out.println("修改武器后:"+monkey3);
}
}
**预期结果:**猴子们的武器颜色分别是红白绿。
实际结果:猴子们的武器都被绿了(一不小心开车了)。
排查原因发现,super.clone(),如果字段是值类型的,就复制值,如果字段是引用类型的,复制引用而不复制引用的对象(String是特殊的引用对象),因此猴子们引用的武器对象是一个。被复制的对象的所有变量值都含有原来对象相同的值,但是其它对象的引用仍然执行原来的对象,叫做浅克隆**。反之,把引用对象的变量指向复制过的新对象,这种叫做深克隆。
我们如果要完成深复制,只需做如下修改:
Weapon类:
//武器
public class Weapon implements Cloneable{
//...
@Override
protected Weapon clone() throws CloneNotSupportedException {
return (Weapon)super.clone();
}
}
Monkey类:
public class Monkey implements Cloneable{
//...
@Override
protected Monkey clone() throws CloneNotSupportedException {
Monkey clone= (Monkey)super.clone();
clone.weapon=this.weapon.clone();
return clone;
}
}
**思考:**如果要深克隆,必须重写clone方法,如果克隆对象依赖对象的层级嵌套一多,代码较复杂。
4.定义和组成结构
原型模式(Prototype):从一个对象创建一个可定制的对象,而不需要知道任何创建细节。
原型模式包含以下主要角色。
- 抽象原型类(Prototype):规定了具体原型对象必须实现的接口。
- 具体原型类(ConcretePrototype):实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类(Acess):使用具体原型类中的 clone() 方法来复制新的对象。
5.优缺点以及应用场景
优点:
- 通过克隆一个已有的对象,简化对象的创建过程,不用关注对象的内部创建细节,符合迪米特法则
- 流的方式比new一个对象克隆对象效率更高
缺点:
- 克隆对象类必须要重写clone方法
- 如果克隆对象依赖对象的嵌套层级较多,并且要达到深克隆,代码较复杂
- clone 方法位于类的内部,当对已有类进行改造的时候,可能需要修改代码,违背了开闭原则
适用场景:
- 保存对象的状态并且对象占用内存较少
- 对象创建成本高,耗用的资源比较多
- 对象初始化复杂
你的收藏和点赞就是我最大的创作动力,关注我我会持续输出更新!
友情提示:请尊重作者劳动成果,如需转载本博客文章请注明出处!谢谢合作!
【作者:我爱吃菠菜 】