原型模式  prototype

意图

用原型实例指定需要创建的对象的类型,然后使用复制这个原型对象的方法创建出更多同类型的对象
 
显然,原型模式就是给出一个对象,然后克隆一个或者更多个对象
小时候看过的动画片《西游记》,主题曲猴哥中有一句“拔一根毫毛 ,吹出猴万个 ”这就是原型模式
孙悟空作为原型对象,“拔一根毫毛 ,吹” 这就是调用复制对象方法,“猴”万个,就是结果了,创建了“万个” 猴子 

原型模式的根本-拷贝

原型模式的根本在于对象的拷贝
说白了就是:如何复制一个对象?
 
对象的表示
Object ref = new Object();
上面这句话可以理解为三个步骤
  1. 创建一个Object类型的引用名为 ref
  2. 创建了一个Object类型的对象
  3. 使变量ref指向这个新的对象的内存地址
 
一个对象内部有一个或多个属性
属性可能是基本类型也可能是一个引用类型
而对于引用类型就相当于我们上面表述的这种形式
Object ref = new Object();
 
引用变量指向实际的对象
 
深拷贝和浅拷贝
拷贝有两种形式,浅拷贝和深拷贝
浅拷贝被复制的对象所有的属性成员变量都含有与原来的对象相同的值
也就是说如果是引用类型,他仍旧会指向原来的对象,也就是所有的备份中的引用类型指向的是同一个对象
浅拷贝仅仅拷贝当前对象本身
 
深拷贝则恰恰相反,深拷贝将会拷贝所有的对象
也就是如果内部有成员变量为引用类型,那么也会拷贝被指向的对象,不仅仅是拷贝当前对象本身
所有被引用到的对象都复制了一遍
 
对于深拷贝,可以借助于序列化实现,深拷贝一个对象

结构

原型模式作为创建型模式的一种
与工厂模式建造者模式是类似的,都是为了创建对象
只不过是创建的方式不同
原型模式的创建逻辑则是借助于已经存在的对象,调用他的拷贝方法,从而创建相同类型的新的对象
根据依赖倒置原则,面向抽象而不是细节进行编程,所以使用抽象角色Prototype用于描述原型类
一种通用的结构描述形式为:
 
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);
}
}

Java天然的原型模式

在Java中,所有的对象都继承自Java.lang.Object
Object中有clone()方法 ,可以将一个对象进行拷贝
所以说Java天生的内置了原型模式---通过对象的clone方法进行对象的拷贝
不过也有一些具体的规则需要注意
 
Java语言提供了Cloneable接口,作为标记接口
凡是实现了Cloneable接口的类都声称:“可以安全的在这个类上使用clone()方法”。
试图调用clone()方法时,如果此对象的类没有实现 Cloneable 接口,则会抛出 CloneNotSupportedException
 
clone()方法如下
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方法的类)

拥有管理器的原型模式

原型模式中最为关键的是调用某个对象的拷贝方法,进行原始对象的复制
所以原型模式一种常见的用法就是借助于这个"原始对象",达到工厂方法的效果
客户端中保存一个ConcretePrototype类型的对象
后续的对象创建获取就是客户端通过这个内部的对象,调用它的拷贝方法进行进一步的操作
 
如果产品结构比较简单,可能只需要几种类型的对象即可
上面的原型结构比较适合,客户端自己保存所有的对象
但是
如果产品等级结构比较杂乱,或者说要创建的原型对象是数目并不是固定的
又可以进一步将管理对象的职责进行提取分离,抽象出来一个管理器的角色
专门用于管理这些对象
 
这种带管理器的原型模式中,客户端就不在持有原型对象的引用了,也就是客户端不在拥有原型对象
取而代之的是通过管理器获取
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角色中也增加相关代码
看得出来,从对象管理器中获取的对象,都是原有对象的一个clone  并不是相同的对象
带管理器的原型模式也叫做 登记形式的原型模式
登记就是相当于在管理器中备案

适用场景

为什么要使用原型模式?
简简单单的new一个对象不好么?为什么非要复制呢?
 
当创建新的对象的过程较为复杂时,使用原型模式可以简化对象的创建过程
比如初始化占用时间较长,这种情况下创建一个对象将不再简单,所以考虑原型模式
 
对于工厂模式中,工厂需要有与产品等级结构相同的结构
一个工厂生产一种产品
然而,如果产品的等级结构容易发生变化的话,工厂类的等级结构可能不得不进行变化
也就是说对于产品等级结构容易变化的场景来说,工厂模式将会不方便
如果采用原型模式,那么就不再需要关注产品的等级结构,产品的等级结构可以随意变动
因为原型模式仅仅关注具体的产品对象,对象之间的结构以及结构的变化并不会产生影响
所以在这种情况下,原型模式拥有比工厂模式更为灵活,扩展性更好
不过
代价是每个类中都必须有一个clone方法,对象的创建逻辑从每个工厂转移到了每个clone方法中
 
在框架中使用原型模式可以与生成的实例进行解耦
框架中面向抽象进行编程,只关注他的抽象类型,不关注他的具体类型
具体的对象可以通过配置文件等方式注入
框架借助于原型模式可以获得这种类型的对象,而完全不用关注这个类型
否则当你使用某种类型时,你必然需要创建对象,也就是始终要接触到具体的类型
这种方法就可以让你永远不知道具体的类型,彻底的分离

总结

原型模式的根本在于复制,所以依赖拷贝方法,java也内置了这种模式
在java中使用时,只需要实现Cloneable接口并且重写或者使用继承而来的clone方法即可
原型模式可以很好地隐藏创建对象的具体类型
当创建一个对象变得复杂时,我们可以考虑使用原型模式 通过复制的方式简化对象的创建过程
但是这有一个前提,那就是复制对象相对比较简单
但是,但是,但是,有的时候,复制一个对象本身却也是非常复杂的,一般可以借助于序列化来进行
而且,每一个类都需要拥有clone方法,当需要对已有的类进行扩展改造时,clone方法也需要进行修改
这并不复合开闭原则
 

原型模式 prototype 创建型 设计模式(七)的更多相关文章

  1. 设计模式05: Prototype 原型模式(创建型模式)

    Prototype 原型模式(创建型模式) 依赖关系的倒置抽象不应该依赖于实现细节,细节应该依赖于抽象.对所有的设计模式都是这样的. -抽象A直接依赖于实现细节b -抽象A依赖于抽象B,实现细节b依赖 ...

  2. Java设计模式05:常用设计模式之原型模式(创建型模式)

    1. Java之原型模式(Prototype Pattern)     原型模式属于对象的创建模式.通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象. ...

  3. 跟着实例学习设计模式(7)-原型模式prototype(创建型)

    原型模式是创建型模式. 设计意图:用原型实例指定创建对象的类型,并通过拷贝这个原型来创建新的对象. 我们使用构建简历的样例的类图来说明原型模式. 类图: 原型模式主要用于对象的复制.它的核心是就是类图 ...

  4. 谈谈设计模式~原型模式(Prototype)

    返回目录 原型模式是创建型模式的一种,其特点在于通过“复制”一个已经存在的实例来返回新的实例(clone),而不是新建(new)实例.被复制的实例就是我们所称的“原型”,这个原型是可定制的. 原型模式 ...

  5. 原型模式--prototype

    C++设计模式——原型模式 什么是原型模式? 在GOF的<设计模式:可复用面向对象软件的基础>中是这样说的:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.这这个定义中,最 ...

  6. 设计模式(四)原型模式Prototype(创建型)

      设计模式(四)原型模式Prototype(创建型) 1.   概述 我们都知道,创建型模式一般是用来创建一个新的对象,然后我们使用这个对象完成一些对象的操作,我们通过原型模式可以快速的创建一个对象 ...

  7. 设计模式学习之原型模式(Prototype,创建型模式)(5)

    通过序列化的方式实现深拷贝 [Serializable] public class Person:ICloneable { public string Name { get; set; } publi ...

  8. Prototype,创建型模式

    读书笔记_探索式测试_混合探索式测试   一.测试场景 1.讲述用户故事 2.描述需求 3.演示产品功能 4.演示集成场景 5.描述设置和安装 6.描述警告和出错情况 二.使用基于场景的探索式测试 1 ...

  9. OOAD-设计模式(三)之创建型设计模式(5种)

    前言 前面介绍了OOAD的基础知识,现在我们来详细的说明一下GOF设计模式中的23种模式,希望大家能够学到东西! 一.工厂方法模式(Factory Method) 1.1.工厂方法模式概述 工厂方法模 ...

随机推荐

  1. android 第一次作业

    天气预报界面截图: 源码coding地址:https://coding.net/u/dsy1600802076/p/android/git/tree/master

  2. rest_framework之认证源码剖析

    如果我们写API有人能访问,有人不能访问,则需要些认证. 如何知道该用户是否已登入? 如果用户登入成功,则给用户一个随机字符串,去访问另一个页面. 以前写session的时候,都是把session写c ...

  3. promise的异步链式调用

    场景:  淘米  干净的米下锅  蒸米饭  吃米饭 ;这几个步骤是一个接着一个执行, 也就是只有前面的做完后, 才会去做后面的. 并且每一步都需要用一部分时间去执行. function deal(ta ...

  4. HBuilder git合作-代码同步

    1. 以下场景的操作都是同样的,包括:新建了文件.删除了文件.独占式修改文件(即不存在多人同时修改一个文件的情况) 提交 项目修改完成后,选中项目,右键Team->Commit 一般是选择Com ...

  5. .net Core 2.0应用程序发布到IIS上注意事项

    .net Core2.0应用程序发布window服务器报错容易错过的配置. 1.应用程序发布. 2.IIS上新建网站. 3.应用程序池选择无托管代码. 4.服务器上安装DotNetCore.1.0.1 ...

  6. MS-UAP发布的UWP的个人隐私策略

    我们十分重视您的隐私.本隐私声明解释了我们从您那里收集的个人数据内容以及我们将如何使用这些数据. 我们不收集任何与个人信息相关的数据,只收集与本UWP运行相关的数据,如: 产品使用数据:如每个页面的使 ...

  7. Redis Cluster(集群)

    一.概述 在前面的文章中介绍过了redis的主从和哨兵两种集群方案,redis从3.0版本开始引入了redis-cluster(集群).从主从-哨兵-集群可以看到redis的不断完善:主从复制是最简单 ...

  8. RabbitMQ进程结构分析与性能调优

    RabbitMQ是一个流行的开源消息队列系统,是AMQP(高级消息队列协议)标准的实现,由以高性能.健壮.可伸缩性出名的Erlang语言开发,并继承了这些优点.业界有较多项目使用RabbitMQ,包括 ...

  9. [Swift]LeetCode167. 两数之和 II - 输入有序数组 | Two Sum II - Input array is sorted

    Given an array of integers that is already sorted in ascending order, find two numbers such that the ...

  10. [Swift]LeetCode200.岛屿的个数 | Number of Islands

    Given a 2d grid map of '1's (land) and '0's (water), count the number of islands. An island is surro ...