设计模式之原型模式

本文介绍了Java中的原型模式,如何简化对象创建、提高效率,以及其在处理浅拷贝和深拷贝时的优缺点,包括使用Cloneable接口和Serializable接口实现克隆的不同方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原型模式:

优点:

  1. 在创建对象的过程比较复杂的时候,可以使用原型模式来简化对象的创造过程,同时提高效率。
  2. 无需重新初始化对象,而动态的获取对象的运行时状态。
  3. 如果被克隆对象字段发生变化,无需修改代码。
  4. 在深拷贝时可能需要较为复杂的代码。

缺点

需要为每个类配置一个克隆的方法,对原有类进行改造的时候,违反了开闭原则。

需求

复制多个对象

使用原型模式前的代码:

被克隆的类


import lombok.Data;

/**
 * @ClassName: Sheep
 * @Description: 羊类
 **/
@Data
public class Sheep {

    //名称
    private String name;
    //年龄
    private int age;
    //颜色
    private String color;


    public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    public Sheep() {
    }
}

客户端:

public class Client {


    public static void main(String[] args) {
        Sheep sheep = new Sheep("tom",2,"white");

        //没有使用原型模式时, 创建对象的方式需要去获取克隆对象的字段,如果克隆类字段有所改变,那我们的代码也需要进行改变
        Sheep sheep1 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());

        System.out.println(sheep.toString());
        System.out.println(sheep1.toString());
        System.out.println(sheep2.toString());
        System.out.println(sheep3.toString());
        System.out.println(sheep4.toString());
    }
}

我们看下执行结果:

Sheep(name=tom, age=2, color=white)
Sheep(name=tom, age=2, color=white)
Sheep(name=tom, age=2, color=white)
Sheep(name=tom, age=2, color=white)
Sheep(name=tom, age=2, color=white)

可以看到在没有使用原型模式的时候,我们需要手动的去初始化对象,并且如果被克隆的对象增加了新的字段,我们的代码也会受到影响。

接下来使用原型模式:
两种方式实现:

  1. 被克隆类实现Cloneable接口,重写clone方法。
  2. 通过序列化的方式。

第一种方式代码如下:

@NoArgsConstructor
public class Sheep implements Cloneable {

    //名称
    private String name;
    //年龄
    private int age;
    //颜色
    private String color;
    //重写克隆方法,由于当前类没有引用类型变量,我们直接使用父类的代码就可以
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
     public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    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 String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }


    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\''
                '}';
    }
}

客户端代码:

public class Client {


    public static void main(String[] args) {
        Sheep sheep = new Sheep("tom",2,"blue");
        sheep.dog = new Dog("边牧", 3);

        try {
            Sheep sheep1 = (Sheep) sheep.clone();
            Sheep sheep2 = (Sheep) sheep.clone();
            Sheep sheep3 = (Sheep) sheep.clone();
            Sheep sheep4 = (Sheep) sheep.clone();
            System.out.println("sheep1:"+sheep1.toString());
            System.out.println("sheep2:"+sheep2.toString());
            System.out.println("sheep3:"+sheep3.toString());
            System.out.println("sheep4:"+sheep4.toString());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }



    }
}

执行结果:

sheep1:Sheep{name='tom', age=2, color='blue'}
sheep2:Sheep{name='tom', age=2, color='blue'}
sheep3:Sheep{name='tom', age=2, color='blue'}
sheep4:Sheep{name='tom', age=2, color='blue'}

可以看到使用原型模式的第一种实现方式,也可以达到目的。并且我们无需手动的初始化对象。在被克隆类增加新的字段后,我们也无需改动。但是现在的代码属于是浅拷贝,如果被克隆的类中存在引用类型字段,这个时候,我们新克隆出来的对象的引用对象只会指向被克隆对象的地址,而不是创建新的引用类型对象。

看代码:
我们的被克隆类新增了Dog类。

public class Sheep implements Cloneable {

    //名称
    private String name;
    //年龄
    private int age;
    //颜色
    private String color;
    //牧羊犬
    public Dog dog;

这个时候再执行我们的客户端代码:

sheep:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@511d50c0}1360875712
sheep1:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@511d50c0}1360875712
sheep2:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@511d50c0}1360875712
sheep3:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@511d50c0}1360875712
sheep4:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@511d50c0}1360875712

根据结果可以看到,新创建的sheep1、sheep2、sheep3、sheep4的dog的hashcode地址一致,并没有创建新的Dog对象。而是将引用指向了sheep的dog。

修改我们的Sheep类的clone方法,使我们可以达到深拷贝。

 @Override
    protected Object clone() throws CloneNotSupportedException {
        Object sheep = null;
        sheep = super.clone();
        Sheep sheepNew = (Sheep) sheep;
        sheepNew.dog = (Dog) dog.clone();
        return sheepNew;
    }

再次执行客户端代码得到结果:

sheep:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@511d50c0}1360875712
sheep1:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@60e53b93}1625635731
sheep2:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@5e2de80c}1580066828
sheep3:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@1d44bcfa}491044090
sheep4:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@266474c2}644117698

此时可以看到我们在克隆对象的同时也克隆了他的引用类型字段。

接下来我们看第二种方式:

Sheep、Dog实现Serializable接口并编写新的克隆方法

 public Object deepClone(){

        //创建流对象
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bais = null;
        ObjectInputStream ois = null;

        try{

            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);//当前这个对象以对象流的方式输出

            //反序列化
            bais = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bais);
            Sheep sheep = (Sheep) ois.readObject();
            return sheep;
        }catch(Exception e){
        }
        return null;
    }

执行下客户端代码:

public class Client {


    public static void main(String[] args) {
        Sheep sheep = new Sheep("tom",2,"blue");
        sheep.dog = new Dog("边牧", 3);

        Sheep sheep1 = (Sheep) sheep.deepClone();
        Sheep sheep2 = (Sheep) sheep.deepClone();
        Sheep sheep3 = (Sheep) sheep.deepClone();
        Sheep sheep4 = (Sheep) sheep.deepClone();
        System.out.println("sheep:"+sheep.toString()+sheep.dog.hashCode());
        System.out.println("sheep1:"+sheep1.toString()+sheep1.dog.hashCode());
        System.out.println("sheep2:"+sheep2.toString()+sheep2.dog.hashCode());
        System.out.println("sheep3:"+sheep3.toString()+sheep3.dog.hashCode());
        System.out.println("sheep4:"+sheep4.toString()+sheep4.dog.hashCode());


    }
}

打印结果:

sheep:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@d716361}225534817
sheep1:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@27973e9b}664223387
sheep2:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@312b1dae}824909230
sheep3:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@7530d0a}122883338
sheep4:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@27bc2616}666641942

可以看到第二种方式也可以达到我们想要的结果,并且可以解决浅拷贝的问题。而且在被克隆类新增引用类型字段后,我们无需修改代码。比第一种方式更好一些。

需要注意的:
在编写案例的时候,由于博主使用@Data注解,导致其重写了类的Equals和HashCode方法,
导致在输出结果的时候,使用了原型模式,打印的Dog的hashcode也一致。
导致博主反复的寻找问题,一致以为是clone方法出现问题。
记录下来,防止其他小伙伴遇到我遇到的这个坑。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值