设计模式——原型模式

原型模式(实现clone)

场景:克隆羊

创建10只姓名为Tom,年龄为1,颜色为白色的羊

原始方法(灵活性差)

@Data
@NoArgsConstructor
@AllArgsConstructor
class Sheep{
    private String name;
    private Integer age;
    private String color;
}

public class CloneSheepDemo {
    public static void main(String[] args) {
        Sheep prototypeSheep=new Sheep("tom",1,"白");
        Sheep sheep1=new Sheep(prototypeSheep.getName(), prototypeSheep.getAge(), prototypeSheep.getColor());
        Sheep sheep2=new Sheep(prototypeSheep.getName(), prototypeSheep.getAge(), prototypeSheep.getColor());
        Sheep sheep3=new Sheep(prototypeSheep.getName(), prototypeSheep.getAge(), prototypeSheep.getColor());
        Sheep sheep4=new Sheep(prototypeSheep.getName(), prototypeSheep.getAge(), prototypeSheep.getColor());
        Sheep sheep5=new Sheep(prototypeSheep.getName(), prototypeSheep.getAge(), prototypeSheep.getColor());
    }
}

先创建一只满足要求的羊作为原型,然后调用有参构造方法new十只羊,参数来源为这只羊

缺点:不够灵活和优雅,需要cv大量的代码,假如后面这只羊需要添加一个属性,所以创建这个羊的代码都需要改,非常麻烦

解决方法:原型模式(简单而优雅)

让需要clone的类实现Cloneable接口,然后实现里面的Clone方法

@Data
@NoArgsConstructor
@AllArgsConstructor
class Sheep implements Cloneable{
    private String name;
    private Integer age;
    private String color;

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

/**
 * @author 李天航
 */
public class CloneSheepDemo {
    @SneakyThrows
    public static void main(String[] args) {
        Sheep prototypeSheep=new Sheep("tom",1,"白");
        Sheep sheep1= (Sheep) prototypeSheep.clone();
        Sheep sheep2= (Sheep) prototypeSheep.clone();
        Sheep sheep3= (Sheep) prototypeSheep.clone();
        Sheep sheep4= (Sheep) prototypeSheep.clone();
        Sheep sheep5= (Sheep) prototypeSheep.clone();
        System.out.println(sheep1);
        System.out.println(sheep2);
        System.out.println(sheep3);
        System.out.println(sheep4);
        System.out.println(sheep5);
    }
}

这样在进行clone的适合我们只需要调用原型的clone方法即可,如果后面增加了属性我们也不需要修改代码,由父类Object来帮我们完成拷贝工作,我们也可以加上一些自己的逻辑。同时clone方法是native方法,由字节码来高效完成拷贝工作,比我们调用构造函数的效率要高。同时客户端clone时无需关系clone具体是怎么实现的,具体的实现交给clone方法内部来完成。

而实现Cloneable接口,为了让这个类可克隆,否则会抛出CloneNotSupportedException异常

Spring源码

image-20220527211041246

我们在注入bean的时候,除了可以将bean设置为单例的(singleton),也可以将bean设置为多例的(prototype),设置为多例的时候,创建bean的方式就是原型模式

浅拷贝和深拷贝

浅拷贝

假如类中有一个引用类型的变量:

@NoArgsConstructor
@AllArgsConstructor
class Hobby{
    String name;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class Sheep implements Cloneable{
    private String name;
    private Integer age;
    private String color;
    private Hobby hobby;

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

此时如果直接使用Object的拷贝方法,基本数据类型和String类型可以被正常复制,而引用类型则是进行引用复制而不会创建新的对象

image-20220527221637820

可以看到所以的Sheep对象中的hobby都指向同一个对象

此时的拷贝就是浅拷贝,相当于对上述所以字段使用等号=来赋值

深拷贝

深拷贝就是字段中依赖的其他对象也都是新的对象,而不是和原型指向相同的对象

可以通过将依赖的对象拿出来单独进行clone,这样就能达到深拷贝:

@NoArgsConstructor
@AllArgsConstructor
class Hobby implements Cloneable{
    String name;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
class Sheep implements Cloneable{
    private String name;
    private Integer age;
    private String color;
    private Hobby hobby;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Sheep clone = (Sheep) super.clone();
        clone.setHobby((Hobby) hobby.clone());
        return clone;
    }
}

但是如果依赖的对象很多,甚至依赖的对象还依赖了其他类的对象,这样一个一个clone会很麻烦,因而我们可以采用JDK的序列化机制来实现深拷贝。

@NoArgsConstructor
@AllArgsConstructor
class Hobby implements Serializable{
    String name;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class Sheep implements Serializable{
    private String name;
    private Integer age;
    private String color;
    private Hobby hobby;

    protected Object deepClone() throws CloneNotSupportedException {
        ByteArrayOutputStream byteArrayOutputStream=null;
        ObjectOutputStream objectOutputStream=null;
        ByteArrayInputStream byteArrayInputStream=null;
        ObjectInputStream objectInputStream=null;
        try {
            byteArrayOutputStream=new ByteArrayOutputStream();
            objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(this);

            byteArrayInputStream=new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            objectInputStream=new ObjectInputStream(byteArrayInputStream);
            return objectInputStream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            try {
                assert objectOutputStream != null;
                assert objectInputStream != null;

                objectOutputStream.close();
                objectInputStream.close();
                byteArrayInputStream.close();
                byteArrayOutputStream.close();
            } catch (IOException | NullPointerException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

看着代码很多,其实大部分都是在处理异常和关闭流和初始化流,核心代码就六行:

            //开启一个字节数组流作为缓冲区
			byteArrayOutputStream=new ByteArrayOutputStream();
			//将这个对象序列化并写入上面的缓冲区
            objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(this);
			
			//创建对象数据流并从缓冲区中读进来进行反序列化
            byteArrayInputStream=new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            objectInputStream=new ObjectInputStream(byteArrayInputStream);
			//读对象的时候会创建一个新的对比保存反序列化的结果
            return objectInputStream.readObject();

因为需要序列化和反序列化,所以需要实现Serializable,其实就只是一个标志,表示这个对象可以序列化

另外,因为我们的深拷贝没有用到super.clone()所以可以不用实现Cloneable接口,自己另写一个方法即可

image-20220527233052818

如图所示,所有的Hobby对象都不一样

使用JDK的序列化机制就不需要我们一个一个手动clone引用类型的对象了,十分方便,也推荐使用

特点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zvzpd2Ub-1653708381573)(https://s2.loli.net/2022/05/28/f4z62swvNlWPuIn.png)]

原型模式深拷贝违背了OCP原则(是吧,想要遵循所有的原则几乎是不可能的),因为我们要为之前写好的类配备一个clone方法,或者让他实现一个Serializable接口。不过这个修改成本其实是很低的,修改后不会改变原有的业务逻辑,并且我们一般会在开发时直接继承Serializable接口,也就消除了这个问题。(我们只需要知道它违反了这个原则即可,需要用的时候还是正常用,不用原型模式耦合度会更大)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值