设计模式 (五) 原型模式

一、简介

  • 概念: 使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象,原型模式属于创建型模式,它提供了一种创建对象的最佳方式。

形象比喻:细胞复制分裂,下面是网上找的细胞分裂图

由图可见,复制新细胞的行为也是细胞自身发起,其实就是说原型对象自己不仅是个对象还是个工厂。

原型模式,通过克隆方式创建的对象是全新的对象,它们都是有自己的新的地址,hashcode不同, 复制新生成的对象在修改后,不会影响到之前被复制的对象,每一个克隆对象都是相对独立的。

二、角色说明

原型模式中的角色主要有:抽象原型类、具体原型类、客户端使用类三种角色。下面是各个角色的说明:

  • 抽象原型类(prototype):声明克隆方法的接口,是所有具体原型类的公共父类,它可以是接口,抽象类甚至是一个具体的实现类。抽象原型类需要具备以下两个条件:
  1. 实现Cloneable接口,说明可以使用clone方法。只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
  2. 重写Object类中的clone方法,clone方法从Object基类继承下来,作用是返回对象的一个拷贝;
  • 具体原型类(concretePrototype):实现了抽象原型类中声明的克隆方法,返回一个自己的克隆对象;
  • 客户端类(Client):使用原型对象只需要通过工厂方式创建或者直接new(实例化一个)原型对象,然后通过原型对象的克隆方法就能获得多个相同的对象;

三、示例

下面通过一个简单的示例说明原型模式的应用,UML类图如下:

相关代码:

【a】抽象原型类

package com.wsh.springboot.springbootdesignpattern.prototypepattern.prototype;

/**
 * @Description: 抽象原型类(prototype)
 * @author: weishihuai
 * @Date: 2019/10/22 17:57
 */
public abstract class AbstractShape implements Cloneable {

    /**
     * 绘制图形方法
     */
    abstract void draw();

    /**
     * 名称
     */
    private String name;

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

    @Override
    protected Object clone() {
        Object object = null;
        try {
            object = super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return object;
    }

    @Override
    public String toString() {
        return "AbstractShape{" +
                "name='" + name + '\'' +
                '}';
    }
}

 可见,抽象原型类实现了Cloneable接口并且重写了Object.clone()方法,返回了一个拷贝之后的对象。

【b】具体原型类

/**
 * @Description: 长方形具体原型类(concretePrototype)
 * @author: weishihuai
 * @Date: 2019/10/22 17:58
 */
public class Rectangle extends AbstractShape {

    public Rectangle() {
        setName("长方形");
    }

    @Override
    void draw() {
        System.out.println("绘制长方形...");
    }

    @Override
    public String toString() {
        return super.toString();
    }
}

/**
 * @Description: 圆形具体原型类(concretePrototype)
 * @author: weishihuai
 * @Date: 2019/10/22 17:58
 */
public class Circle extends AbstractShape {

    public Circle() {
        setName("圆形");
    }

    @Override
    void draw() {
        System.out.println("绘制圆形...");
    }

    @Override
    public String toString() {
        return super.toString();
    }
}

【c】客户类

/**
 * @Description: 客户类(Client)
 * @author: weishihuai
 * @Date: 2019/10/22 18:01
 */
public class Client {
    public static void main(String[] args) {
        AbstractShape rectangle = new Rectangle();
        System.out.println(rectangle + ">>>>>>" + rectangle.hashCode());
        Rectangle cloneRectangle = (Rectangle) rectangle.clone();
        System.out.println(cloneRectangle + ">>>>>>" + cloneRectangle.hashCode());
        Rectangle cloneRectangle2 = (Rectangle) rectangle.clone();
        System.out.println(cloneRectangle2 + ">>>>>>" + cloneRectangle2.hashCode());
    }
}

【d】运行结果

可见,拷贝之后的对象与原对象hashCode不一样,他们是完全不同的两个对象。

由原型模式引出的两个概念:浅克隆和深克隆。

  • 浅克隆:在复制对象的时候,只会复制简单基本数据类型和String等,对于引用类型的话,只会复制引用,就是它和原对象指向的还是同一个地址,修改之后会影响到旧对象对应的值。
  • 深克隆:就是在复制对象的时候,无论是基本数据类型,还是引用类型,如数组、对象等,全部都会重复复制多一份,复制之后的新对象和旧对象相互独立,互不影响,当修改克隆对象后对于原型对象没有丝毫影响。

综上,在实际工作中,我们肯定要实现深克隆,浅克隆存在一些问题,在复制基本数据类型时还是可以用的。

四、浅克隆

下面通过一个简单的示例说明浅克隆:

【a】教师类

/**
 * @Description: 教师类
 * @author: weishihuai
 * @Date: 2019/10/22 20:10
 */
public class Teacher {
    private Integer pkid;
    private String name;

    public Teacher(Integer pkid, String name) {
        this.pkid = pkid;
        this.name = name;
    }

    public Integer getPkid() {
        return pkid;
    }

    public void setPkid(Integer pkid) {
        this.pkid = pkid;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Teacher{" +
                "pkid='" + pkid + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

【】【b】学生类

package com.wsh.springboot.springbootdesignpattern.prototypepattern.shallowclone;

/**
 * @Description:
 * @author: weishihuai
 * @Date: 2019/10/22 20:11
 */
public class Student implements Cloneable {
    private double weight;
    private Integer pkid;
    private String name;
    private Teacher teacher;

    public Student(double weight, Integer pkid, String name, Teacher teacher) {
        this.weight = weight;
        this.pkid = pkid;
        this.name = name;
        this.teacher = teacher;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    public Integer getPkid() {
        return pkid;
    }

    public void setPkid(Integer pkid) {
        this.pkid = pkid;
    }

    public String getName() {
        return name;
    }

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

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    @Override
    protected Object clone() {
        Object object = null;
        try {
            object = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
        return object;
    }

    @Override
    public String toString() {
        return "Student{" +
                "weight=" + weight +
                ", pkid=" + pkid +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
}

【c】测试用例

/**
 * @Description: 客户端
 * @author: weishihuai
 * @Date: 2019/10/22 20:13
 */
public class Client {
    public static void main(String[] args) {
        Teacher teacher = new Teacher(1, "黄老师");
        Student student = new Student(50.0D, 1, "张三", teacher);
        Student student1 = (Student) student.clone();

        System.out.println(student == student1);

        System.out.println(student.getTeacher() == student1.getTeacher());
        System.out.println("复制前teacher的hashCode: " + student.getTeacher().hashCode());
        System.out.println("复制之后teacher新对象的hashCode: " + student1.getTeacher().hashCode());


        System.out.println("克隆之前student学生的老师: " + student.getTeacher().getName());
        //克隆对象student1将老师姓名修改为张老师,导致旧student的老师姓名也被修改。
        Teacher teacher1 = student1.getTeacher();
        teacher1.setName("张老师");
        System.out.println("克隆之后student学生的老师: " + student.getTeacher().getName());

        System.out.println("克隆之后的新学生的老师: " + student.getTeacher().getName());
    }
}

 【d】运行结果

可见浅拷贝之后的student对象与原来的对象是不同的对象,但是student对象里面的teacher对象与原teacher还是指向的同一个地址,旧对象修改会导致新对象也被修改,这就是浅拷贝的缺点,浅拷贝在克隆基本数据类型的时候比较常见。

 注意:String是一个例外,但对于我们编程来说可以和操作基本数据类型一样做,基本没影响,大大方便了我们的编程。String类型的变量clone后的表现好象也实现了深度clone,但其实只是一个假象。因为String是一个不可更改的类(immutable class),在所有String类中的函数都不能更改自身的值。

五、深克隆

还是用上面浅克隆的例子说明怎么实现深克隆,深克隆主要用两种实现方法: 重写clone()方法、序列化方式

(一)、重写clone()方法

【a】学生类

public class Student implements Cloneable {
    private double weight;
    private Integer pkid;
    private String name;
    private Teacher teacher;

    public Student(double weight, Integer pkid, String name, Teacher teacher) {
        this.weight = weight;
        this.pkid = pkid;
        this.name = name;
        this.teacher = teacher;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    public Integer getPkid() {
        return pkid;
    }

    public void setPkid(Integer pkid) {
        this.pkid = pkid;
    }

    public String getName() {
        return name;
    }

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

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    @Override
    protected Object clone() {
        Student student = null;
        try {
            student = (Student) super.clone();
            student.teacher = (Teacher) this.teacher.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
        return student;
    }

    @Override
    public String toString() {
        return "Student{" +
                "weight=" + weight +
                ", pkid=" + pkid +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
}

【b】教师类

/**
 * @Description: 教师类
 * @author: weishihuai
 * @Date: 2019/10/22 20:10
 */
public class Teacher implements Cloneable {
    private Integer pkid;
    private String name;

    public Teacher(Integer pkid, String name) {
        this.pkid = pkid;
        this.name = name;
    }

    public Integer getPkid() {
        return pkid;
    }

    public void setPkid(Integer pkid) {
        this.pkid = pkid;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Teacher{" +
                "pkid='" + pkid + '\'' +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    protected Object clone() {
        Object object = null;
        try {
            object = super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return object;
    }
}

【c】 客户端

/**
 * @Description: 客户端
 * @author: weishihuai
 * @Date: 2019/10/22 20:13
 */
public class Client {
    public static void main(String[] args) {
        Teacher teacher = new Teacher(1, "黄老师");
        Student student = new Student(50.0D, 1, "张三", teacher);
        Student student1 = (Student) student.clone();

        System.out.println(student);
        System.out.println(student1);

        System.out.println(student == student1);
        System.out.println(student.getTeacher() == student1.getTeacher());
        System.out.println("复制前teacher的hashCode: " + student.getTeacher().hashCode());
        System.out.println("复制之后teacher新对象的hashCode: " + student1.getTeacher().hashCode());
    }
}

【d】运行结果

可见,复制之后的student对象以及对应的teacher对象都是全新的,与旧对象互不影响,这就是深克隆。深拷贝关键点在于,实现cloneable接口以及用object的clone方法。

(二)、序列化方式

简单的讲就是序列化就将对象写到流的一个过程,写到流里面去(就是字节流)就等于复制了对象,但是原来的对象并没有动,只是复制将类型通过流的方式进行读取,然后写到另一块内存地址中。

【a】教师类

import java.io.Serializable;

/**
 * @Description: 教师类
 * @author: weishihuai
 * @Date: 2019/10/22 20:10
 */
public class Teacher implements Serializable {
    private Integer pkid;
    private String name;

    public Teacher(Integer pkid, String name) {
        this.pkid = pkid;
        this.name = name;
    }

    public Integer getPkid() {
        return pkid;
    }

    public void setPkid(Integer pkid) {
        this.pkid = pkid;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Teacher{" +
                "pkid='" + pkid + '\'' +
                ", name='" + name + '\'' +
                '}';
    }

}

【b】学生类

import java.io.*;

/**
 * @Description:
 * @author: weishihuai
 * @Date: 2019/10/22 20:11
 */
public class Student implements Serializable {
    private double weight;
    private Integer pkid;
    private String name;
    private Teacher teacher;

    public Student(double weight, Integer pkid, String name, Teacher teacher) {
        this.weight = weight;
        this.pkid = pkid;
        this.name = name;
        this.teacher = teacher;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    public Integer getPkid() {
        return pkid;
    }

    public void setPkid(Integer pkid) {
        this.pkid = pkid;
    }

    public String getName() {
        return name;
    }

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

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    @Override
    public String toString() {
        return "Student{" +
                "weight=" + weight +
                ", pkid=" + pkid +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }


    public Student deepCloneBySerializable() {
        Student student = null;
        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);
            student = (Student) objectInputStream.readObject();
            return student;
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (null != objectInputStream) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != byteArrayInputStream) {
                try {
                    byteArrayInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != objectOutputStream) {
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != byteArrayOutputStream) {
                try {
                    byteArrayOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return student;
    }
}

【c】客户端

/**
 * @Description: 客户端
 * @author: weishihuai
 * @Date: 2019/10/22 20:13
 */
public class Client {
    public static void main(String[] args) {
        Teacher teacher = new Teacher(1, "黄老师");
        Student student = new Student(50.0D, 1, "张三", teacher);
        Student student1 = (Student) student.deepCloneBySerializable();

        System.out.println(student);
        System.out.println(student1);

        System.out.println(student == student1);
        System.out.println(student.getTeacher() == student1.getTeacher());
        System.out.println("复制前teacher的hashCode: " + student.getTeacher().hashCode());
        System.out.println("复制之后teacher新对象的hashCode: " + student1.getTeacher().hashCode());
    }
}

这就是使用序列化方式深拷贝对象,推荐使用此种方式实现深拷贝,避免重写clone()方法逻辑太多于复杂。

六、总结

  • 优点:
  1. 使用原型模式创建对象比直接new一个对象在性能上要好的多,性能提高;
  2. 逃避构造函数的约束,使用原型模式复制对象不会调用类的构造方法,直接在内存中复制数据;
  3. 简化对象的创建;
  • 缺点:
  1. 原型模式需要每个类都实现Cloneable接口,并且各自重写clone()克隆方法,这些对于新创建的类影响不大,但是对于已存在的类的话,需要一个一个去修改源代码,这违背了开闭原则。
  2. 在实现深克隆时需要编写较为复杂的代码。
  • 适用场景:
  • 1. 当直接创建对象的代价比较大时,占用CPU大时;例如,一个对象需要在一个高代价的数据库操作之后被创建,我们可以先将对象缓存起来,下次请求的时候直接返回缓存对象,如果需要修改,再对其稍作修改;
  • 2. 一个对象多个修改者的场景;
  • 3. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用;
  • 4. 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值