设计模式:简单工厂模式
2016-05-17 15:12
204 查看
简单工厂模式
简单工厂模式是类的创建模式,又叫做静态工厂方法模式。简单工厂模式由一个工厂对象决定生产出哪一种产品类的实例。
定义:提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口、抽象类,也可以是具体的类。
在模块内部新建一个类,在这个类里面来创建接口,然后把创建好的接口返回给客户端,这样外部应用就只需要根据这个类来获取相应的接口对象,然后就可以操作接口定义的方法了。把这样的对象称为简单工厂。
简单工厂结构:
Api: 定义客户所需要的功能接口
Impl:具体实现Api的实现类,可能会有多个
Factory:工厂,选择合适的实现类来创建Api接口对象
Client:客户端,通过Factory去获取Api接口对象,然后面向Api接口编程
为什么要使用简单工厂模式
原因很简单:解耦。
A对象如果要调用B对象,最简单的做法就是直接new一个B出来。这么做有一个问题,假如C类和B类实现了同一个接口/继承自同一个类,系统需要把B类修改成C类,程序不得不重写A类代码。如果程序中有100个地方new了B对象,那么就要修改100处。
这就是典型的代码耦合度太高导致的"牵一发动全身"。所以,有一个办法就是写一个工厂IFactory,A与IFactory耦合,修改一下,让所有的类都实现C接口并且IFactory生产出C的实例就可以了。
简单工厂模式示例
以水果为例:
有两个子类苹果和葡萄:
有一个园丁,专门负责生产出各种水果:
想要什么水果就问园丁拿就好了:
程序这么写优点就出来了:
1、用户不自己去生产产品,只需要负责去拿自己需要的东西就好了,这样用户-->产品之间的耦合度就降低了
2、代码模块职责更明确了,有专门消费的模块、有专门生产的模块
总结一下简单工厂的优点:
简单工厂的工厂类中包含了必要的逻辑判断,这样就可以根据客户端的选择条件来动态的实例化相关的类,
对于客户端来说,其去除了与具体产品(比如 Apple)之间的依赖。
当然简单工厂模式是有缺点的:
最为明显的就是,其违背了开-闭原则,
在上面的 Demo 中,如果我要再增加一种水果---橘子(Orange),
那么做法如下,首先是定义一个橘子类 Orange,让其实现 Fruit接口 ,然后呢,您还必须修改工厂类,
因为您必须在工厂类的 switch 中添加几句代码
case "Orange":
return new Orange();
这样就非常明显了,因为您已经对Gardener类进行了修改,且这个修改是必须得,否则您就无法增加葡萄这种水果。
所以,简单工厂是明显的违背了开-闭原则的。
改进
上面的代码虽然实现了用户-->产品之间的分离,但还是有一个问题,工厂并不知道有多少种产品,所以每一次新增产品的时候,都需要新增else if分支,这样是不是不便呢?所以我们又想了一个办法,就是反射,园丁可以这么修改:
调用的地方可以写成:
当然,这么写其实也有一点点问题,假如有一天我的项目想进行一个重构,重整类路径,包路径,比方说生产Apple的地方有100处,岂不是要修改100处?当然不用,有以下三种方法推荐:
1、写一个接口FruitPath,里面定义常量:
2、写一个Fruit.properties文件,里面定义水果和类路径的对应关系:
3、写一个Fruit.xml文件,里面定义水果和类路径的对应关系:
第一种方式不说了,第二种方式.properties可以用Java自带的Properties类来解析,第三种方式.xml可以用DOM4J来解析。这样, 假设我以后要修改水果的路径,修改一个文件就可以了。
从设计模式的角度讲,这么修改也有很大的优点。现在不管我新增还是删除水果,园丁(类工厂)都不用变了,只需要告诉工厂我需要哪种水果就够了,工厂自然会给调用者返回。这种写法,也是Spring的基础。
最后说一点,希望大家明白,简单工厂模式或者说工厂模式的关注点并不在于在工厂中是如何生产出来需要的类的,而在于将创建产品与消费产品分离。前面使用过if...else if...else、反射,除了这些方法,还可以有别的方法可以创建产品,比如传入一个具体产品的标识,根据这个标识去数据库里面查询。
简单工厂模式在Java中的应用及解读
以后每一篇文章尽可能地介绍设计模式在Java中的应用,因为我认为不是每种设计模式开发者都有机会可以用到,但是能在原有代码中敏锐地看出这是一种什么设计模式,至少能说明对这种设计模式是理解了。这里讲一下JDK中的简单工厂模式。
JDK中的简单工厂模式有很多应用,比较典型的比如线程池,具体可以参见Java多线程18:线程池。我们使用线程池的时候,可以使用ThreadPoolExecutor,根据自己的喜好传入corePoolSize、maximumPoolSize、keepAliveTimem、unit、workQueue、threadFactory、handler这几个参数,new出一个指定的ThreadPoolExecutor出来。
JDK给开发者提供了Executors这个类,可以让用户产生ThreadPoolExecutor和使用ThreadPoolExecutor分离开,比如可以让Executors提供一个单线程的线程池Executors.newSingleThreadExecutor()、让Executors提供一个无界线程池Executors.newCachedThreadPool()等,这样,开发者可以不用关心线程池是如何去实现的,直接使用Executors方法提供给开发者的ThreadPoolExecutor就可以了。
工厂模式的优缺点
优点:
1、简单优化了软件体系结构,明确了各自功能模块的职责和权利
2、通过工厂类,外界不需要直接创建具体产品对象,只需要负责消费,不需要关心内部如何创建对象
缺点:
1、改进前的简单工厂模式全部创建逻辑都集中在一个工厂类中,能创建的类只能是考虑到的,如果需要添加新的类,就必须改变工厂类了
2、改进前的简单工厂模式随着具体产品的不断增多,可能会出现共产类根据不同条件创建不同实例的需求,这种对条件的判断和对具体产品类型的判断交错在一起,很难避免功能模块的蔓延,对系统的维护和扩展不利
3、改进后的简单工厂模式主要是使用反射效率会低一些
图中虚线框,就好比是一个组件的包装边界,表示接口、实现类和工厂类组合成了一个组件,在这个封装体里面,只有接口和工厂是对外的,也就是让外部知道并使用的,所以故意漏了一些在虚线框外,而具体的实现类是不对外的,被完全包含在虚线框内。对于客户端而言,只是知道了接口Api和简单工厂Factory,通过Factory就可以获得Api了,这样就达到了让Client在不知道具体实现类的情况下获取接口Api。所以看似简单的把“new
Impl()”这句话从客户端里面移动到了简单工厂里面,其实是有了质的变化的。
虽然说简单工厂的方法多是用来造接口的,但是仔细分析就会发现,真正能实现功能的是具体的实现类,这些实现类(Impl和Impl2这里两个类)是已经做好的,并不是真的靠简单工厂来创造出来的,简单工厂的方法无外乎就是:实现了选择一个合适的实现类来使用。所以简单工厂方法的内部主要实现的功能是“选择合适的实现类”来创建实例对象。既然要实现选择,那么就需要选择的条件或者是选择的参数,选择条件或者是参数的来源通常又有几种:
来源于客户端,由Client来传入参数。上面的实例中的参数内容1跟2就是来自于客户端。
来源于配置文件,从配置文件获取用于判断的值。
来源于程序运行期的某个值,比如从缓存中获取某个运行期的值。
简单工厂模式是类的创建模式,又叫做静态工厂方法模式。简单工厂模式由一个工厂对象决定生产出哪一种产品类的实例。
定义:提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口、抽象类,也可以是具体的类。
在模块内部新建一个类,在这个类里面来创建接口,然后把创建好的接口返回给客户端,这样外部应用就只需要根据这个类来获取相应的接口对象,然后就可以操作接口定义的方法了。把这样的对象称为简单工厂。
简单工厂结构:
Api: 定义客户所需要的功能接口
Impl:具体实现Api的实现类,可能会有多个
Factory:工厂,选择合适的实现类来创建Api接口对象
Client:客户端,通过Factory去获取Api接口对象,然后面向Api接口编程
为什么要使用简单工厂模式
原因很简单:解耦。
A对象如果要调用B对象,最简单的做法就是直接new一个B出来。这么做有一个问题,假如C类和B类实现了同一个接口/继承自同一个类,系统需要把B类修改成C类,程序不得不重写A类代码。如果程序中有100个地方new了B对象,那么就要修改100处。
这就是典型的代码耦合度太高导致的"牵一发动全身"。所以,有一个办法就是写一个工厂IFactory,A与IFactory耦合,修改一下,让所有的类都实现C接口并且IFactory生产出C的实例就可以了。
简单工厂模式示例
以水果为例:
public interface Fruit { void grow();// 生长 void harveset(); // 收货 void plant();// 种植 }
有两个子类苹果和葡萄:
public class Apple implements Fruit { public void grow() { System.out.println("Apple.grow()"); } public void harveset() { System.out.println("Apple.harveset()"); } public void plant() { System.out.println("Apple.plant()"); } }
public class Grape implements Fruit { public void grow() { System.out.println("Grape.grow()"); } public void harveset() { System.out.println("Grape.harveset()"); } public void plant() { System.out.println("Grape.plant()"); } }
有一个园丁,专门负责生产出各种水果:
public class Gardener { public static Fruit getFruit(String fruit) { if ("apple".equalsIgnoreCase(fruit)) { return new Apple(); } else if ("grape".equalsIgnoreCase(fruit)) { return new Grape(); } else { return null; } } }
想要什么水果就问园丁拿就好了:
public static void main(String[] args) { Fruit fruit0 = Gardener.getFruit("APPLE"); fruit0.grow(); Fruit fruit1 = Gardener.getFruit("GRAPE"); fruit1.harveset(); }
程序这么写优点就出来了:
1、用户不自己去生产产品,只需要负责去拿自己需要的东西就好了,这样用户-->产品之间的耦合度就降低了
2、代码模块职责更明确了,有专门消费的模块、有专门生产的模块
总结一下简单工厂的优点:
简单工厂的工厂类中包含了必要的逻辑判断,这样就可以根据客户端的选择条件来动态的实例化相关的类,
对于客户端来说,其去除了与具体产品(比如 Apple)之间的依赖。
当然简单工厂模式是有缺点的:
最为明显的就是,其违背了开-闭原则,
在上面的 Demo 中,如果我要再增加一种水果---橘子(Orange),
那么做法如下,首先是定义一个橘子类 Orange,让其实现 Fruit接口 ,然后呢,您还必须修改工厂类,
因为您必须在工厂类的 switch 中添加几句代码
case "Orange":
return new Orange();
这样就非常明显了,因为您已经对Gardener类进行了修改,且这个修改是必须得,否则您就无法增加葡萄这种水果。
所以,简单工厂是明显的违背了开-闭原则的。
改进
上面的代码虽然实现了用户-->产品之间的分离,但还是有一个问题,工厂并不知道有多少种产品,所以每一次新增产品的时候,都需要新增else if分支,这样是不是不便呢?所以我们又想了一个办法,就是反射,园丁可以这么修改:
public class Gardener { public static Fruit getFruit(String fruitPath) throws Exception { Class<?> c = Class.forName(fruitPath); return (Fruit)c.newInstance(); } }
调用的地方可以写成:
public static void main(String[] args) throws Exception { Fruit fruit0 = Gardener.getFruit("com.xrq.simplefactory.Apple"); fruit0.grow(); Fruit fruit1 = Gardener.getFruit("com.xrq.simplefactory.Grape"); fruit1.harveset(); }
当然,这么写其实也有一点点问题,假如有一天我的项目想进行一个重构,重整类路径,包路径,比方说生产Apple的地方有100处,岂不是要修改100处?当然不用,有以下三种方法推荐:
1、写一个接口FruitPath,里面定义常量:
public interface FruitPath { public final static String apple = "com.xrq.simplefactory.Apple"; public final static String grape = "com.xrq.simplefactory.Grape"; }
2、写一个Fruit.properties文件,里面定义水果和类路径的对应关系:
Apple=com.xrq.simplefactory.Apple Grape=com.xrq.simplefactory.Grape
3、写一个Fruit.xml文件,里面定义水果和类路径的对应关系:
<apple>com.xrq.simplefactory.App 4000 le</apple> <grape>com.xrq.simplefactory.Grape</grape>
第一种方式不说了,第二种方式.properties可以用Java自带的Properties类来解析,第三种方式.xml可以用DOM4J来解析。这样, 假设我以后要修改水果的路径,修改一个文件就可以了。
从设计模式的角度讲,这么修改也有很大的优点。现在不管我新增还是删除水果,园丁(类工厂)都不用变了,只需要告诉工厂我需要哪种水果就够了,工厂自然会给调用者返回。这种写法,也是Spring的基础。
最后说一点,希望大家明白,简单工厂模式或者说工厂模式的关注点并不在于在工厂中是如何生产出来需要的类的,而在于将创建产品与消费产品分离。前面使用过if...else if...else、反射,除了这些方法,还可以有别的方法可以创建产品,比如传入一个具体产品的标识,根据这个标识去数据库里面查询。
简单工厂模式在Java中的应用及解读
以后每一篇文章尽可能地介绍设计模式在Java中的应用,因为我认为不是每种设计模式开发者都有机会可以用到,但是能在原有代码中敏锐地看出这是一种什么设计模式,至少能说明对这种设计模式是理解了。这里讲一下JDK中的简单工厂模式。
JDK中的简单工厂模式有很多应用,比较典型的比如线程池,具体可以参见Java多线程18:线程池。我们使用线程池的时候,可以使用ThreadPoolExecutor,根据自己的喜好传入corePoolSize、maximumPoolSize、keepAliveTimem、unit、workQueue、threadFactory、handler这几个参数,new出一个指定的ThreadPoolExecutor出来。
JDK给开发者提供了Executors这个类,可以让用户产生ThreadPoolExecutor和使用ThreadPoolExecutor分离开,比如可以让Executors提供一个单线程的线程池Executors.newSingleThreadExecutor()、让Executors提供一个无界线程池Executors.newCachedThreadPool()等,这样,开发者可以不用关心线程池是如何去实现的,直接使用Executors方法提供给开发者的ThreadPoolExecutor就可以了。
工厂模式的优缺点
优点:
1、简单优化了软件体系结构,明确了各自功能模块的职责和权利
2、通过工厂类,外界不需要直接创建具体产品对象,只需要负责消费,不需要关心内部如何创建对象
缺点:
1、改进前的简单工厂模式全部创建逻辑都集中在一个工厂类中,能创建的类只能是考虑到的,如果需要添加新的类,就必须改变工厂类了
2、改进前的简单工厂模式随着具体产品的不断增多,可能会出现共产类根据不同条件创建不同实例的需求,这种对条件的判断和对具体产品类型的判断交错在一起,很难避免功能模块的蔓延,对系统的维护和扩展不利
3、改进后的简单工厂模式主要是使用反射效率会低一些
简单工厂分析
为什么将new Impl()放在工厂类里面就算是封装完全了呢。这是因为工厂类是存在于模块内部的。接口是用来封装隔离具体的实现的,目标就是不要让客户端知道封装体内部的具体实现。简单工厂的位置是位于封装体内的,也就是简单工厂是跟接口和具体的实现在一起的,算是封装体内部的一个类,所以简单工厂知道具体的实现类是没有关系的。整理一下简单工厂的结构图,如图所示:图中虚线框,就好比是一个组件的包装边界,表示接口、实现类和工厂类组合成了一个组件,在这个封装体里面,只有接口和工厂是对外的,也就是让外部知道并使用的,所以故意漏了一些在虚线框外,而具体的实现类是不对外的,被完全包含在虚线框内。对于客户端而言,只是知道了接口Api和简单工厂Factory,通过Factory就可以获得Api了,这样就达到了让Client在不知道具体实现类的情况下获取接口Api。所以看似简单的把“new
Impl()”这句话从客户端里面移动到了简单工厂里面,其实是有了质的变化的。
虽然说简单工厂的方法多是用来造接口的,但是仔细分析就会发现,真正能实现功能的是具体的实现类,这些实现类(Impl和Impl2这里两个类)是已经做好的,并不是真的靠简单工厂来创造出来的,简单工厂的方法无外乎就是:实现了选择一个合适的实现类来使用。所以简单工厂方法的内部主要实现的功能是“选择合适的实现类”来创建实例对象。既然要实现选择,那么就需要选择的条件或者是选择的参数,选择条件或者是参数的来源通常又有几种:
来源于客户端,由Client来传入参数。上面的实例中的参数内容1跟2就是来自于客户端。
来源于配置文件,从配置文件获取用于判断的值。
来源于程序运行期的某个值,比如从缓存中获取某个运行期的值。
相关文章推荐
- Java enum的用法详解
- cocos2d-x tolua++ 类型转换
- 判断网络是否,gps,wifi是否开启
- mysql锁
- css中的position用法
- base64 加密方法
- 高斯模糊的实现
- android layout_weight无效
- Microsoft.Ace.OleDb.12.0 操作excel
- 浏览器渲染流程
- Android fragment接口通信
- wxy第一篇博客
- Mac下Jenkins+SVN+Xcode构建持续导出环境
- Delphi中@,^,#,$分别表示什么?
- struts.xml中class路径错误报错的问题
- Leetcode 260 Single Number III
- iOS 脚本打包
- Unity3d截图 指定相机截图
- 在linux中执行sysctl -p时的一个报错
- Java内存泄漏例子