您现在的位置是:首页 >技术杂谈 >设计模式详解(六)——原型模式网站首页技术杂谈

设计模式详解(六)——原型模式

Yarrow-Y 2024-06-17 11:28:29
简介设计模式详解(六)——原型模式

原型模式简介

原型模式定义
原型模式是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。就是用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

原型模式包含以下角色:
抽象原型类:规定了具体原型对象必须实现的clone()方法
具体原型类:实现了抽象原型类的clone()方法,它是可以被复制的对象
访问类:使用具体原型类中的clone()方法来复制新对象

原型模式优缺点:
优点:
1.可以克隆对象, 无需与它们所属的具体类相耦合
2.可以克隆预生成原型, 避免反复运行初始化代码
3.可以更方便地生成复杂对象
4.可以用继承以外的方式来处理复杂对象的不同配置
5.性能提高
6.逃避构造函数的约束
7.java自带的原型模型基于内存二进制流的复制,在性能上比直接new一个对象更加优良

缺点:
1.克隆包含循环引用的复杂对象可能会非常麻烦
2.配备克隆方法需要对类的功能进行通盘考虑
3.必须实现 Cloneable 接口
4.需要为每一个类都配置一个 clone 方法
5.clone方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则

使用场景

  1. 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源
  2. 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式
  3. 一个对象多个修改者的场景
  4. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
  5. 如果需要复制一些对象, 同时又希望代码独立于这些对象所属的具体类, 可以使用原型模式
  6. 对象之间相同或相似,即只是个别的几个属性不同的时候
  7. 创建对象成本大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源
  8. 创建一个对象需要频繁的数据准备或访问权限等,需要提高性能或者提高安全性
  9. 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值

原型模式的实现
原型模式的克隆分为浅克隆和深克隆,实现克隆的方式:
**浅克隆:**创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。或者创建一个新对象,复制对象时仅仅复制对象本身,包括基本属性,但该对象的属性引用其他对象时,该引用对象不会被复制,即拷贝出来的对象与被拷贝出来的对象中的属性引用的对象是同一个(仍指向原有属性所指向的对象的内存地址);
**深克隆:**创建一个新对象,属性中引用的其他对象也会被克隆,即修改被克隆对象的任何属性都不会影响到克隆出来的对象(不再指向原有对象地址)

以下举一个例子:
克隆羊:
Sheep类实现了Cloneable接口,重写了clone方法:
1.主方法

/**
 * @author yyx
 */
public class Sheep implements Cloneable {
    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 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 + ''' +
                '}';
    }

    //使用默认的克隆方法
    @Override
    protected Object clone() {
        Sheep sheep = null;
        try {
            sheep = (Sheep) super.clone();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sheep;
    }
}

2.测试类Client

/**
 * @author yyx
 */
public class Client {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("DuoLi", 5, "白色");
        Sheep sheep1 = (Sheep) sheep.clone();
        Sheep sheep2 = (Sheep) sheep.clone();
        Sheep sheep3 = (Sheep) sheep.clone();

        System.out.println("sheep1=" + sheep1);
        System.out.println("sheep2=" + sheep2);
        System.out.println("sheep3=" + sheep3);
    }
}

运行结果如下所示:

sheep1=Sheep{name='DuoLi', age=5, color='白色'}
sheep2=Sheep{name='DuoLi', age=5, color='白色'}
sheep3=Sheep{name='DuoLi', age=5, color='白色'}

浅拷贝案例
在上面代码修改,在属性中多加个羊的朋友属性:

  1. 主方法
/**
 * @author yyx
 */
public class Sheep implements Cloneable {
    private String name;
    private int age;
    private String color;
    public Sheep friend;

    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 + ''' +
                '}';
    }

    //使用默认的克隆方法
    @Override
    protected Object clone() {
        Sheep sheep = null;
        try {
            sheep = (Sheep) super.clone();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sheep;
    }
}

2.测试类Client

/**
 * @author yyx
 */
public class Client {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("DuoLi", 5, "白色");
        sheep.friend=new Sheep("haha",6,"黑色");
        Sheep sheep1 = (Sheep) sheep.clone();
        Sheep sheep2 = (Sheep) sheep.clone();
        Sheep sheep3 = (Sheep) sheep.clone();

        System.out.println("sheep1=" + sheep1);
        System.out.println("sheep2=" + sheep2);
        System.out.println("sheep3=" + sheep3);

        System.out.println("sheep4="+sheep1+";sheep4 friend="+sheep1.friend.hashCode());
        System.out.println("sheep5="+sheep2+";sheep5 friend="+sheep2.friend.hashCode());
    }
}

运行结果如下所示:

sheep1=Sheep{name='DuoLi', age=5, color='白色'}
sheep2=Sheep{name='DuoLi', age=5, color='白色'}
sheep3=Sheep{name='DuoLi', age=5, color='白色'}
sheep4=Sheep{name='DuoLi', age=5, color='白色'};sheep4 friend=460141958
sheep5=Sheep{name='DuoLi', age=5, color='白色'};sheep5 friend=460141958

结论:
结论是克隆出来的羊并没有重新创建朋友对象,而是指向了原羊的朋友对象。

浅拷贝简述:
对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性复制一份给新的对象。
对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将成员变量的引用值(内存地址)复制了一份给新的对象。
浅拷贝是使用默认的clone()方法来实现的(sheep=(Sheep) super.clone();)

深拷贝案例
深拷贝实现方式1:重写clone方法来实现
深拷贝实现方式2:通过对象序列化来实现

方式一:重写clone方法实现
类DeepClone:

import java.io.Serializable;

/**
 * @author yyx
 */
public class DeepClone implements Cloneable, Serializable {
    private static final long serialVersionUID=1L;
    private String name;
    private String age;

    public DeepClone(String name, String age) {
        this.name = name;
        this.age = age;
    }

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

类DeepPrototype:

import java.io.Serializable;

/**
 * @author yyx
 */
public class DeepPrototype implements Cloneable, Serializable {
    String name;
    DeepClone deepClone;

    //方式一:使用clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object deep=null;
        deep=super.clone();
        //对引用数据类型的属性,进行单独处理
        DeepPrototype deepPrototype=(DeepPrototype)deep;
        deepPrototype.deepClone=(DeepClone) deepClone.clone();
        return deepPrototype;
    }
}

测试方法Client :

/**
 * @author yyx
 */
public class Client {
    public static void main(String[] args) {
        DeepPrototype p=new DeepPrototype();
        p.name="haha";
        p.deepClone=new DeepClone("xixi","xixi.class");
        DeepPrototype p1 = null;
        try {
            p1= (DeepPrototype) p.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        System.out.println("name="+p.name+";deepClone="+p.deepClone.hashCode());
        System.out.println("name="+p1.name+";deepClone="+p1.deepClone.hashCode());
    }
}

运行结果如下所示:

name=haha;deepCloneableTarget=460141958
name=haha;deepCloneableTarget=1163157884

结论:可发现这样指向的就不是同一个内存地址了。但是如果有多个引用类型的属性,需要一个一个单独处理。

方式二:通过反序列化
DeepPrototype的克隆方法:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * @author yyx
 */
public class DeepPrototype implements Cloneable, Serializable {
    String name;
    DeepClone deepClone;

//    //方式一:使用clone方法
//    @Override
//    protected Object clone() throws CloneNotSupportedException {
//        Object deep=null;
//        deep=super.clone();
//        //对引用数据类型的属性,进行单独处理
//        DeepPrototype deepPrototype=(DeepPrototype)deep;
//        deepPrototype.deepClone=(DeepClone) deepClone.clone();
//        return deepPrototype;
//    }

    //方式二:通过序列化的方式(推荐使用)
    public Object deepClone() {
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;
        try {
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);//将当前的对象以对象流的方式输出

            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            DeepPrototype deepPrototype = (DeepPrototype) ois.readObject();
            return deepPrototype;
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

测试方法Client :

/**
 * @author yyx
 */
public class Client {
    public static void main(String[] args) {
        DeepPrototype p=new DeepPrototype();
        p.name="haha";
        p.deepClone=new DeepClone("xixi","xixi.class");

        DeepPrototype p2= (DeepPrototype) p.deepClone();
        System.out.println("name="+p.name+";deepCloneableTarget="+p.deepClone.hashCode());
        System.out.println("name="+p2.name+";deepCloneableTarget="+p2.deepClone.hashCode());
    }
}


运行结果如下所示:

name=haha;deepCloneableTarget=1020371697
name=haha;deepCloneableTarget=2094548358

结论:多个引用类型也是一次即可。

深拷贝注意事项:
创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时可能够提高效率。
如果原始对象发生变化(增加或者减少属性),其他克隆对象的也会发生相应的变化,无需修改代码。
在实现深克隆时可能需要比较复杂的代码。

总而言之:
对于一些复杂对象的创建,复制原型对象可能比使用构造函数创建实例更方便。
可以动态增加或减少产品类。由于创建产品类实例的方法是产品内部具有的,所以新增产品对整个结构没有影响。
可以利用深克隆的方式保存对象的状态,

不足:
需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。
实现深克隆时可能需要编写复杂的代


以上代码下载请点击该链接:https://github.com/Yarrow052/Java-package.git

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。