java设计模式之工厂模式

来源:互联网 发布:java数据库连接代码 编辑:程序博客网 时间:2024/06/06 16:42


        一直想写点东西来介绍下我理解的java设计模式,顺便和大家共同交流学习,希望对大家有所帮助。

今天就从比较常用的工厂模式谈起,说起工厂模式,一个典型的应用就是spring框架,稍后在工厂模式的扩展中我会稍作介绍。那接下来我就根据一些小例子谈一下我对java工厂模式的理解。


一、引子

    话说很多年前,有个人叫小黑,他娶了个老婆,只会做三种食物:大米、馒头和窝窝。老婆会根据他的口味做饭,每当小黑想吃大米饭的时候,小黑会把米给他的老婆,并对他老婆说:“我饿了,开始做大米饭吧。”如果小黑想吃馒头,小黑会把面给老婆,说:“我饿了,开始做馒头吧。”每当小黑想吃窝窝的时候,小黑就会把窝窝面给老婆,说:“我饿了,开始做窝窝吧。”大家可能会感觉小黑有病,他直接告诉他老婆做饭不就行了。

   而当把小黑的行为放到我们程序设计中来时,会发现这是一个普遍存在的现象。幸运的是,这种有病的现象在OO(面向对象)语言中可以避免了。下面就以Java语言为基础来引入我们本文的主题:工厂模式。



二、工厂模式的分类


   工厂模式在《Java与模式》中分为三类:


1)简单工厂模式(Simple Factory

2)工厂方法模式(Factory Method

3)抽象工厂模式(Abstract Factory

这三种模式从上到下逐步抽象,并且更具一般性。
GOF在《设计模式》一书中将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory)。将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。
两种分法皆可,下面我们就以小黑的故事来看一下java的工程模式是怎么给小黑治病的。


三、详述三种工厂模式


1.简单工厂模式(Simple Factory


简单工厂模式又称静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口,他是最简单的工厂模式,当然实现的功能也最简单。

先来看一下简单工厂模式中都有哪些组件:

1) 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑。在java中它往往由一个具体类实现。在小黑的例子中,充当这个角色的是小黑的老婆,因为她负责做饭,即生产食物。

2) 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。在小黑的例子中,我们可以把大米饭、馒头和窝窝抽象成食物(Food)。

3) 具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。
在小黑的例子中的具体产品就是大米饭(Rice)、馒头(Mantou)和窝窝(Wowo)。

接下来我们就用简单工厂模式来给小黑治治病。


抽象产品角色:Food

package com.factory;//定义抽象类:食物public abstract class Food {// 食物有一个方法:被吃public abstract void eated();}



具体产品角色:Rice

package com.factory;//Rice继承于Food类public class Rice extends Food {// Rice类有自己的具体方法实现@Overridepublic void eated() {System.out.println("大米正在被吃,小黑吃的好香......");}}



具体产品角色:Mantou

package com.factory;//Mantou继承于Food类public class Mantou extends Food {// Mantou类有自己的具体方法实现@Overridepublic void eated() {System.out.println("馒头正在被吃,小黑吃的好香......");}}



具体产品角色:Wowo

package com.factory;//Wowo继承于Food类public class Wowo extends Food {// Wowo类有自己的具体方法实现@Overridepublic void eated() {System.out.println("窝窝正在被吃,小黑吃的好香......");}}



工厂类角色:Cooker(小黑的老婆)

package com.factory;//工厂类,负责生产Food,在小黑的中指的故事中就是小黑的老婆public class Cooker {// 厨师有个方法:cook,接收一个参数:小黑要想吃的食物的种类:want// 此处注意,cook方法返回的是抽象类型Food类型// 工厂只有一个,所以cook方法应该是静态的public static Food cook(String want) {// 如果接收的参数是Rice,说明小黑想吃米饭,那么就返回一碗米饭if ("Rice".equalsIgnoreCase(want)) {return new Rice();}// 如果接收的参数是Mantou,说明小黑想吃馒头,那么就返回一个馒头else if ("Mantou".equalsIgnoreCase(want)) {return new Mantou();}// 如果接收的参数是Wowo,说明小黑想吃窝窝,那么就返回一个窝窝else if ("Wowo".equalsIgnoreCase(want)) {return new Wowo();}// 否则,小黑的老婆就发怒了:“老娘不会,要吃自己做!”else {System.out.println("我不会做,要吃自己做");return null;}}}


小黑要吃饭了

package com.factory;//小黑饿了public class Manage {public static void main(String[] args) {/* * 小黑告诉他老婆他想吃馒头, 于是就是调用了Cooker类的cook方法,并且将“Mantou”传进去 */Food mantou = Cooker.cook("Mantou");mantou.eated();}}


输出结果:

馒头正在被吃,小黑吃的好香......



各个类之间的UML关系图如下:




以上内容就是简单工厂的内容了,使用简单工厂给小黑治疗之后,就会发现小黑正常了,小黑每次想要吃米饭的时候,他只要告诉老婆开始做饭,并且把Rice这个参数传递给他老婆就可以了,他老婆自然就知道要做米饭,这样做的好处是显而易见的,我们把对象的创建交给了具体的工厂来完成,客户免除了直接创建对象的责任,而只负责“吃饭”就可以了

下面我们对小黑的故事进行一下扩展,来分析一下简单工厂模式的利弊,对于客户端(即小黑),吃饭变成了一件简单的事情,他只需要调用他老婆的cook方法,把想要吃的饭传递过去,他老婆就会做给他。突然有一天,小黑在别人家吃到了一种叫包子的新食物,于是回家,调用老婆的cook方法,将“Baozi”传递了过去,按理说他老婆应该给他返回一个包子,但问题出现了,他发现老婆不会做包子,于是他老婆必须得先学会做包子,然后把这项技能加进Cooker类中,才能在小黑想要吃包子的时候给小黑返回一个包子,对应在程序中,我们必须修改类Cooker的代码,增加做包子的逻辑,这样其实就体现了简单工厂模式在扩展性上的局限性。大家试想,我能不能在不修改Cooker类逻辑代码的基础上完成小黑想吃包子的愿望呢,这就需要请工厂方法模式(Factory Method)出马了



2.工厂方法模式(Factory Method


我们先来看下它的组成吧:
1、抽象工厂角色:这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
2、具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。
3、抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。

4、具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。

下面我们就用工厂方法模式给小黑的老婆扩展技能:

抽象产品角色依然Food

package com.factoryMethod;//定义抽象类:食物public abstract class Food {// 食物有一个方法:被吃public abstract void eated();}

具体产品角色依然包括上述中的Rice、Mantou和Wowo,

这次我们新加一个具体产品角色:Baozi


package com.factoryMethod;public class Baozi extends Food {// 包子也有自己的方法@Overridepublic void eated() {System.out.println("包子正在被吃,小黑吃的好香......");}}


以下就是工厂方法模式的核心:抽象工厂角色Cooker类(注意此Cooker类和简单工厂模式中Cooker类的区别)


package com.factoryMethod;//抽象工厂角色public interface Cooker {// 抽象工厂的方法,生产Foodpublic Food cook();}


具体工厂角色:RiceCooker(实现了Cooker接口,只负责生产米饭一种食物)

package com.factoryMethod;//具体米饭工厂,只负责生产米饭public class RiceCooker implements Cooker {@Overridepublic Food cook() {// 米饭工厂,专门生产米饭return new Rice();}}

具体工厂角色:MantouCooker(实现了Cooker接口,只负责生产馒头一种食物)


package com.factoryMethod;public class MantouCooker implements Cooker {@Overridepublic Food cook() {// 馒头工厂,专门生产馒头return new Mantou();}}



具体工厂角色:WowoCooker(实现了Cooker接口,只负责生产窝窝一种食物)


package com.factoryMethod;public class WowoCooker implements Cooker {@Overridepublic Food cook() {// 窝窝工厂,专门生产窝窝return new Wowo();}}


以下就是我们为小黑的新食物:包子准备的新的具体工厂角色:BaoziCooker(实现了Cooker接口,只负责生产包子一种食物)

package com.factoryMethod;public class BaoziCooker implements Cooker {@Overridepublic Food cook() {// 包子工厂,专门生产包子return new Baozi();}}

接下来,小黑要吃饭了,比如小黑想吃包子,他可以这样做


package com.factoryMethod;//小黑饿了public class Manage {public static void main(String[] args) {/* * 小黑告诉他老婆他想吃包子, 于是就是调用了他老婆的做包子的技能 */Cooker baoziCooker = new BaoziCooker();Food baozi = baoziCooker.cook();baozi.eated();}}

输出结果:
包子正在被吃,小黑吃的好香......


各类之间的UML关系图如下所示:




工厂方法模式的核心就在于用一个抽象工厂角色代替了简单工厂模式中的实际工厂类(在小黑的故事中,两者名字虽然都叫Cooker,但读者应该很容易看出他们的区别),在工厂方法模式中,将生产一种实际产品的过程放在一个实例的工厂类中,而每个实例工厂类实现了抽象工厂角色的接口(当然,这里使用继承也可),每当我们新增一种产品的时候,只要新增一个具体实例工厂类就可以了,对于现有代码,可不做任何改变,使得原来不具有扩展性的简单工厂模式立即灵活起来。

 我们回到小黑的故事,看一下工厂方法模式是否可以在不改变原来代码的前提下实现小黑想吃包子的愿望?答案显然是肯定的,我们把每个实例工厂(比如RiceCooker类)看做是小黑老婆的一项技能,当小黑想要吃包子的时候,小黑的老婆会看一下自己是否会做包子,如果不会,则立即学习怎么做包子,学会之后只要给自己加上一项做包子的技能即可(体现在程序中即为一个对Cooker接口的新的实现:BaoziCooker),对之前自己的做米饭、做馒头和做窝窝的技能(体现在程序中即为:RiceCooker、MantouCooker和WowoCooker类)不做任何修改。


随着时间的流逝,小黑觉得吃饭的时候只是吃东西会经常噎着,终于有一天他向他老婆提出了一个要求:我在吃东西的时候,我还想喝点东西,比如,我想在吃馒头的时候喝水(Water),或者是在吃包子的时候喝茶(Tea),这就是我们今天所说的套餐的概念。这时小黑的老婆又郁闷了,因为她每次在给小黑做馒头的同时还得想着给他烧水,或者每次在给小黑做包子的时候还得想着给小黑沏茶,终于有一天小黑的老婆受不了了,上吊自杀了。我们试想一下,小黑的老婆为啥自杀,因为他感觉到了压力的存在,这是工厂方法模式已经不足以满足小黑对老婆的要求了,那么是否存在一种模式来帮助小黑的老婆满足小黑的需求,使他彻底抛弃自杀的念头呢,当然有,这就是我们的抽象工厂模式(Abstract Factory)

下面我们就看一下我们是否可以通过抽象工厂模式来拯救小黑老婆的生命


3. 抽象工厂模式(Abstract Factory)

抽象工厂模式的组成和工厂方法的组成其实一样,也分为:

抽象工厂角色:这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。
抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。


在小黑的故事中,将存在两个抽象产品角色一个是我们前面定义过的Food,在这里我们增加一个抽象产品角色饮料(Drink)


package com.abstractFactory;public abstract class Drink {//饮料有个一方法,被喝public abstract void drinked();}

针对饮料有两个子类,即两个具体产品角色:白开水(Water)和茶(Tea)


Water类:

package com.abstractFactory;public class Water extends Drink {@Overridepublic void drinked() {// 水继承饮料的方法,被喝System.out.println("白开水正在被喝,小黑喝的好开心......");}}

Tea类:


package com.abstractFactory;public class Tea extends Drink {@Overridepublic void drinked() {// 茶继承饮料的方法,被喝System.out.println("茶正在被喝,小黑喝的好开心......");}}

接下里出场的将是抽象工厂模式中的抽象工厂角色,大家注意下这里的抽象工厂角色和工厂方法中的抽象工厂角色的区别,抽象工厂模式中的抽象工厂角色将会同时生产两种产品,Food和Drink

package com.abstractFactory;//抽象工厂角色public interface Cooker {// 抽象工厂的方法,生产Foodpublic Food cookFood();// 抽象工厂的方法,生产Drinkpublic Drink makeDrink();}


接下来,我们为小黑的具体的要求制定具体工厂角色,比如对于小黑的第一个要求:吃馒头的时候喝水,我们可以为其定义以下具体工厂,我们暂且将其定义为MantouWaterCooker


package com.abstractFactory;public class MantouWaterCooker implements Cooker {@Overridepublic Food cookFood() {return new Mantou();}@Overridepublic Drink makeDrink() {return new Water();}}

而对于小黑的第二个要求,我们可以为其增加以下具体工厂类,我们暂且将其定义为BaoziTeaCooker类


package com.abstractFactory;public class BaoziTeaCooker implements Cooker {@Overridepublic Food cookFood() {return new Baozi();}@Overridepublic Drink makeDrink() {return new Tea();}}

而小黑想要吃某个套餐的时候,比如想吃着包子,喝着茶,他可以这样做:



package com.abstractFactory;//小黑饿了public class Manage {public static void main(String[] args) {//创建一个生产包子和茶套餐的工厂,然后生产一个包子,一杯茶Cooker baoziTeaCooker = new BaoziTeaCooker();Food baozi = baoziTeaCooker.cookFood();Drink tea = baoziTeaCooker.makeDrink();baozi.eated();tea.drinked();}}

输出结果:
包子正在被吃,小黑吃的好香......茶正在被喝,小黑喝的好开心......


大家可能觉得上面的代码有点眼花缭乱,但是当大家理清抽象工厂模式的原理之后,大家应该会感到清晰一点,下面我用UML图的形式给大家划一下各个角色之间的关系




这样的话小黑想吃着馒头喝水的时候就可以直接创建一个MantouWaterCooker,生产出来的就是一个馒头加一杯水,也就是说抽象工厂模式在工厂方法模式上又进行了一此扩展,它可以生产一个产品系列(馒头和水,或者包子和茶),这样我们想要其他产品系列的时候,比如,有一天小黑想吃着窝窝喝茶,他完全可以新建一个WowoTeaCooker,对现有工厂不做任何修改。


抽象工厂模式的优点

抽象工厂模式除了具有工厂方法模式的优点外,最主要的优点就是可以在类的内部对产品族进行约束。所谓的产品族,一般或多或少的都存在一定的关联,抽象工厂模式就可以在类内部对产品族的关联关系进行定义和描述,而不必专门引入一个新的类来进行管理。

抽象工厂模式的缺点

产品族的扩展将是一件十分费力的事情,假如产品族中需要增加一个新的产品(比如小黑突然有一天想吃着馒头喝着水并且吹着风扇),则几乎所有的工厂类都需要进行修改。所以使用抽象工厂模式时,对产品等级结构的划分是非常重要的。

适用场景

当需要创建的对象是一系列相互关联或相互依赖的产品族时,便可以使用抽象工厂模式。说的更明白一点,就是一个继承体系中,如果存在着多个等级结构(即存在着多个抽象类),并且分属各个等级结构中的实现类之间存在着一定的关联或者约束,就可以使用抽象工厂模式。假如各个等级结构中的实现类之间不存在关联或约束,则使用多个独立的工厂来对产品进行创建,则更合适一点。

总结

无论是简单工厂模式,工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也是极为相似的,他们的最终目的都是为了解耦。在使用时,我们不必去在意这个模式到底工厂方法模式还是抽象工厂模式,因为他们之间的演变常常是令人琢磨不透的。经常你会发现,明明使用的工厂方法模式,当新需求来临,稍加修改,加入了一个新方法后,由于类中的产品构成了不同等级结构中的产品族,它就变成抽象工厂模式了;而对于抽象工厂模式,当减少一个方法使的提供的产品不再构成产品族之后,它就演变成了工厂方法模式。

所以,在使用工厂模式时,只需要关心降低耦合度的目的是否达到了。



扩展

好了,通过以上介绍,相信大家对java工厂模式已经有一个初步了解了,在本文开头的时候我曾经说过,在实际场景中使用java工厂模式的一个典型的例子就是Spring框架,大家都知道Spring框架一个重要思想是IOC(控制反转),也就是说Spring会帮我们控制对象的创建,帮我们管理对象的存在。其实反过头我们想一下,我们介绍的三种工厂模式的核心思想其实就是把对象的创建过程从客户程序中剥离出来,达到对象创建由专有类(工厂)来负责,客户类只负责消费的目的,这不正符合Spring的思想吗?其实Spring框架中的IOC就是依赖于java工厂模式来实现的,只不过Spring把要创建哪个对象的配置信息写在了配置文件中,下面我们就用一个简单的例子模拟一下Spring中bean工厂的实现原理(其中涉及到了Java的反射机制,如有疑问请查阅相关知识)


首先,我们在com.spring包下创建一个名为spring.properties的配置文件(模拟spring的配置文件,当然spring用的xml文件),文件内容如下:

FoodType=com.spring.Wowo

以上内容指定给主程序,我们想要创建的是Wowo这个对象,以下就是我们的模拟程序,


package com.spring;import java.util.Properties;//小黑饿了public class Test {public static void main(String[] args) {// 创建一个Properties类型对象,用来装载spring.properties文件Properties pro = new Properties();try {// 加载spring.properties(具体方法为将spring.properties的路径作为输入流装载到我们主程序Test中)pro.load(Test.class.getClassLoader().getResourceAsStream("com/spring/spring.properties"));// 然后调用Properties类型文件的方法,拿到我们想要拿到的内容String foodType = pro.getProperty("FoodType");// 这是重点:利用java的反射机制将我们拿到的路径装载到内存并创建这个类的实例,我们知道我们创建的是Wowo类,所以将其强制转换为Food类型Food food = (Food) Class.forName(foodType).newInstance();//然后调用eated方法food.eated();} catch (Exception e) {e.printStackTrace();}}}


输出结果:

窝窝正在被吃,小黑吃的好香......



看,我们通篇没有使用new关键字,只是在配置文件中读到了我们创建对象的全名(路径加类名),然后利用java反射机制创建了该对象。试想我们在使用spring框架的时候,大多数时间不也在写配置文件么,当我们把我们想要spring帮我们创建的类写到配置文件后,剩下的工作Spring就帮我们做了,他会去读我们的配置文件,然后根据我们的意图在合适的时间帮我创建我们想要的对象,这其实也是spring的核心所在。


好了,写了三个晚上终于写完了,以上就是我对java工厂模式的一点理解,如有不正确的地方,还希望各位同行批评指正。


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 淘宝直播有延迟怎么办 淘宝直播间中奖怎么办 微信扫码付款后卖家不发货怎么办 淘宝打骚扰电话怎么办 淘宝卖家打骚扰电话怎么办 被商家打了怎么办 保底消费入坑怎么办 留党查看到期怎么办 遭遇淘宝控价怎么办 淘宝店没有了怎么办 淘宝店铺运费险不出单怎么办 闲鱼定金被骗怎么办 肯德基团购过期怎么办 word不可以修改怎么办 店铺预售不发货怎么办 埋件设置不符合怎么办 闲鱼付了款卖家不发货怎么办 微信里付了款卖家不发货怎么办 运动鞋穿臭了怎么办 小车陷泥土了怎么办 孩子有心事不说怎么办 网状运动鞋乱了怎么办 运动鞋布面坏了怎么办 运动鞋面破了怎么办 脚磨烂了怎么办小妙招 网眼运动鞋破了怎么办 运动鞋后面烂了怎么办 运动鞋面坏了怎么办 磨档磨的特别疼怎么办 夏天高跟鞋里面脏了怎么办 走路鞋底有声音怎么办 鞋后跟海绵塌了怎么办 鞋后跟凹进去了怎么办 穿高跟鞋臭脚怎么办 运动鞋磨后脚跟怎么办 鞋两边磨脚踝怎么办 新鞋子磨脚踝怎么办 耐克鞋两边挤脚怎么办 凉鞋臭怎么办快速去除 白鞋子鞋边划了怎么办 真皮白鞋发黄怎么办