把书读薄 | 《设计模式之美》设计模式与范式(创建型-原型模式)

简介: 本文对应设计模式与范式:创建型(47),原型模式 (Prototype Pattern) ,创建型设计设计模式的最后一种。了解此模式的应用场景,深浅拷贝的区别即可,同样非常简单。

0x1、定义与使用场景


又称 克隆模式,定义如下:


如果对象的 创建成本较大,且同一个类的不同对象间差别不大(大部分字段相同)的情况下,可以利用已有对象(原型)进行复制(或者叫拷贝、克隆)的方式来创建新的对象,以达到节省创建时间的目的。


简单点说就是以某个对象为原型,克隆出一个一模一样的对象,常见应用场景:


  • 资源优化 (对象初始化需使用较多外部资源时,如IO、数据库、CPU,权限等);
  • 复杂依赖 (依赖嵌套,A创建依赖B,B创建依赖C,一连串的对象get和set);
  • 同一对象多个修改者 (多个对象的调用者都需要修改对象的值,克隆多个供使用其使用,保护性拷贝);
  • 需保存原始对象状态 (如记录历史操作的场景,通过原型模式快速保存记录);
  • 结合工厂模式使用 (定义统一的复制接口,如clone、copy,使用一个工厂来统一进行拷贝和新对象创建,然后由工厂方法提供给调用者使用)。


组成角色


网络异常,图片无法展示
|


0x2、深浅拷贝


原型模式分为 浅拷贝深拷贝 两种,浅拷贝只复制对象中基本数据类型和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象...而深拷贝得到的则是一份完完整整独立的对象,所以,深拷贝更耗时和耗内存空间。


注:浅拷贝得到的对象和原始对象会共享部分数据,可能出现数据被修改的风险,故更适用于不可变对象。


很多开源框架和组件中都有原型模式的相关实现,并不一定非得从零去实现浅拷贝和深拷贝。下面以Java为例,讲解其中深浅拷贝相关的姿势~


① Java中==与equals的区别:


  • ==基本数据类型(int,long等) → 比较存储的值是否相等,引用型变量 → 比较所指向对象地址是否相等;


  • equals:不能用于比较基本数据类型,没有对equals()方法进行重写,默认比较指向的对象地址,若想比较对象内容,需自行重写此方法,做相应的判断。


② 克隆必须满足的三个条件


  • 对任何对象x,都有 x.clone() != x,即不是同一对象;


  • 对任何对象x,都有 x.clone().getClass == x.getClass(),即对象类型一致;


  • 如果对象obj的equals()方法定义得当的话,obj.clone().equals(obj)应该是成立的(推荐,不强制);


③ Java中浅拷贝的实现方式


想被克隆的类实现 Cloneable 接口,重写 clone() 方法,验证代码示例如下(省略get、set方法):


// 引用类型
public class Money {
    private String type;    // 币种
    public Money(String type) { this.type = type; }
    @Override
    public String toString() {
        return "Money{" + "type='" + type + '\'' + '}';
    }
}
// 原型类,实现Cloneable接口,重写clone方法
public class Assets implements Cloneable{
    private int amount;     // 数目
    private Money money;    // 币种
    private String kind;    // 资金种类
    public Assets(int amount, Money money, String kind) {
        System.out.println("执行了构造方法!");
        this.amount = amount;
        this.money = money;
        this.kind = kind;
    }
    @Override
    protected Object clone() {
        Assets assets = null;
        try {
            assets = (Assets)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return assets;
    }
    @Override
    public String toString() {
        return "Assets{" + "amount=" + amount + ", money=" + money + ", kind='" + kind + '\'' + '}';
    }
}
// 测试用例:
public class AssetsTest {
    public static void main(String[] args) {
        Assets a1 = new Assets(100, new Money("人民币"), "现金");
        Assets a2 = (Assets) a1.clone();
        System.out.println("a1.equals(a2) → " + a1.equals(a2));
        System.out.println("a1 == a2 → " + (a1 == a2));
        System.out.println("a1.getClass == a2.getClass → " + (a1.getClass() == a2.getClass()));
        System.out.println("a1.getMoney() == a2.getMoney() → " + (a1.getMoney() == a2.getMoney()));
        System.out.println("a1 → " + a1.toString());
        System.out.println("a2 → " + a2.toString());
        a1.setAmount(200);
        System.out.println("修改基本类型属性后:");
        System.out.println("a1 → " + a1.toString());
        System.out.println("a2 → " + a2.toString());
        a1.getMoney().setType("美金");
        System.out.println("修改引用用类型属性后:");
        System.out.println("a1 → " + a1.toString());
        System.out.println("a2 → " + a2.toString());
    }
}


运行结果如下:


网络异常,图片无法展示
|


分析运行结果不难得出Java中浅拷贝具有如下特点:


  • 执行clone方法,不会调用构造方法;


  • 克隆会生成新的对象变量,但指向同一个内存地址;


  • 克隆前后数据类型一致;


  • 克隆时,基本数据类型属性会新建,引用类型只会生成一个新的引用变量,依旧指向同一个内存地址;


④ Java中深拷贝的两种实现方式


相比浅拷贝,深拷贝会连引用类型数据也新建,Java中实现深拷贝的两种方式:


  • 引用类型也实现Cloneable接口,重写clone()方法,原型类clone()方法调用引用类类型的clone()为money对象赋值:


@Override
protected Object clone() {
    Assets assets = null;
    try {
        assets = (Assets)super.clone();
        assets.setMoney((Money)this.getMoney().clone());
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return assets;
}


注:深拷贝需要整个克隆涉及的对象都正确的实现clone()方法,其只能怪一个没正确实现克隆,都会导致深拷贝失败。


  • 序列化,定义一个方法完成对象转二进制流和二进制流转对象,然后返回反序列化后的对象。


public Assets deepClone() {
    try {
        // 写入当前对象的二进制流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        // 读取二进制流产生新对象
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (Assets)ois.readObject();
    } catch (IOException | ClassNotFoundException e) {
        e.printStackTrace();
    }
    return null;
}


0x3、加餐:四类创建型设计模式总结


  • 单例模式 → 创建全局唯一的对象;


  • 工厂模式 → 创建不同但类型相关的对象(继承同一父类或接口),由给点参数决定构建那种类型的对象;


  • 建造者模式 → 创建复杂对象,通过设置不同的可选参数,定制化创建不同对象;


  • 原型模式 → 针对创建成本较大的对象,利用已有对象进行复制的方式创建,以此达到节省创建时间的目的;
相关文章
|
5月前
|
设计模式
**工厂模式与抽象工厂模式**都是创建型设计模式,用于封装对象创建,减少耦合
【6月更文挑战第23天】**工厂模式与抽象工厂模式**都是创建型设计模式,用于封装对象创建,减少耦合。工厂模式专注于单个对象,通过具体工厂创建具体产品,适用于简单对象创建;抽象工厂则关注一系列相关产品,提供创建一族对象的接口,适用于处理多个不兼容产品族。选择模式基于问题域的复杂性,单个产品需求时用工厂模式,多产品族时用抽象工厂模式。
32 5
|
2月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑】设计模式——原型模式
对比原型模式和传统方式的实现思路、代码方案、优缺点,阐述原型模式的使用场景,以及深拷贝、浅拷贝等相关概念,并扩展原型模式在Spring源码中的应用。
【Java笔记+踩坑】设计模式——原型模式
|
2月前
|
设计模式 Java
Java设计模式-原型模式(3)
Java设计模式-原型模式(3)
Java设计模式-原型模式(3)
|
6月前
|
设计模式 搜索推荐 数据库连接
第二篇 创建型设计模式 - 灵活、解耦的创建机制
第二篇 创建型设计模式 - 灵活、解耦的创建机制
|
4月前
|
设计模式
iLogtail设计模式问题之iLogtail中的原型模式是什么
iLogtail设计模式问题之iLogtail中的原型模式是什么
iLogtail设计模式问题之iLogtail中的原型模式是什么
|
4月前
|
设计模式 JavaScript
js设计模式【详解】—— 原型模式
js设计模式【详解】—— 原型模式
49 6
|
5月前
|
设计模式 Oracle Java
工厂模式是一种创建型设计模式,它提供了一种创建对象的最佳方式。
【6月更文挑战第20天】工厂模式简化对象创建,根据参数或条件生成MySQL或Oracle数据库连接。`DatabaseConnectionFactory`作为工厂,动态返回具体连接类型。装饰器模式则用于运行时动态增加对象功能,如`LoggingDecorator`为`Runnable`对象添加日志记录,保持代码整洁。在`Main`类中展示了如何使用这两种模式。
42 6
|
5月前
|
设计模式 Java
Java设计模式之原型模式详解
Java设计模式之原型模式详解
|
5月前
|
设计模式
原型模式-大话设计模式
原型模式-大话设计模式
|
5月前
|
设计模式
创建型设计模式之建造者模式
创建型设计模式之建造者模式