设计模式系列—原型模式

开发 前端
本篇和大家一起来学习原型模式,在学习原型模式之前我们需要先认识下浅拷贝和深拷贝这两个概念。

 前言

  • 23种设计模式速记
  • 单例(singleton)模式
  • 工厂方法(factory method)模式
  • 抽象工厂(abstract factory)模式
  • 建造者/构建器(builder)模式

23种设计模式快速记忆的请看上面第一篇,本篇和大家一起来学习原型模式,在学习原型模式之前我们需要先认识下浅拷贝和深拷贝这两个概念。

浅拷贝和深拷贝
浅拷贝
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化。

深拷贝
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变。

模式定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

原型模式其实就是一个对象在创建另一个可定制的对象,而且不需要指定任何创建的细节。Java提供了Coneable接口,其中有一个唯一方法Clone(),实现这个接口就可以完成原型模式了。

实例说明
浅拷贝案例
声明 User 实体类,需要实现 Cloneable 接口,并覆写 clone() 方法。

User 属性包括基础数据类型和引用数据类型,方便演示

package com.niuh.designpattern.prototype; 
 
/** 
 * 用户信息 
 */ 
public class User implements Cloneable { 
 
    // 基础数据类型 
    private int id; 
    private String name
    private String sex; 
    private String pwd; 
 
    // 引用数据类型 
    private BaseInfo baseInfo; 
 
    public User(int id, String name, String sex, String pwd, BaseInfo baseInfo) { 
        this.id = id; 
        this.name = name
        this.sex = sex; 
        this.pwd = pwd; 
        this.baseInfo = baseInfo; 
    } 
 
    public int getId() { 
        return id; 
    } 
 
    public void setId(int id) { 
        this.id = id; 
    } 
 
    public String getName() { 
        return name
    } 
 
    public void setName(String name) { 
        this.name = name
    } 
 
    public String getSex() { 
        return sex; 
    } 
 
    public void setSex(String sex) { 
        this.sex = sex; 
    } 
 
    public String getPwd() { 
        return pwd; 
    } 
 
    public void setPwd(String pwd) { 
        this.pwd = pwd; 
    } 
 
    public BaseInfo getBaseInfo() { 
        return baseInfo; 
    } 
 
    public void setBaseInfo(BaseInfo baseInfo) { 
        this.baseInfo = baseInfo; 
    } 
 
    @Override 
    public String toString() { 
        return "hashCode: " + super.hashCode() + ", User{" + 
                "id=" + id + 
                ", name='" + name + '\'' + 
                ", sex='" + sex + '\'' + 
                ", pwd='" + pwd + '\'' + 
                ", baseInfo=" + baseInfo + 
                '}'
    } 
 
    @Override 
    protected User clone() throws CloneNotSupportedException { 
        return (User) super.clone(); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
package com.niuh.designpattern.prototype; 
 
import java.util.Date
 
/** 
 * 基础类 
 */ 
public class BaseInfo { 
 
    private String desc
 
    // ....... 
 
 
    public BaseInfo(String desc) { 
        this.desc = desc
    } 
 
    public String getDesc() { 
        return desc
    } 
 
    public void setDesc(String desc) { 
        this.desc = desc
    } 
 
    @Override 
    public String toString() { 
        return "BaseInfo{" + 
                "desc=" + desc + 
                '}'
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
package com.niuh.designpattern.prototype; 
 
 
/** 
 * 原型设计模式 
 */ 
public class PrototypePattern { 
    public static void main(String[] args) throws CloneNotSupportedException { 
 
        BaseInfo baseInfo = new BaseInfo("张三"); 
 
        User user1 = new User(1, "张三""男""123456", baseInfo); 
 
        // new User  ...... 
 
        // 克隆机制 
        User user2 = user1.clone(); 
        user2.setId(2); 
        user2.setName("李四"); 
        BaseInfo baseInfo1 = user2.getBaseInfo(); 
        baseInfo1.setDesc("李四"); 
 
 
        System.out.println(user1); 
        System.out.println(user2); 
 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

输出结果如下:

由输出的结果可见,通过 user1.clone() 拷贝对象后得到的 user2,和 user1 是两个不同的对象,HashCode 值不一样。user1 和 user2 的基础数据类型的修改互不影响,而引用类型 baseInfo 修改后是会有影响的。

深拷贝案例
通过上面的例子可以看到,浅拷贝会带来数据安全方面的隐患,例如我们只是想修改了 user2 的 baseInfo,但是 user1 的 baseInfo 也被修改了,因为它们都是指向的同一个地址。所以,此种情况下,我们需要用到深拷贝。

深拷贝,在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。

对于 User 的引用类型的成员变量 BaseInfo ,需要实现 Cloneable 并重写 clone() 方法。

package com.niuh.designpattern.prototype; 
 
import java.util.Date
 
/** 
 * 基础类 
 */ 
public class BaseInfo implements Cloneable { 
 
    private String desc
 
    // ....... 
 
 
    public BaseInfo(String desc) { 
        this.desc = desc
    } 
 
    public String getDesc() { 
        return desc
    } 
 
    public void setDesc(String desc) { 
        this.desc = desc
    } 
 
    @Override 
    public String toString() { 
        return "BaseInfo{" + 
                "desc=" + desc + 
                '}'
    } 
 
    @Override 
    protected BaseInfo clone() throws CloneNotSupportedException { 
        //BaseInfo 如果也有引用类型的成员属性,也应该和 User 类一样实现 
        return (BaseInfo) super.clone(); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

在 User 的 clone() 方法中,需要拿到拷贝自己后产生的新的对象,然后对新的对象的引用类型再调用拷贝操作,实现对引用类型成员变量的深拷贝。

package com.niuh.designpattern.prototype; 
 
/** 
 * 用户信息 
 */ 
public class User implements Cloneable { 
 
    // 基础数据类型 
    private int id; 
    private String name
    private String sex; 
    private String pwd; 
 
    // 引用数据类型 
    private BaseInfo baseInfo; 
 
    public User(int id, String name, String sex, String pwd, BaseInfo baseInfo) { 
        this.id = id; 
        this.name = name
        this.sex = sex; 
        this.pwd = pwd; 
        this.baseInfo = baseInfo; 
    } 
 
    public int getId() { 
        return id; 
    } 
 
    public void setId(int id) { 
        this.id = id; 
    } 
 
    public String getName() { 
        return name
    } 
 
    public void setName(String name) { 
        this.name = name
    } 
 
    public String getSex() { 
        return sex; 
    } 
 
    public void setSex(String sex) { 
        this.sex = sex; 
    } 
 
    public String getPwd() { 
        return pwd; 
    } 
 
    public void setPwd(String pwd) { 
        this.pwd = pwd; 
    } 
 
    public BaseInfo getBaseInfo() { 
        return baseInfo; 
    } 
 
    public void setBaseInfo(BaseInfo baseInfo) { 
        this.baseInfo = baseInfo; 
    } 
 
    @Override 
    public String toString() { 
        return "hashCode: " + super.hashCode() + ", User{" + 
                "id=" + id + 
                ", name='" + name + '\'' + 
                ", sex='" + sex + '\'' + 
                ", pwd='" + pwd + '\'' + 
                ", baseInfo=" + baseInfo + 
                '}'
    } 
 
    @Override 
    protected User clone() throws CloneNotSupportedException { 
        // 深拷贝 
        User user = (User) super.clone(); 
        user.baseInfo = baseInfo.clone(); 
        return user
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.

与上面的使用方式一样,输出结果如下:

由输出结果可见,深拷贝后,不管是基础数据类型还是引用类型的成员变量,修改其值都不会相互造成影响。

序列化机制实现深拷贝
需要在 User 类实现 Serializable,成员类型(BaseInfo)也需要实现 Serializable 接口。

package com.niuh.designpattern.prototype; 
 
import java.io.ByteArrayInputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.IOException; 
import java.io.ObjectInputStream; 
import java.io.ObjectOutputStream; 
import java.io.Serializable
 
/** 
 * 用户信息 
 */ 
public class User implements Cloneable , Serializable { 
 
    // 基础数据类型 
    private int id; 
    private String name
    private String sex; 
    private String pwd; 
 
    // 引用数据类型 
    private BaseInfo baseInfo; 
 
    public User(int id, String name, String sex, String pwd, BaseInfo baseInfo) { 
        this.id = id; 
        this.name = name
        this.sex = sex; 
        this.pwd = pwd; 
        this.baseInfo = baseInfo; 
    } 
 
    public int getId() { 
        return id; 
    } 
 
    public void setId(int id) { 
        this.id = id; 
    } 
 
    public String getName() { 
        return name
    } 
 
    public void setName(String name) { 
        this.name = name
    } 
 
    public String getSex() { 
        return sex; 
    } 
 
    public void setSex(String sex) { 
        this.sex = sex; 
    } 
 
    public String getPwd() { 
        return pwd; 
    } 
 
    public void setPwd(String pwd) { 
        this.pwd = pwd; 
    } 
 
    public BaseInfo getBaseInfo() { 
        return baseInfo; 
    } 
 
    public void setBaseInfo(BaseInfo baseInfo) { 
        this.baseInfo = baseInfo; 
    } 
 
    @Override 
    public String toString() { 
        return "hashCode: " + super.hashCode() + ", User{" + 
                "id=" + id + 
                ", name='" + name + '\'' + 
                ", sex='" + sex + '\'' + 
                ", pwd='" + pwd + '\'' + 
                ", baseInfo=" + baseInfo + 
                '}'
    } 
 
    @Override 
    protected User clone() throws CloneNotSupportedException { 
        // 深拷贝 
        // User user = (User) super.clone(); 
        // user.baseInfo = baseInfo.clone(); 
        // return user
 
 
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 
 
        try (ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream)) { 
 
            oos.writeObject(this); 
 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
 
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); 
 
        try (ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream)) { 
            try { 
                User user = (User) ois.readObject(); 
                return user
            } catch (ClassNotFoundException e) { 
                e.printStackTrace(); 
            } 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
        return null
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.

这个时候并没有使用Java深拷贝,改变成员属性Baseinfo,也能保存对象的独立性。

通过序列化机制来完成深拷贝不推荐使用,因为序列化操作是CPU密集型,解析流是比较消耗性能,速度会比较慢

优点

  • 可以不耦合具体类的情况下克隆对象
  • 避免重复的初始化代码
  • 更方便的构建复杂对象

缺点

  • 适用性不是很广。
  • 每一个类必须配备一个克隆方法。
  • 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。

应用场景
当代码不应该依赖于需要复制的对象的具体类时,请使用Prototype模式。

  • 某些结构复杂的对象的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有比较稳定一致的接口;
  • 一般在初始化的信息不发生变化的情况下,克隆是最好的方法。

源码中的应用

#Spring  
org.springframework.beans.factory.support.AbstractBeanDefinition 
#JDK 
java.util.Arrays 
java.util.ArrayList 
...... 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

ArrayList中的使用
ArrayList也有clone()方法,如下

  • 返回一个Object对象,所以在使用此方法的时候要强制转换。
  • ArrayList的本质是维护了一个Object的数组,所以克隆也是通过数组的复制实现的,属于浅复制。
@Override  
  public Object clone() { 
    try { 
        ArrayList<?> result = (ArrayList<?>) super.clone(); 
        result.array = array.clone(); 
        return result; 
    } catch (CloneNotSupportedException e) { 
       throw new AssertionError(); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

ArrayList的Clone浅复制的巧妙使用
当你需要使用remove方法移除掉集合中的对象,而非要修改集合中的对象的时候,可以选择使用。

//添加两个元素 
Student stJack=new Student("Jack", 13); 
Student stTom=new Student("Tom", 15); 
list.add(stJack); 
list.add(stTom); 
 
//克隆 
ArrayList<Student> listCopy=(ArrayList<Student>) list.clone(); 
 
//移除且不修改 
listCopy.remove(1); 
System.out.println(list); 
System.out.println(listCopy); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

移除且不修改集合中的元素,只是在List内部的数组中移除了指向元素的地址,可以放心的使用clone。

PS:以上代码提交在 Github :https://github.com/Niuh-Study/niuh-designpatterns.git

责任编辑:姜华 来源: 今日头条
相关推荐

2021-10-28 19:09:09

模式原型Java

2013-11-26 17:00:08

Android设计模式

2021-05-18 08:52:31

Prototype 原型模式设计模式

2022-09-21 08:47:05

项目多线程对象

2023-08-08 20:13:36

设计模式原型模式

2021-06-07 09:51:22

原型模式序列化

2020-06-08 08:04:49

设计模式结构型接口

2022-01-12 13:33:25

工厂模式设计

2020-11-03 13:05:18

命令模式

2020-10-23 09:40:26

设计模式

2020-11-04 08:54:54

状态模式

2015-06-08 09:05:10

Java原型模式

2021-06-09 08:53:34

设计模式策略模式工厂模式

2021-03-02 08:50:31

设计单例模式

2022-01-14 09:22:22

设计模式桥接

2020-10-19 09:28:00

抽象工厂模式

2013-11-26 15:48:53

Android设计模式SDK

2021-09-29 13:53:17

抽象工厂模式

2021-10-26 00:21:19

设计模式建造者

2020-11-09 08:20:33

解释器模式
点赞
收藏

51CTO技术栈公众号