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原形模式就无能为力了。
总之,工厂模式和原形模式虽然在引入目的上不同,但在实现上,原形模式可以实现工厂模式同样的功能。但读者也不要因为这样,而将两者混为一体,因为,反过来,在将原形模式作为生成本身拷贝的这项功能使用时,工厂模式根本无法取代它。

相关文章
|
监控 Java 数据库
使用Collections.sort排序集合
使用Collections.sort排序集合
springboot 集成 swagger 2.x 和 3.0 以及 Failed to start bean ‘documentationPluginsBootstrapper‘问题的解决
本文介绍了如何在Spring Boot项目中集成Swagger 2.x和3.0版本,并提供了解决Swagger在Spring Boot中启动失败问题“Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerEx”的方法,包括配置yml文件和Spring Boot版本的降级。
springboot 集成 swagger 2.x 和 3.0 以及 Failed to start bean ‘documentationPluginsBootstrapper‘问题的解决
|
人工智能 计算机视觉 Python
CodeBuddy 实现图片转素描手绘工具
总的来说,这次使用 CodeBuddy 实现图片转素描手绘工具的体验非常棒。它不仅帮我实现了一直想尝试的功能,还让我在开发过程中学习到了很多新知识和技巧。如果你也和我一样,对编程实现有趣的功能感兴趣,又担心自己搞不定复杂的代码,不妨试试 CodeBuddy,相信它会给你带来惊喜!
320 1
|
Java 数据安全/隐私保护
基于Java的电影院管理系统设计与实现
基于Java的电影院管理系统设计与实现
571 0
|
IDE Java 测试技术
【IDEA】常用设置,让你更快速的编程
在使用JetBrains的进行软件开发时,了解和正确配置一些常用设置是非常重要的。IDEA的强大功能和定制性使得开发过程更加高效和舒适。在本文中,我们将介绍一些常用的IDEA设置,帮助您更好地利用IDEA进行开发。这些设置包括编辑器选项、代码样式、插件管理和键盘快捷键等。IDEA是JetBrains公司开发的一款集成开发环境(Integrated Development Environment,简称IDE),全名为。它是一个功能强大、智能化的Java开发工具。
|
并行计算 安全 Go
Go语言中的并发编程:掌握goroutines和channels####
本文深入探讨了Go语言中并发编程的核心概念——goroutine和channel。不同于传统的线程模型,Go通过轻量级的goroutine和通信机制channel,实现了高效的并发处理。我们将从基础概念开始,逐步深入到实际应用案例,揭示如何在Go语言中优雅地实现并发控制和数据同步。 ####
|
编解码 弹性计算 Ubuntu
倚天自强!
倚天710推出接近2年,您是否真正用过阿里云搭载倚天710的ECS云服务器?您是否对ARM架构的服务器感到陌生?让我通过本篇文章向您展示搭载倚天710云服务器的强大性能、超高的可靠性以及其优秀的性价比!倚天云服务器采用倚天710处理器,基于新一代CIPU架构,通过芯片快速路径加速手段完成计算、存储、网络性能的数量级提升。
35163 15
倚天自强!
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
852 0
|
JavaScript 前端开发
Webpack 中的 module、chunk、bundle 究竟是什么?
Webpack 中的 module、chunk、bundle 究竟是什么?
845 0
|
数据采集 JSON Serverless
通过百炼大模型+FC函数计算构建小红书图文工作流
使用阿里云函数服务和百炼平台,快速构建小红书图文创作工作流。通过两步轻松创建: 1) 在函数计算中利用Puppeteer构建卡片生成服务; 2) 在百炼平台上创建工作流,整合大模型、脚本和函数计算节点,实现图文内容的自动化处理和生成。此方案适合高效创作小红书内容。
2674 7

热门文章

最新文章