前言在设计模式系列文章中,我们写过工厂模式、单列模式、建造者模式。在创造模式中,今天要跟大家分享的是原型模式。原型模式在我们的代码中很常见,但却是一种很容易被我们忽略的模式。那么什么是原型模式呢?Prototype模式其实就是一种克隆对象的方法,这在我们编码中是很常见的,比如我们常用的BeanUtils.copyProperties就是一个对象的浅拷贝。其实我们的实例化对象操作并不是特别耗性能,所以对于一些特殊的场景,我们还是需要克隆那些实例化对象:依赖外部资源或者硬件密集型操作,比如数据库查询,或者一些有IO操作的场景来获取同一个对象在同一个状态下的副本,这样就不需要重复创建操作来获取状态看我们的类图:在上图中我们可以看到原型模式其实很简单:第一个是抽象原型(prototype)声明克隆方法,它可以是一个接口或一个基类。在简单的场景下,我们可以不用基类直接使用具体类。二是实现或扩展具体原型类的clone方法。当我们在具体原型类中使用object方法时,会返回一个基类的抽象原型对象。对于以上的理论知识,我们在实践中还是给出一个。让我们举个例子!让我们举个例子。假设我们有一个场景,公司举办了一个活动,有50,000种产品参加了活动。我们需要能够在后台定时同步各个产品的销售情况,以便为后续的活动做产品分析,我们如何处理这个销售同步问题?首先,销售和库存是这里的热点数据,但是必须相互隔离,因为库存对实时性要求很高。sales可以允许有短暂的延迟,只要能保证数据的最终一致性就足够了,所以在下单的时候,我们可以根据一个MQ来更新我们数据库中的产品销量。我们去查销量的时候,不可能每次都查DB,所以可以通过redis缓存来处理。同时我们在缓存中记录我们当前查询的更新时间。再次查询的时候,以redis数据中的更新时间作为查询条件,查询到DB中的更新时间大于我们当前redis中的记录时间,这样就减少了sql扫描表的行数(更新后的数据与全量数据对比,更新的数据量还是占少数)基于以上流程,我们开始写demo。在演示中,我们首先创建了一个ItemSold类和一个SkuSold类。同时,ItemSold重写了Cloneable中的clone方法。然后在最后的测试类mian方法中,我调用了clone方法复制了一个新的商品销售类。细心的同学不知道在看结果的时候有没有发现问题呢?在for循环中,我分别打印出了ItemSold和SkuSold对象的内存地址。复制的SkuSold的内存地址其实和原型地址是一样的,ItemSold的副本和原型地址是不一样的。为了解决这个问题,我们就来说说原型模式的两种实现方式,浅拷贝和深拷贝。这里说明一下,我们是在for循环中进行数据转换的。一般来说,我们不引用底层模型返回结果模型,需要做一层转换才能达到防腐的效果。为了体现深浅文案,写的比较简单,还是要根据实际情况自己动手。浅拷贝和深拷贝浅拷贝:当拷贝对象只包含简单数据类型如int、float或不可变对象(字符串)时,直接将这些字段拷贝到新对象中。不复制引用对象,而是将引用对象的地址复制到克隆对象中进行深复制:复制对象中无论是简单数据类型还是引用对象类型,都会完整复制一份到新对象中.举个例子这好比两个兄弟可以给每个人买衣服,然后住在一个房子里(浅拷贝)。如图,大家就明白上面的demo是浅拷贝了,那我们怎么实现深拷贝呢?首先我们来看一下Java提供的Cloneable接口。接口上面的解释可以大致理解为:一个类实现了Cloneable接口来实现该类的clone方法,从而实现类实例的字段合法复制。如果在该类没有实现Cloneable接口的实例上调用Object的clone()方法,会抛出CloneNotSupportedException异常。那么我们这里如何实现深拷贝呢?第一种方法:在重写ItemSold中的clone方法的时候,我们也对SkuSold进行了一次拷贝(因为我们这里是一个List对象,所以只能先得到一个浅拷贝,然后通过浅拷贝遍历拷贝的List对象并然后调用引用对象的clone方法实现深拷贝)这里如果引用对象有多个层次,可以考虑用递归来实现,但是代码看起来要复杂很多。第二种方法:通过序列化将对象写入流中,然后从流中取出。以上两种写法其实是可以实现的,但是不管用哪种方法,深拷贝比浅拷贝要花费更多的时间和空间。所以还是酌情考虑吧。其实浅拷贝和深拷贝的工具已经有很多了。深拷贝:SerializationUtils浅拷贝:BeanUtils想想上面的业务场景。使用一个MQ来增加销量,同时更新redis缓存,但是需要注意不要将一些核心业务数据和非核心业务数据共享一个消费组,防止影响核心数据的消费率。同时,我们在设计的时候,更多的是考虑这样做的优缺点,以及开发成本的问题。其实我们可以在其他地方使用原型模式。比如我们Sending活动的PUSH通知,在向平台上百万、千万甚至上亿的用户发送通知时,通知的内容基本相同,只是推送给用户不同或者一些小的变化inspecialfieldvalues,那么我们这里可以使用原型模式来做,同时开多个线程做push。这里需要注意线程安全问题,所以在每个线程内部做一个拷贝对象。总结原型模式使用方便,但是每次我们clone一个基类或者有引用对象的时候,我们都需要修改原型对象的clone方法,这不符合我们的开闭原则。一般情况下,不建议使用这种模式,除非创建对象的成本特别高,或者在一些特殊场景下使用。最后,一些不常见的模式我就不详细分享给大家了,后面会再分享。综上所述,稍后我将开始与您分享行为模式。我是敖丙,知道的越多,不知道的越多,我们下期再见!
