请选择 进入手机版 | 继续访问电脑版

[设计模式] 原型模式 prototype 创建型 设计模式(七)

计算机科学 计算机科学 1012 人阅读 | 0 人回复

意图

用原型实例指定需要创建的对象的类型,然后使用复制这个原型对象的方法创建出更多同类型的对象

显然,原型模式就是给出一个对象,然后克隆一个或者更多个对象

小时候看过的动画片《西游记》,主题曲猴哥中有一句“拔一根毫毛 ,吹出猴万个 ”这就是原型模式

孙悟空作为原型对象,“拔一根毫毛 ,吹” 这就是调用复制对象方法,“猴”万个,就是结果了,创建了“万个” 猴子

原型模式的根本-拷贝

原型模式的根本在于对象的拷贝

说白了就是:如何复制一个对象?

对象的表示

Object ref = new Object();

上面这句话可以理解为三个步骤

  1. 创建一个Object类型的引用名为 ref
  2. 创建了一个Object类型的对象
  3. 使变量ref指向这个新的对象的内存地址

image.png

一个对象内部有一个或多个属性,属性可能是基本类型也可能是一个引用类型

而对于引用类型就相当于我们上面表述的这种形式

Object ref = new Object();

引用变量指向实际的对象

深拷贝和浅拷贝

拷贝有两种形式,浅拷贝和深拷贝

浅拷贝被复制的对象所有的属性成员变量都含有与原来的对象相同的值

也就是说如果是引用类型,他仍旧会指向原来的对象,也就是所有的备份中的引用类型指向的是同一个对象

浅拷贝仅仅拷贝当前对象本身

image.png

深拷贝则恰恰相反,深拷贝将会拷贝所有的对象

也就是如果内部有成员变量为引用类型,那么也会拷贝被指向的对象,不仅仅是拷贝当前对象本身

所有被引用到的对象都复制了一遍

image.png

对于深拷贝,可以借助于序列化实现,深拷贝一个对象

结构

原型模式作为创建型模式的一种,与工厂模式建造者模式是类似的,都是为了创建对象

只不过是创建的方式不同

原型模式的创建逻辑则是借助于已经存在的对象,调用他的拷贝方法,从而创建相同类型的新的对象

根据依赖倒置原则,面向抽象而不是细节进行编程,所以使用抽象角色Prototype用于描述原型类

一种通用的结构描述形式为:

image.png

Client 客户端角色

客户端程序发起创建对象请求

Prototype 抽象原型角色

抽象角色用于描述原型类,给出具体原型类需要的协议 接口或者抽象类

ConcretePrototype具体原型角色

被复制的对象类型

代码示例

package prototype;
public interface Prototype extends Cloneable {
    Prototype clone();
}
package prototype;
public class ConcreatePrototype implements Prototype {
    @Override
    public Prototype clone() {
        try{
            return (Prototype)super.clone();
        }catch (CloneNotSupportedException e){
            return null;
        }
    }
}
package prototype;
public class Client {
    public static void main(String[] args){
        Prototype prototype = new ConcreatePrototype();
        Prototype clonedObj = (Prototype)prototype.clone();
        System.out.println(clonedObj.getClass());
        System.out.println(prototype == clonedObj);
    }
}

image.png

Java天然的原型模式

在Java中,所有的对象都继承自Java.lang.Object

Object中有clone()方法 ,可以将一个对象进行拷贝

所以说Java天生的内置了原型模式---通过对象的clone方法进行对象的拷贝

不过也有一些具体的规则需要注意

Java语言提供了Cloneable接口,作为标记接口

凡是实现了Cloneable接口的类都声称:“可以安全的在这个类上使用clone()方法”。

试图调用clone()方法时,如果此对象的类没有实现 Cloneable 接口,则会抛出 CloneNotSupportedException

clone()方法如下

image.png

clone方法是浅拷贝而不是深拷贝

Object中的clone()方法规定了拷贝的一般协议,可以参看API文档

  1. 对于任何对象 x,表达式:x.clone() != x,克隆对象与原始对象不是同一个对象
  2. x.clone().getClass() == x.getClass() ,克隆对象与原始对象是同一种类型
  3. x.clone().equals(x) 为true

这三点并不是必须的,并不是必须的,并不是必须的,但是除非特殊情况,否则建议应该都满足

Object 类本身不实现接口 Cloneable

所以,如果在类型为Object的对象上调用clone方法,会抛出异常

Java语言通过Object提供的protected方法以及Cloneable标记接口机制

定义了一套复制对象的工作流程:

  1. 实现Cloneable接口
  2. 覆盖或者使用继承而来的clone()方法

对于最简单的原型模式 的应用,只需要原型类完成这步即可

这就是Java中原型模式型使用方式

因为在Java中,所有的对象直接或者间接地继承Object

所以始终内置的隐含了Object这一抽象角色

我们的示例代码中,Prototype 就是相当于java中的Object

图中Prototype 为具体的原型类(提供了clone方法的类)

image.png

拥有管理器的原型模式

原型模式中最为关键的是调用某个对象的拷贝方法,进行原始对象的复制

所以原型模式一种常见的用法就是借助于这个"原始对象",达到工厂方法的效果

客户端中保存一个ConcretePrototype类型的对象

后续的对象创建获取就是客户端通过这个内部的对象,调用它的拷贝方法进行进一步的操作

如果产品结构比较简单,可能只需要几种类型的对象即可

上面的原型结构比较适合,客户端自己保存所有的对象

但是

如果产品等级结构比较杂乱,或者说要创建的原型对象是数目并不是固定的

又可以进一步将管理对象的职责进行提取分离,抽象出来一个管理器的角色

专门用于管理这些对象

这种带管理器的原型模式中,客户端就不在持有原型对象的引用了,也就是客户端不在拥有原型对象

取而代之的是通过管理器获取

image.png

Client 客户端角色

向管理员发起创建对象的请求

Prototype、ConcretePrototype 角色与之前的概念相同

PrototypeManager 角色

原型管理器角色,负责原型对象的创建和管理

示例代码

在原来的基础上增加原型管理器

package prototype;
import java.util.HashMap;
public class PrototypeManager {
    /*hashMap维护原型对象
    * */
    private HashMap<String,Object> map = new HashMap<>();
    /*饿汉式单例模式返回创建原型对象管理器
    逻辑上原型对象管理器只有一个
    * */
    private static PrototypeManager prototypeManager= new PrototypeManager();
    public static PrototypeManager getPm(){
        return prototypeManager;
    }
    /*初始化内置两个原型对象
    * */
    private PrototypeManager(){
        map.put("product1",new ConcreatePrototype());
        map.put("product2",new ConcreatePrototype());
    }
    /*提供了添加原型对象方法*/
    public void add(String key,Prototype prototype){
        map.put(key,prototype);
    }
    /*提供了获取对象的方法,获取的对象依赖的是clone,而不是保存进去的对象*/
    public Prototype get(String key){
        return ((Prototype)map.get(key)).clone();
    }
}

测试类Client角色中也增加相关代码

image.png

看得出来,从对象管理器中获取的对象,都是原有对象的一个clone 并不是相同的对象

带管理器的原型模式也叫做 登记形式的原型模式

登记就是相当于在管理器中备案

适用场景

为什么要使用原型模式?

简简单单的new一个对象不好么?为什么非要复制呢?

当创建新的对象的过程较为复杂时,使用原型模式可以简化对象的创建过程

比如初始化占用时间较长,这种情况下创建一个对象将不再简单,所以考虑原型模式

对于工厂模式中,工厂需要有与产品等级结构相同的结构

一个工厂生产一种产品

然而,如果产品的等级结构容易发生变化的话,工厂类的等级结构可能不得不进行变化

也就是说对于产品等级结构容易变化的场景来说,工厂模式将会不方便

如果采用原型模式,那么就不再需要关注产品的等级结构,产品的等级结构可以随意变动

因为原型模式仅仅关注具体的产品对象,对象之间的结构以及结构的变化并不会产生影响

所以在这种情况下,原型模式拥有比工厂模式更为灵活,扩展性更好

不过

代价是每个类中都必须有一个clone方法,对象的创建逻辑从每个工厂转移到了每个clone方法中

在框架中使用原型模式可以与生成的实例进行解耦

框架中面向抽象进行编程,只关注他的抽象类型,不关注他的具体类型

具体的对象可以通过配置文件等方式注入

框架借助于原型模式可以获得这种类型的对象,而完全不用关注这个类型

否则当你使用某种类型时,你必然需要创建对象,也就是始终要接触到具体的类型

这种方法就可以让你永远不知道具体的类型,彻底的分离

总结

原型模式的根本在于复制,所以依赖拷贝方法,java也内置了这种模式

在java中使用时,只需要实现Cloneable接口并且重写或者使用继承而来的clone方法即可

原型模式可以很好地隐藏创建对象的具体细节。

common_log.png 转载务必注明出处:程序员潇然,疯狂的字节X,https://crazybytex.com/thread-106-1-1.html

关注下面的标签,发现更多相似文章

文章被以下专栏收录:

    黄小斜学Java

    疯狂的字节X

  • 目前专注于分享Java领域干货,公众号同步更新。原创以及收集整理,把最好的留下。
    包括但不限于JVM、计算机科学、算法、数据库、分布式、Spring全家桶、微服务、高并发、Docker容器、ELK、大数据等相关知识,一起进步,一起成长。
热门推荐
[CXX1300] CMake '3.18.1' was not
[md][CXX1300] CMake '3.18.1' was not found in SDK, PATH, or
[若依]微服务springcloud版新建增添加一个
[md]若依框架是一个比较出名的后台管理系统,有多个不同版本。
chrome 浏览器手动同步更新书签数据备份chr
[md]现在很多人会使用浏览器自带的同步工具,这样即使换了电脑,