原型模式 (学习笔记2021-09-27)

原型模式 (学习笔记2021-09-27)

前言:

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

**意图:**用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

**主要解决:**在运行期建立和删除原型。

何时使用: 1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、为了避免创建一个与产品类层次平行的工厂类层次时。 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

**如何解决:**利用已有的一个原型对象,快速地生成和原型对象一样的实例。

关键代码: 1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。

应用实例: 1、细胞分裂。 2、JAVA 中的 Object clone() 方法。

优点: 1、性能提高。 2、逃避构造函数的约束。

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

使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。

**注意事项:**与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。

前提条件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGjFfedf-1632740659983)(https://z3.ax1x.com/2021/09/27/42Mrzq.md.png)]

使用原型模式

1.0 创建原型模式抽象

/**
 * @Author: ZhiHao
 * @Date: 2021/9/27 15:36
 * @Description: 怪兽抽象
 * @Versions 1.0
 **/
@Data
public abstract class Monster implements Cloneable, Serializable {

    private static final long serialVersionUID=1L;

    private Long id;

    private String name;

    private UserOne userOne;

    // 重写克隆
    @Override
    public Object clone() throws CloneNotSupportedException {
        try {
            Object clone = super.clone();
            if (clone instanceof Monster){
                return (Monster) clone;
            }
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

2.0 创建怪兽实现

/**
 * @Author: ZhiHao
 * @Date: 2021/9/27 15:42
 * @Description: 普通怪物
 * @Versions 1.0
 **/
public class WellKnownMonster extends Monster {
}
// ------------------------
/**
 * @Author: ZhiHao
 * @Date: 2021/9/27 15:50
 * @Description: 动态玩家生成的怪物
 * @Versions 1.0
 **/
public class DynamcPlayergeneratedMonster extends Monster {
}

3.0 怪兽注册表

/**
 * @Author: ZhiHao
 * @Date: 2021/9/27 15:54
 * @Description: 怪物注册表
 * @Versions 1.0
 **/
public class MonsterRegistry {

    public static final Map<String, Monster> monsterMap = new HashMap<>(2);

    static {
        init();
    }

    private static void init() {
        Monster monster = new WellKnownMonster();
        monster.setId(1L);
        monster.setName("普通怪兽");
        monster.setUserOne(new UserOne("WellKnownMonster-修改测试-1"));
        registryMonster("WellKnownMonster", monster);

        Monster dynamcPlayergeneratedMonster = new DynamcPlayergeneratedMonster();
        dynamcPlayergeneratedMonster.setId(1L);
        dynamcPlayergeneratedMonster.setName("动态玩家生成的怪物");
        dynamcPlayergeneratedMonster.setUserOne(new UserOne("dynamcPlayergeneratedMonster-修改测试-1"));
        registryMonster("DynamcPlayergeneratedMonster", dynamcPlayergeneratedMonster);
    }

    // 注册怪兽
    private static void registryMonster(String name, Monster monster) {
        monsterMap.put(name, monster);
    }

    // 浅拷贝, 寻找正确怪兽
    public static Monster getCloneMonster(String name) {
        Monster monster = monsterMap.get(name);
        try {
            Object clone = monster.clone();
            if (clone instanceof Monster) {
                return (Monster) clone;
            }
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

    // 深拷贝
    public static Monster deepCopy(String name) throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
        objectOutputStream.writeObject(monsterMap.get(name));
        ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()));
        Object readObject = inputStream.readObject();
        return (Monster) readObject;
    }
}

4.0 测试

public void applicationTest() throws Exception {
        // 这里实现的拷贝是, 浅拷贝, 修改对象里面的引用对象值时候, 会连缓存中的引用对象也一起修改
        Monster monster = MonsterRegistry.getCloneMonster("WellKnownMonster");
        monster.setName("123123");
        UserOne userOne = monster.getUserOne();
        userOne.setName("普通怪兽-333333333");
        System.out.println(monster.getName());
        System.out.println("原来缓存中的也被影响了"+MonsterRegistry.monsterMap.get("WellKnownMonster").getName());
        System.out.println(monster.getUserOne().getName());
        System.out.println(MonsterRegistry.monsterMap.get("WellKnownMonster").getUserOne().getName());
        // 下面使用对象序列化, 实现的深拷贝, 不会影响
        Monster wellKnownMonster = MonsterRegistry.deepCopy("WellKnownMonster");
        wellKnownMonster.getUserOne().setName("普通怪兽-66666666666");
        System.out.println(MonsterRegistry.monsterMap.get("WellKnownMonster").getName());
        System.out.println(wellKnownMonster.getUserOne().getName());
    }
// ----------------------
123123
普通怪兽
普通怪兽-333333333
原来缓存中的也被影响了普通怪兽-333333333
普通怪兽
普通怪兽-66666666666

什么是原型模式?

如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式,来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式,简称原型模式。

原型模式的两种实现方法

原型模式有两种实现方法,深拷贝和浅拷贝。浅拷贝只会复制对象中基本数据类型数据和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象……而深拷贝得到的是一份完完全全独立的对象。所以,深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间。

如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的,但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,也就变得复杂多了。除非需要从数据库中加载 10 万条数据并构建散列表索引,操作非常耗时,这种情况下比较推荐使用浅拷贝,否则,没有充分的理由,不要为了一点点的性能提升而使用浅拷贝。
1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懵懵懂懂程序员

如果节省了你的时间, 请鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值