Java设计模式—— 原型模式

Posted 小小印z

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java设计模式—— 原型模式相关的知识,希望对你有一定的参考价值。

目录

一、问题的提出

二、原型模式

三、原型模式具体实现方法

(1)利用构造函数方法

浅复制

深复制

(2)利用Cloneable接口方法

浅复制

深复制

        (3)利用Serializable序列化接口方法


        原型模式是指用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。适合原型模式的情景如下:

  • 程序需要从一个对象出发,得到若干个和其状态相同,并可独立变化其状态的对象时;
  • 对象创建需要独立于它的构造方法和表示时
  • 以原型对象为基础,克隆新的对象,并完善对象实例变量时

一、问题的提出

        在计算机程序开发过程中,有时会遇到为一个类创建多个实例的情况,这些实例内部成员往往完全相同或有细微的差异,而且实力的创建开销比较大或者需要输入较多参数。如果能通过复制一个已创建的对象实例来重复创建多个相同的对象,这就可以大大减少创建对象的开销,这个时候就需要原型模式。 

二、原型模式

        原型模式是指使用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象,原型模式是一种对象创建型模式

原型模式复制功能分为浅复制深复制

(1)浅复制

浅复制(Shallow Copy)是指在复制一个对象时,仅复制对象本身及其所有基本数据类型的成员变量。而对于非基本数据类型的成员变量,则只是将其引用复制过来,因此,原对象和复制后的对象会共享同一个引用对象。因此,如果原对象的引用对象发生改变,复制后的对象也会受到影响

(2)深复制

深复制(Deep Copy)是指在复制一个对象时,不仅复制对象本身及其所有基本数据类型的成员变量,而且会对所有的非基本数据类型的成员变量进行递归复制。因此,原对象和复制后的对象不会共享任何引用对象

三、原型模式具体实现方法

student类:

        student类包含两个基本数据类型name、age、一个引用类型变量add。

package shejimoshi.yuanxing;

public class Student 
    String name;
    int age;
    Address add;

    public Student(String name, int age, Address add) 
        this.name = name;
        this.age = age;
        this.add = add;  //籍贯
    

    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 Address getAdd() 
        return add;
    

    public void setAdd(Address add) 
        this.add = add;
    

   

Address类:

package shejimoshi.yuanxing;

class Address
        String pro;  //出生省
        String city; //出生市
        String zip;  //出生地邮编

        public Address(String pro, String city, String zip) 
            this.pro = pro;
            this.city = city;
            this.zip = zip;
        

        public String getPro() 
            return pro;
        

        public void setPro(String pro) 
            this.pro = pro;
        

        public String getCity() 
            return city;
        

        public void setCity(String city) 
            this.city = city;
        

        public String getZip() 
            return zip;
        

        public void setZip(String zip) 
            this.zip = zip;
        
    

原型复制常用方法有三种:利用构造函数方法、利用Cloneable接口方法、利用Serializable序列化接口方法

(1)利用构造函数方法

浅复制

所需类代码如下所示:

这里写了一个构造方法,传入一个Student对象进行复制。利用已经创建好的学生对象s,构建复制对象copy_s。

package shejimoshi.yuanxing;

public class Student 
    String name;
    int age;
    Address add;

    public Student(String name, int age, Address add) 
        this.name = name;
        this.age = age;
        this.add = add;  //籍贯
    
    Student(Student s) 
        name = s.getName();
        age = s.getAge();
        add = s.getAdd();  //引用地址拷贝
    


测试类:

public class Test 
    public static void main(String[] args) 
        Address address = new Address("辽宁","大连","1618020202");
        Student s = new Student("zhangyin",20,address);
        //在这里复制出一个对象
        Student copy_s = new Student(s);
        System.out.println(copy_s.getName());
        System.out.println(copy_s.getAge());
        System.out.println(copy_s.getAdd().city);
    

深复制

在深复制中其实就是复制引用类型对象时创建了一个新的

public class Student 
    String name;
    int age;
    Address add;

    public Student(String name, int age, Address add) 
        this.name = name;
        this.age = age;
        this.add = add;  //籍贯
    
     Student(Student s) 
        name = s.getName();
        age = s.getAge();
        add = new Address(s.getAdd());
    
class Address
        String pro;  //出生省
        String city; //出生市
        String zip;  //出生地邮编

        public Address(String pro, String city, String zip) 
            this.pro = pro;
            this.city = city;
            this.zip = zip;
        
        public Address(Address address) 
            pro = address.getPro();
            city = address.getCity();
            zip = address.getZip();
        
//省略setter getter

    

(2)利用Cloneable接口方法

        Java类都继承自Object类。事实上,Object类提供了一个clone() 方法,可以将一个Java对象复制一份,因此在Java中可以直接用Object提供clone()方法来实现对象的克隆。但是clone是一个protected的方法,外部类不能直接调用。在此Java规定了对象复制规范:能够实现复制的Java类必须实现一个标识接口Cloneable。

        该接口中没有定义任何方法,因此它仅是起到一个标识作用,表达的语义是:该类用到了对象复制功能,因此抛开本模式而言,空接口有时也是非常有意义的。 

浅复制

public class Student implements Cloneable
    String name;
    int age;
    Address add;

    public Student(String name, int age, Address add) 
        this.name = name;
        this.age = age;
        this.add = add;  //籍贯
    
    
    protected Object clone() throws CloneNotSupportedException 
        return super.clone();
    

测试类:

public class Test 
    public static void main(String[] args) throws CloneNotSupportedException 
        Address address = new Address("辽宁","大连","1618020202");
        Student zy = new Student("zhangyin",20,address);
        //深复制
        Student copy_zy = (Student) zy.clone();
        System.out.println(copy_zy.getName());
        System.out.println(copy_zy.getAge());
        System.out.println(copy_zy.getAdd().city);
    

深复制

public class Student implements Cloneable
    String name;
    int age;
    Address add;

    public Student(String name, int age, Address add) 
        this.name = name;
        this.age = age;
        this.add = add;  //籍贯
    

    protected Object clone() throws CloneNotSupportedException 
        Student s = (Student)super.clone();
        s.setAdd((Address) add.clone());
        return s;
    
class Address implements Cloneable
        String pro;  //出生省
        String city; //出生市
        String zip;  //出生地邮编

        public Address(String pro, String city, String zip) 
            this.pro = pro;
            this.city = city;
            this.zip = zip;
        
        protected Object clone() throws CloneNotSupportedException
            return (Address)super.clone();
        

(3)利用Serializable序列化接口方法

        利用构造方法、Cloneable接口方法实现对象深复制都稍显复杂,而利用Serializable序列化接口方法实现深复制要简单的多。Serializable接口同样是一个空接口,表示该对象支持序列化技术。

        这是一个 Java 中的 clone() 方法的实现,它创建了一个与原始对象状态相同的新对象实例。该方法创建了一个新的 ByteArrayOutputStream 和 ObjectOutputStream 对象,用于将原始对象序列化为字节流。然后,使用 ByteArrayInputStream 和 ObjectInputStream 对字节流进行反序列化,并将结果对象作为克隆返回。

        需要注意的是,该实现仅适用于被克隆的类可序列化,这意味着它实现了 java.io.Serializable 接口。如果类不可序列化,则会抛出异常。

        此外,在序列化或反序列化过程中可能发生任何异常,需要进行异常处理。在此实现中,任何异常都会被捕获并使用 printStackTrace() 方法打印到标准错误输出。但这并不是一个好习惯,因为它会使错误诊断变得困难。更好的做法是抛出一个 CloneNotSupportedException 异常,并提供有意义的错误消息。

public class Student implements Cloneable, Serializable 
    String name;
    int age;
    Address add;

    public Student(String name, int age, Address add) 
        this.name = name;
        this.age = age;
        this.add = add;  //籍贯
    
    protected Object clone() throws CloneNotSupportedException 
        //在这一行,我们创建了一个 obj 对象,用于存储克隆后的对象。
        Object obj = null;
        try 
            /*
            * 这三行代码使用 Java 的序列化机制将当前对象 this 序列化为字节数组并存储在 bos 中。
            * 首先,我们创建了一个 ByteArrayOutputStream 对象 bos,用于存储序列化后的字节数组。
            * 接着,我们创建了一个 ObjectOutputStream 对象 oos,它的作用是将对象序列化为字节流。
            * 最后,我们将当前对象 this 写入 oos 对象中,从而将其序列化为字节数组并存储在 bos 中。
            * */
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            //从流里读回来
            /*
            * 这三行代码将 bos 中的字节数组反序列化为对象。
            * 我们首先使用 ByteArrayInputStream 对象 bis 将字节数组包装为输入流。
            * 接着,我们使用 ObjectInputStream 对象 ois,它的作用是将字节流反序列化为对象。
            * 最后,我们使用 ois 对象的 readObject() 方法从输入流中读取对象并将其存储在 obj 中。
            * */
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            obj = ois.readObject();
        catch (Exception e) 
            e.printStackTrace();
        
        return obj;
    
class Address implements Serializable 
        String pro;  //出生省
        String city; //出生市
        String zip;  //出生地邮编

        public Address(String pro, String city, String zip) 
            this.pro = pro;
            this.city = city;
            this.zip = zip;
        

Serializable接口是Java中的一个标记接口,它不包含任何方法或字段,仅仅是用于标识一个类可以被序列化。序列化是指将对象转换为可存储或传输的格式的过程,反序列化则是将这种格式的数据还原成原始的对象。

要利用Serializable接口进行序列化和反序列化,需要完成以下步骤:

  1. 让类实现Serializable接口 需要让需要序列化的类实现Serializable接口,这样就可以将其对象序列化为字节流并传输或存储。

  2. 创建ObjectOutputStream对象 在进行序列化时,需要将对象序列化为字节流并传输或存储,这需要使用ObjectOutputStream对象来实现。创建ObjectOutputStream对象时需要传入一个OutputStream对象,OutputStream对象可以是文件输出流或网络输出流。

  3. 调用ObjectOutputStream的writeObject方法 使用ObjectOutputStream对象的writeObject方法将需要序列化的对象写入到输出流中。

  4. 创建ObjectInputStream对象 在进行反序列化时,需要将字节流还原为原始对象,这需要使用ObjectInputStream对象来实现。创建ObjectInputStream对象时需要传入一个InputStream对象,InputStream对象可以是文件输入流或网络输入流。

  5. 调用ObjectInputStream的readObject方法 使用ObjectInputStream对象的readObject方法将从输入流中读取序列化的对象,并返回原始的对象。

下面是一个使用Serializable接口进行序列化和反序列化的示例代码:

 

import java.io.*;

public class SerializableExample 
    public static void main(String[] args) throws IOException, ClassNotFoundException 
        // 创建一个需要序列化的对象
        Person person = new Person("Tom", 20);

        // 创建ObjectOutputStream对象,将对象序列化到文件中
        FileOutputStream fileOutputStream = new FileOutputStream("person.dat");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(person);
        objectOutputStream.close();

        // 创建ObjectInputStream对象,将文件中的字节流反序列化为对象
        FileInputStream fileInputStream = new FileInputStream("person.dat");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        Person person2 = (Person) objectInputStream.readObject();
        objectInputStream.close();

        // 输出反序列化后的对象信息
        System.out.println(person2.getName()); // Tom
        System.out.println(person2.getAge()); // 20
    


class Person implements Serializable 
    private String name;
    private int age;

    public Person(String name, int age) 
        this.name = name;
        this.age = age;
    

    public String getName() 
        return name;
    

    public int getAge() 
        return age;
    

Java设计模式----原型模式

细胞的有丝分裂会产生两个基因相同的细胞,基因相同就表示它们一模一样。这一过程很像代码设计中的一个设计模式,即原型模式


1.原型模式

原型模式(Prototype Pattern),就是以一个现有的对象为原型,克隆出一个或多个一模一样的对象。

原型模式的UML类图:

 

  • Prototype(抽象原型类): 具体原型的父类,也可以是具体原型对象,提供克隆方法的接口;
  • ConcretePrototype(具体原型类): 实现父类中声明的克隆方法,并返回自己的一个克隆对象;
  • Client(客户端):让一个原型对象克隆自身以创建一个新的对象。

 


2.代码实现

2.1 通用形式

Java中所有类都继承自Object, Object类中提供了Object clone()方法,Java中,只要实现了Cloneable接口的对象就可以通过clone()方法克隆自身,一般地,原型模式如下代码所示:

abstract class Prototype implements Cloneable {
    public Prototype clone() throws CloneNotSupportedException {
        return (Prototype) super.clone();
    }
}

class ConcretePrototype1 extends Prototype {
    @Override
    public Prototype clone() throws CloneNotSupportedException {
        return (ConcretePrototype1)super.clone();
    }
}
class ConcretePrototype2 extends Prototype {
    @Override
    public Prototype clone() throws CloneNotSupportedException {
        return (ConcretePrototype2)super.clone();
    }
}

 

客户端调用Prototype的克隆方法:

public class ProtoTypeDemo1 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Prototype proto = new ConcretePrototype1();
        Prototype clone1 = proto.clone();
        Prototype clone2 = proto.clone();
        System.out.println(proto);
        System.out.println(clone1);
        System.out.println(clone2);
    }
}

 

输出

blog.design.prototype.ConcretePrototype1@610455d6
blog.design.prototype.ConcretePrototype1@511d50c0
blog.design.prototype.ConcretePrototype1@60e53b93

可以看到,我以proto对象为原型,克隆出了两个新的对象,它们在内存中的地址不同。

 

2.2 浅拷贝和深拷贝

尽管Java中,clone()方法使用起来十分简便,但是需要注意的是,Object.clone()方法,默认的是浅拷贝(Shallow copy),即不会克隆原型对象中的引用变量所指向的对象;相反地,深拷贝(Deep copy),就是可以克隆原型对象中的引用变量所指向的对象。

浅拷贝与深拷贝图示:

 

浅拷贝就是直接把原型中的引用复制给了克隆对象,克隆对象和原型中引用类型的成员变量指向相同的堆内存地址;

深拷贝则是将原型的引用类型成员也克隆一份放到新的内存中。

通常,深拷贝需要程序员自己实现,原型的成员变量所指向的对象,可能还包含其它的引用类型的成员变量,不可能将这一连串的对象都拷贝,一般来说,只需要拷贝到业务所需要的层数即可。

  


3.总结

总之, 使用原型模式可以:

  • 减少使用new关键字而产生的大量开销,
  • 提升创建对象的效率。

特别是对于需要大量准备工作才能调用new来创建对象的场景下,使用原型模式能更加高效。

通常,原型模式也可以和工厂模式和单例模式结合起来使用,工厂根据单例对象,来克隆出一模一样的新的对象,客户端通过工厂方法获取克隆对象。

此外,组合模式、装饰器模式也可以应用原型模式使结构够高效,因为它们都涉及到创建多个大体上一模一样的对象。

 

以上是关于Java设计模式—— 原型模式的主要内容,如果未能解决你的问题,请参考以下文章

Java设计模式-原型模式

java设计模式--原型模式

Java设计模式—— 原型模式

Java 原型模式(克隆模式)

java设计模式——原型模式

Java设计模式之原型模式