一、原型模式的定义与特点

定义:原型(prototype)模式用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。

二、原型模式的优缺点

优点:

  • Java自带的原型模式基于内存二进制流的赋值,在性能上比慧姐new一个对象更加优良
  • 可以使用申客隆的方式保存对象的状态,使用原型模式将对象赋值一份,并将其状态保存起来,简化了创建对象的过程,以便在用的时候使用,可以辅助实现撤销操作。

缺点:

  • 需要为每一个类都配置一个clone方法
  • clone方法位于类的内部,当对已有类进行改造的时候,需要修改代码啊,违背了开闭原则。
  • 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦,因此,深克隆、浅克隆需要运用得当。

三、原型模式的结构与实现

结构如下

  1. 抽象原型类:规定了具体原型对象必须实现的接口
  2. 具体访问类:实现抽象原型类的clone()方法。它是可复制对象
  3. 访问类:使用具体原型类中的clone()方法来赋值新的对象
  1. 原型模式的结构图

模式的实现

原型模式的克隆分为浅克隆深克隆

  • 浅克隆:创建一个新的对象,新对象的属性和原来的对象完全相同,对于非基本类型属性,任然指向原有属性所指向的对象的内存地址。
  • 深克隆:创建一个新的对象,属性中引用的其他对象也会被克隆,不再只想原有对象地址。

直接上例子,浅克隆,要点

  1. 要克隆的对象实现了Cloneable接口
  2. 覆盖对象类的clone()方法。(覆盖clone()方法,访问修饰符设置为public
  3. 在clone()方法中调用super.clone()
  1. /**
  2. * 具体的原型类,实现了浅克隆
  3. * @author liyuan
  4. * @date 2021年05月26日 10:08
  5. */
  6. public class Student implements Cloneable{
  7. private String name;
  8. private Integer score;
  9. public String getName() {
  10. return name;
  11. }
  12. public void setName(String name) {
  13. this.name = name;
  14. }
  15. public Integer getScore() {
  16. return score;
  17. }
  18. public void setScore(Integer score) {
  19. this.score = score;
  20. }
  21. public Student() {
  22. System.out.println("student原型创建成功");
  23. }
  24. @Override
  25. public String toString() {
  26. return "Student [name=" + name + ", score=" + score + "]";
  27. }
  28. @Override
  29. public Object clone() throws CloneNotSupportedException {
  30. System.out.println("原型复制成功");
  31. return (Student)super.clone();
  32. }
  33. }

设置验证的方法

class StudentCloneTest{

    public static void main(String[] args) throws CloneNotSupportedException {
        Student student = new Student();
        student.setName("z3");
        student.setScore(99);
        Student student1 = (Student)student.clone();
        student1.setScore(80); // 注意修改student2的age值 但是没有影响 student1的值
        System.out.println("student==student1?"+(student==student1));
        System.out.println(student.getScore());
        System.out.println(student1.getScore());
    }
}

输出的结果是

student原型创建成功
原型复制成功
student==student1?false
99
80

接下来发觉浅克隆的缺点,直接上例子

public class Teacher implements Cloneable{

    private String name;

    private Student student;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    @Override
    public String toString() {
        return "Teacher [name=" + name + ", student=" + student + "]";
    }

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

执行验证的方法

class TeacherCloneTest{
    public static void main(String[] args) throws CloneNotSupportedException {
        Student s1 = new Student();
        s1.setScore(80);
        s1.setName("张三");
        Teacher teacher1 = new Teacher();
        teacher1.setName("小赵老师");
        teacher1.setStudent(s1);

        Teacher teacher2 = (Teacher) teacher1.clone();
        Student s2 = teacher2.getStudent();
        s2.setName("李四");
        s2.setScore(20);
        // 浅克隆导致的问题,输出的student的值被修改了
        System.out.println(teacher1);
        System.out.println(teacher2);
    }
}

输出的结果是

student原型创建成功
Teacher [name=小赵老师, student=Student [name=李四, score=20]]
Teacher [name=小赵老师, student=Student [name=李四, score=20]]

还原teacher和student重写的toString方法,输出如下代码的值:

System.out.println(teacher1.toString());
System.out.println(teacher2.toString());
System.out.println(teacher1.getStudent());
System.out.println(teacher2.getStudent());

结果如下:

com.liyuan.designMode.prototype.instance.Teacher@1b6d3586
com.liyuan.designMode.prototype.instance.Teacher@4554617c
com.liyuan.designMode.prototype.instance.Student@74a14482
com.liyuan.designMode.prototype.instance.Student@74a14482

很明显,浅克隆克隆出来的teacher对象是不同的地址,但是他们引用的student对象是同一个地址,因此当把student属性修改了,原来的teacher对象的student的属性也会被修改

深克隆

注意点:要克隆的类和类中所有非基本数据类型的属性对应的类必须在clone()方法中注意

public class TeacherDeep implements Cloneable {
    private String name;

    private Student student;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    @Override
    public String toString() {
        return "TeacherDeep [name=" + name + ", student=" + student + "]";
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        TeacherDeep teacher = (TeacherDeep)super.clone();
        teacher.setStudent((Student)teacher.getStudent().clone());
        return teacher;
    }
}

运行测试代码

class TeacherDeepCloneTest{
    public static void main(String[] args) throws CloneNotSupportedException {
        Student s1 = new Student();
        s1.setScore(80);
        s1.setName("张三");
        TeacherDeep teacher1 = new TeacherDeep();
        teacher1.setName("小赵老师");
        teacher1.setStudent(s1);

        TeacherDeep teacher2 = (TeacherDeep) teacher1.clone();
        Student s2 = teacher2.getStudent();
        s2.setName("李四");
        s2.setScore(20);
        // 浅克隆导致的问题,输出的student的值被修改了
        System.out.println(teacher1);
        System.out.println(teacher2);

    }
}

输出的结果是

student原型创建成功
原型复制成功
TeacherDeep [name=小赵老师, student=com.liyuan.designMode.prototype.instance.Student@1b6d3586]
TeacherDeep [name=小赵老师, student=com.liyuan.designMode.prototype.instance.Student@4554617c]

很明显,深克隆克服了浅克隆的缺点。

实现深克隆的方法还可以用序列化的方式处理

代码如下

public class TeacherDeepSerial implements Serializable {
    private String name;

    private StudentSerial student;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public StudentSerial getStudent() {
        return student;
    }

    public void setStudent(StudentSerial student) {
        this.student = student;
    }

    public Object deepClone() throws Exception {
        //将对象写入流中
        ByteArrayOutputStream bao=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(bao);
        oos.writeObject(this);
        //将对象从流中取出
        ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray());
        ObjectInputStream ois=new ObjectInputStream(bis);
        return(ois.readObject());
    }
}

public class StudentSerial implements Serializable {
    private String name;

    private Integer score;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getScore() {
        return score;
    }

    public void setScore(Integer score) {
        this.score = score;
    }

}

验证方法如下

class TeacherDeepSerialCloneTest{
    public static void main(String[] args) throws Exception {
        StudentSerial s1 = new StudentSerial();
        s1.setScore(80);
        s1.setName("张三");
        TeacherDeepSerial teacher1 = new TeacherDeepSerial();
        teacher1.setName("小赵老师");
        teacher1.setStudent(s1);
        TeacherDeepSerial teacher2 = (TeacherDeepSerial) teacher1.deepClone();
        System.out.println(teacher1);
        System.out.println(teacher2);
        System.out.println(teacher1.getStudent());
        System.out.println(teacher2.getStudent());
    }
}

输出的结果如下

com.liyuan.designMode.prototype.instance.TeacherDeepSerial@6d6f6e28
com.liyuan.designMode.prototype.instance.TeacherDeepSerial@723279cf
com.liyuan.designMode.prototype.instance.StudentSerial@4b67cf4d
com.liyuan.designMode.prototype.instance.StudentSerial@10f87f48

四、原型模式的应用场景

  1. 对象之间相同或者相似,即只是个别的几个属性不同的时候
  2. 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
  3. 创建一个对象需要繁琐的数据准备或者访问权限等,需要提高性能或者提高安全性。
  4. 系统中大量使用该对象,且各个调用者都需要给他的属性重新赋值

五、原型模式的扩展

原型模式可扩展为带原型管理器的原型模式,它在原型模式的基础上增加了一个原型管理器PrototypeManager类。该类用HashMap保存多个复制的原型,Client类可以通过管理器的get(String id)方法从中获取赋值的原型。其结构如下图所示

                                                                                                    原型管理器的结构图

【例】用带原型管理器的原型模式来生成包含“圆”和“正方形”等图形的原型,并且对其进行面积计算。

分析:本实例中由于存在不同的图形类,例如,“圆”和“正方形”,它们计算面积的方法不一样,所以需要用一个原型管理器来管理它们,其结构如如下:

具体的代码如下:

public interface Shape extends Cloneable{

    public Object clone();

    public void countArea();
}

public class PrototypeManager {

    private Map<String,Shape> ht = new HashMap<>();

    public PrototypeManager() {
        ht.put("Circle", new Circle());
        ht.put("Square", new Square());
    }

    public void addshape(String key, Shape obj) {
        ht.put(key, obj);
    }

    public Shape getShape(String key) {
        Shape temp = ht.get(key);
        return (Shape) temp.clone();
    }
}

public class Circle implements Shape{

    @Override
    public Object clone() {
        Circle w = null;
        try {
            w = (Circle) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("拷贝圆失败!");
        }
        return w;
    }

    @Override
    public void countArea() {
        int r = 0;
        System.out.print("这是一个圆,请输入圆的半径:");
        Scanner input = new Scanner(System.in);
        r = input.nextInt();
        System.out.println("该圆的面积=" + 3.1415 * r * r + "\n");
    }
}

public class Square implements Shape{

    @Override
    public Object clone() {
        Square w = null;
        try {
            w = (Square) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("拷贝正方形失败!");
        }
        return w;
    }

    @Override
    public void countArea() {
        int a = 0;
        System.out.print("这是一个正方形,请输入它的边长:");
        Scanner input = new Scanner(System.in);
        a = input.nextInt();
        System.out.println("该正方形的面积=" + a * a + "\n");
    }
}

public class Test {

    public static void main(String[] args) {
        PrototypeManager prototypeManager = new PrototypeManager();
        Shape circle = prototypeManager.getShape("Circle");
        circle.countArea();

        Shape square = prototypeManager.getShape("Square");
        square.countArea();

    }
}

测试结果如下

这是一个圆,请输入圆的半径:1
该圆的面积=3.1415

这是一个正方形,请输入它的边长:1
该正方形的面积=1

六、思考

  1. 现有业务中,哪里能用到原型模式?
  2. spring中哪里用到了原型模式?