手撕设计模式——克隆对象之原型模式

1.业务需求

​ 大家好,我是菠菜啊,前俩天有点忙,今天继续更新了。今天给大家介绍克隆对象------原型模式。老规矩,在介绍这期之前,我们先来看看这样的需求:《西游记》中每次孙悟空拔出一撮猴毛吹一下,变出一大批猴子加入战斗,他到底是怎么变的?如果我们帮他实现这个功能,代码怎么设计?

2.代码实现

首先先说第一个问题,怎么变的我也不知道。

但是第二个问题,可以尝试一下。

实现初步思路:

​ 我们新建一个猴子类,并且实例化多个猴子对象不就行了,太简单了。

Monkey类:

java 复制代码
//猴子
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类:

java 复制代码
//武器
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类:

java 复制代码
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接口:

java 复制代码
//猴子
public class Monkey implements Cloneable{

   //......
    
    @Override
    protected Monkey clone() throws CloneNotSupportedException {
        return (Monkey)super.clone();
    }

   

}

Client类:

java 复制代码
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修改后:

java 复制代码
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类:

java 复制代码
//武器
public class Weapon implements Cloneable{

    //...
   
    @Override
    protected Weapon clone() throws CloneNotSupportedException {
        return (Weapon)super.clone();
    }

 
}

Monkey类:

java 复制代码
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 方法位于类的内部,当对已有类进行改造的时候,可能需要修改代码,违背了开闭原则

适用场景:

  • 保存对象的状态并且对象占用内存较少
  • 对象创建成本高,耗用的资源比较多
  • 对象初始化复杂

你的收藏和点赞就是我最大的创作动力,关注我我会持续输出更新!

友情提示:请尊重作者劳动成果,如需转载本博客文章请注明出处!谢谢合作!

【作者:我爱吃菠菜 】

相关推荐
num_killer7 小时前
小白的Langchain学习
java·python·学习·langchain
期待のcode8 小时前
Java虚拟机的运行模式
java·开发语言·jvm
程序员老徐8 小时前
Tomcat源码分析三(Tomcat请求源码分析)
java·tomcat
a程序小傲8 小时前
京东Java面试被问:动态规划的状态压缩和优化技巧
java·开发语言·mysql·算法·adb·postgresql·深度优先
仙俊红8 小时前
spring的IoC(控制反转)面试题
java·后端·spring
阿湯哥8 小时前
AgentScope Java 集成 Spring AI Alibaba Workflow 完整指南
java·人工智能·spring
小楼v9 小时前
说说常见的限流算法及如何使用Redisson实现多机限流
java·后端·redisson·限流算法
与遨游于天地9 小时前
NIO的三个组件解决三个问题
java·后端·nio
czlczl200209259 小时前
Guava Cache 原理与实战
java·后端·spring
yangminlei9 小时前
Spring 事务探秘:核心机制与应用场景解析
java·spring boot