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

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

适用场景:

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

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

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

【作者:我爱吃菠菜 】

相关推荐
阿伟*rui4 分钟前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
Yaml44 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~4 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616884 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7895 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java5 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
睡觉谁叫~~~5 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust