Factory模式与Prototype模式的异同

简介:

原型模式与工厂模式的定义,本文不想在这讲太多,本文主要想在这讲一下对原型模式的一些误解--将原型模式等价于工厂模式;
为什么会产生这种误导呢?其实也不是我们的错,关键在于设计模式这本书以及网上的其它资料很喜欢将原型和工厂方法进行比较,从而导致我们误解了原型引入的本质意义。按我的理解,原型引入的根本原因就是在于它可以利用一个原型对象(在这,我指的是实例,而非类),快速地生成一批和原型对象一样的实例。举个例子来说,你有一个类A的实例a (A a=new A()),现在你想生成一个和a一样的实例b,那么,按照原型的定义,你应该可以这样做b=a.clone()。这样,你就可以得到一个和a一模一样的实例b(即a和部b的数据成员的值完全一样)。
上面是原型的一个简单说明,那么引入原型有什么好处呢?按我的理解,就是在于:你如果要生成一大批很相像的类的实例时,省得每次去做重复的赋值工作。再举个例子,如果你有一个类A,它有十个成员变量,现在你打算生成100个A的实例,而这些实例的变量值大部分相同(比如说七个相同),只有一小部分不一样(比如说三个),那么如果没有Prototype,那么你就得每次New一个A的对像,然后赋值,这样,你要重复100次同样的七个变量的赋值工作,显然,这样很麻烦。现在你有了原型,那么问题就简单了,你只要生成一个A的实例,再通过clone来生成其它的实例,然后再一一修改其它实例不同的地方。
可能我这么讲,大家不信,那下面,再让我们来看看Java中活生生的原型应用。
学过Java的人都知道,在Java中,有一个clone()函数,这个函数的功能,就是返回一个和当前调用它的对象一样的实例。那么Java中为什么要引入这个函数呢?在<<Think in Java>>一书中,作者如是解释:
如果,你要将一个对象的引用作为参数传进去,但又不希望函数改变对象的值,那么,你该怎么办?由于在Java中对对象没有像C++那样的Const修饰符,所以,为了实现这个功能,Java中引入了clone函数,使得你将对象的引用作为参数传进函数时,这个函数可以调用该对象的Clone方法生成该对象的一份拷贝,从而达到不修改原对象的目的。
我之所以用上面这么多篇幅来讲述原型本质,目的就在于希望各位不要像我一样,把原型的功能与它的意义给混了,以致于当真正要使用原型来解决问题时,却不知可以使用它。
好了,上面说了原型的本质意义--至少我认为是这样的。那为什么很多资料喜欢将原型同工厂模式进行比较呢?不知是不是巧合,虽然原型引入的初衷是像我上面所说,但他实现起来,却又完全可以达到工厂模式的郊果(后面,我会用代码实现可以用工厂模式实现的Mac,Win产品系列生成问题)。而且,用起来甚至比工厂模式更方便、灵活。对于工厂模式与原形模式在功能上的这点巧合,我想也许是因为本来工厂模式和原型模式都是创建型模式,这样,他们的基本功能都能生成对象,因而使得原型模式在功能上可以代替工厂模式。对这两种模式在功能上的相同点,程序员2001年第11期杂志上有一篇”非鱼“写的文章,作者理解得非常巧妙,即:如果你将工厂模式的UMl图对折,你得到的就是Prototype原型的UML图。有兴趣比较这两种模式的朋友,可以去参考这篇文章。
接下来,让我们在实现机制上来看看原型模式为什么可以实现工厂模式的功能(本文只限于Java语言)。在Java中,对于原型的实现,其实根本不用我们做,在object类中早就定义了一个clone函数,而这个函数,就使得我们可以动态地生成对象的当前拷贝。即然这样,那么让我们来看看,如果要实现工厂模式的功能,我
们该如何使用原型模式为做到呢?
工厂模式实现的生产产品的功能,关键是利用了继承的特性。也就是说,你生成的产品,一定是由同一个抽象产品类派生出来的。所以,在工厂模式下,你如果要生成一类产品,就要引入一个抽像产品类,然后再由它派生出具体产品。同样,在原型模式中,你完全可以同样定义一个这样的“抽象产品--具体产品”层次,再利用具体产品本身的clone功能来产生具体产品本身。从而达到实现工厂模式功能的目的。可能说到这,大家有点糊涂了。实际上,在原型模式中,每个具体产品就扮演了工厂模式里的具体工厂的角色(为什么会这样,其实很简单,因为,每个具体产品都具有生成
自己拷贝的功能?从这种意义上讲,难道这不正是工厂的作用吗?)。另外,要在Java中利用原形模式实现工厂模式的功能,则更为简单,因为object已经为我们实现了clone函数,且对于clone方法,Java中默认是:如果A是父类且A实现了clone函数,B是A的子类,则B不用实现clone函数,它只要调用父类的clone函数,Java就会在运行时动态地为我们生成正确的B的对象。理解这点的关键在于,所有类实现的clone操作都是调用object的clone方法。这也就是说,我上面所说的父类A根本就不用自己实现clone方法,而仅仅是调用父类(object)的clone方法而已。好,到了这,读者也许又有疑问了,既然所有的cloen操作都是由object实现的,而在java中所有的自定义类默认都是由object派生而来,那这样的话,应该所有的类都自动就具有了clone自己的能力?
确实,如果object不将它的clone函数声明为protect的话,情况的确如此。但Java为了安全方面的原因,所以没有将clone方法公开,而是声明为保护类型,这样的话,子类是不可以直接调用object类的clone方法的,而必须做到如下两点:
1.必须实现Cloneable接口;
2.必须声明一个clone方法,来调用object的clone函数;
Java在调用父类的clone函数时,都会在运行时动态地进行检查,如果发现调用的类不符合上面的任何一点,则会抛出一个异常。
明白了上面的原因,那么如果我们希望某个类具备clone自身的能力,那么,我们可以这样做:
1.直接按上面所说,自己实现clone操作;
2.声明一个抽象父类,实现上面的clone操作并将它声明为公开方法,再由此类派生出子类,这样,所有的 子类只要调用父类的clone方法,就能够正确地拷贝自己。
通常,我们都是使用第一种方式,但在我们现在讨论的如何用原型模式实现工厂模式的功能的问题中,我们最好是采用第二种方式。
最后,让我们通过具体的代码来看看如何用Prototype模式实现工厂模式的功能。

问题:
现有两类产品 1-Ram,2--Cpu,现在要生成具体的产品
MacRam,MacCpu和WinRam,WinCpu.

代码如下:
/**
*A:Abstract
*C:Concrete 
*/

/** 定义抽象产品Ram的类 APrototypeRam 
* 同时他也是抽象工厂
*/

abstract class APrototypeRam implements Cloneable {
public Object clone() {
Object o=null;
try {
o=super.clone();//调用父类,即Object的clone()
}
catch(CloneNotSupportedException e) {
System.err.println("APrototypeRam is not cloneable!");
}
return o;
}
}

/** 定义抽象产品Ram的类APrototypeProductCpu
* 同时他也是抽象工厂
*/

abstract class APrototypeCpu implements Cloneable {
public Object clone() {
Object o=null;
try {
o=super.clone();//调用父类,即Object的clone()
}
catch(CloneNotSupportedException e) {
System.err.println("APrototypeCpu is not cloneable!");
}
return o;
}
}

/** 定义具体产品MacRam的类CPrototypeMacRam
* 同时他也是具体工厂
*/

class CPrototypeMacRam extends APrototypeRam{
public String toString() {
return "MacRam";
}
}

/** 定义具体产品WinRam的类CPrototypeWinRam
* 同时他也是具体工厂
*/

class CPrototypeWinRam extends APrototypeRam {
public String toString() {
return "WinRam";
}
}

/** 定义具体产品MacCpu的类CPrototypeMacCpu
* 同时他也是具体工厂
*/

class CPrototypeMacCpu extends APrototypeCpu{
public String toString() {
return "MacCpu";
}
}

/** 定义具体产品WinCpu的类CPrototypeWinCpu
* 同时他也是具体工厂
*/

class CPrototypeWinCpu extends APrototypeCpu{
public String toString() {
return "WinCpu";
}
}

/** 客户端,使用CPrototypeRam和CPrototypeCpu生成如下产品
* MacRam,MacCpu,WinRam,WinCpu
*/

public class Prototype {
public static void main(String[] args) {
/**
* 在生成产品之前,先生成原型产品,以便后面利用它们成批生产相同产品
* 其作用等价于产品工厂
*/
CPrototypeMacRam prototypeMacRam=new CPrototypeMacRam();
CPrototypeWinRam prototypeWinRam=new CPrototypeWinRam();
CPrototypeMacCpu prototypeMacCpu=new CPrototypeMacCpu();
CPrototypeWinCpu prototypeWinCpu=new CPrototypeWinCpu();

CPrototypeMacRam MacRam=(CPrototypeMacRam)prototypeMacRam.clone();
CPrototypeWinRam WinRam=(CPrototypeWinRam)prototypeWinRam.clone();
CPrototypeMacCpu MacCpu=(CPrototypeMacCpu)prototypeMacCpu.clone();
CPrototypeWinCpu WinCpu=(CPrototypeWinCpu)prototypeWinCpu.clone();
System.out.println("打印原形产品与它的克隆产品与比较异同!");
System.out.println("prototypeMacRam:"+prototypeMacRam+" Cloned:"+MacRam);
System.out.println("prototypeWinRam:"+prototypeWinRam+" Cloned:"+WinRam);
System.out.println("prototypeMacCpu:"+prototypeMacCpu+" Cloned:"+MacCpu);
System.out.println("prototypeWinCpu:"+prototypeWinCpu+" Cloned:"+WinCpu);

}
}

通过上面代码,我们可以清楚地看到,用Prototype模式实现工厂模式更为简单,如果再配上原型管理器的话,那么Prototype模式则会变得更为灵活,限于篇幅,本文没有讲到原型管理器,有兴趣的朋友可以参看后文列出的参考文献。但同时,我们也发现,使用原形模式时,有一个不足之处,即在客户端代码里,我们必须显示进行类型转换,这样可能导致错误。为了改正这一点,我想,我们可以使用真正的工厂模式将Prototype模式再封装一遍。对工厂模式的这项功能,恐怕,Prototype原形模式就无能为力了。
总之,工厂模式和原形模式虽然在引入目的上不同,但在实现上,原形模式可以实现工厂模式同样的功能。但读者也不要因为这样,而将两者混为一体,因为,反过来,在将原形模式作为生成本身拷贝的这项功能使用时,工厂模式根本无法取代它。

相关文章
|
8月前
|
缓存 负载均衡 Java
2025春招 SpringCloud 面试题汇总
大家好,我是V哥。SpringCloud是面试中的重点,涵盖基础概念、组件细节、高级特性及性能优化等内容。为帮助大家更好地准备2025年的Spring Cloud面试,我整理了一系列常见面试题及答案,涉及服务注册与发现(Eureka)、配置管理(Spring Cloud Config)、负载均衡(Ribbon)、断路器(Hystrix)、微服务网关(Spring Cloud Gateway)等关键知识点。此外,还包括分布式事务管理、链路追踪(Sleuth+Zipkin)、安全性(OAuth2)以及性能优化和实践经验。希望这些内容能助你一臂之力,顺利通过面试。欢迎关注威哥爱编程,全栈之路就你行。
2178 24
|
8月前
|
机器学习/深度学习 编解码 计算机视觉
RT-DETR改进策略【注意力机制篇】| CVPRW-2024 分层互补注意力混合层 H-RAMi 针对低质量图像的特征提取模块
RT-DETR改进策略【注意力机制篇】| CVPRW-2024 分层互补注意力混合层 H-RAMi 针对低质量图像的特征提取模块
129 0
|
11月前
|
监控 负载均衡 Java
5 大 SpringCloud 核心组件详解,8 张图彻底弄懂
本文图文详解 Spring Cloud 的五大核心组件,帮助深入理解和掌握微服务架构。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
5 大 SpringCloud 核心组件详解,8 张图彻底弄懂
|
人工智能 搜索推荐 API
一键解锁:快速上手文心一言指令编程实践
【7月更文第18天】随着人工智能技术的飞速发展,对话式AI已经成为连接人与信息的新桥梁。百度的“文心一言”(ERNIE)作为国内领先的预训练语言模型,以其强大的语义理解和生成能力,正逐步改变我们获取信息和交互的方式。本文旨在为开发者提供一份快速上手指南,通过实际代码示例,深入浅出地介绍如何利用文心一言API进行指令编程,解锁AI对话新体验。
688 7
|
12月前
|
API 定位技术
api接口如何对接?(带你了解api接口的相关知识)
API接口是在产品和研发领域广泛应用的专业术语,主要用于公司内部系统衔接及公司间合作。本文将详细讲解API接口的概念、必要性及其核心要素。首先介绍API接口的基本原理与应用场景,随后阐述其重要性,最后解析API接口的核心组成部分,帮助读者深入理解API接口的工作机制。适合产品小白和求职者阅读,提升专业知识。
|
消息中间件 存储 监控
RabbitMQ、Kafka对比(超详细),Kafka、RabbitMQ、RocketMQ的区别
RabbitMQ、Kafka对比(超详细),Kafka、RabbitMQ、RocketMQ的区别,设计目标、适用场景、吞吐量、消息存储和持久化、可靠性、集群负载均衡
RabbitMQ、Kafka对比(超详细),Kafka、RabbitMQ、RocketMQ的区别
|
12月前
|
芯片 Windows
1A锂电池充电管理芯片,USB输入,PW4056HH适用于中高端和海外
PW4056HH是一款专为USB接口设计的1A锂电池充电管理芯片,具备简单外设、内置充电管理功能,支持0.2A-1A充电电流调节,配备双LED指示灯及NTC温度保护。其优势包括过压保护、高耐压性能和良好的稳定性和抗干扰能力,适用于中高端产品。
1A锂电池充电管理芯片,USB输入,PW4056HH适用于中高端和海外
|
安全 Linux 网络安全
【工具使用】几款优秀的SSH连接客户端软件工具推荐FinalShell、Xshell、MobaXterm、OpenSSH、PUTTY、Terminus、mRemoteNG、Terminals等
【工具使用】几款优秀的SSH连接客户端软件工具推荐FinalShell、Xshell、MobaXterm、OpenSSH、PUTTY、Terminus、mRemoteNG、Terminals等
118118 0
|
Kubernetes 负载均衡 Cloud Native
Docker和K8s区别,使用场景,具体怎么使用以及详细命令
@[TOC](目录) Docker 和 Kubernetes(简称 K8s) 都是容器技术领域中非常重要的工具,但它们在构建、部署和管理容器化应用程序方面发挥着不同的作用。本文将详细介绍 Docker 和 Kubernetes 的区别、使用场景以及具体的命令使用方法。 # 一、Docker 和 Kubernetes 的区别 Docker 是一种轻量级容器技术,可用于打包、交付和运行应用程序。Docker 将应用程序和所有依赖项 (库、框架等) 打包到一个称为 Docker 镜像的容器中,然后将该镜像部署到主机或云平台上。Docker 的主要优势在于它可以在不同的环境中提供一致的应用程序运行环境
4389 1