Java设计模式之工厂模式
摘要:主要记录工厂系列的设计模式、静态工厂、简单工厂、抽象工厂和模拟了spring的BeanFactory。同时对他们的优劣做了简单的对比。
一:简介
什么叫做工厂?从现实的角度上说、就是生产一些实际的东西而存在的场所、比如造船厂、造纸厂、汽车厂等等。让我一下就可以想的到:哦这些长是生产这些东西的。相比于我们的Java程序中、我们需要一个对象一般都是由自己new Object()来产生我们想要的对象、但是我们获取Object实例的时候只能调用new、使用他指定的方式来生产实例、那么我们想在造出自己想要的东西的时候多一些额外的操作呢?答案:通过代码搭建的工厂来造实例、而不是手动造。
那在Java代码中哪里有工厂呢?当然没有真正的工厂!所谓的工厂就是使用Java代码实现的创建实例的一种方式、比如 常用的单例模式就是一种静态工厂方法、他使用getInstance这个方法获取唯一的实例、那这个getInstance就有点工厂的味道了、更确切的讲是工厂中员工的味道、因为他为我们生产实例。
二:静态工厂模式
从简介中也知道、单例模式就是一种静态工厂模式。同时在JDK中搜索getInstance你会发现非常多、说明这种静态工厂模式在JDK中的使用也是非常普遍。至于是不是单例则不一定。如果我们给生成单例的类换个名称:SingletonFactory、是不是就更贴切一点了?比如这篇笔记围绕的问题、如何达到任意定制交通工具、任意定制生产过程?我们先实现一个生产一辆car的工厂、只允许有这一辆car——Car单例模式:
package com.chy.dp.factory; import java.util.ArrayList; import java.util.List; public class SingletonCar implements Moveable { // 如果只有这一辆car、则是单例模式 private static SingletonCar car; @SuppressWarnings("unused") // 如果填充这个List则是多例模式 private static List<SingletonCar> carList = new ArrayList<SingletonCar>(); private SingletonCar() { } public static SingletonCar getInstance() { if (car == null) { car = new SingletonCar(); } return car; } @Override public void run() { System.out.println("冒着烟奔跑中car..."); } }
三:简单工厂模式
1、如何达到任意定制交通工具、任意定制生产过程?
2、我们要达到任意制定交通工具、则必须要有多态的存在、那么就需要定义一个所有制作交通工具的具体工厂的抽象类工厂或者接口——VehicleFactory代码:
package com.chy.dp.factory; public abstract class VehicleFactory { public abstract Moveable create(); }
3、因为VehicleFactory制造的是任意性质的交通工具、create方法返回的就不能是一个具体的交通工具类、而是他们的抽象接口——Moveable代码:
package com.chy.dp.factory; public interface Moveable { public void run(); }
4、既然Moveable是所有交通工具都具有的性能、所以所有的交通工具都要实现这个接口——Car代码:
package com.chy.dp.factory; public class Car implements Moveable{ @Override public void run() { System.out.println("冒着烟奔跑中car..."); } }
5、前面我们就提到过、我们需要一个真实的Car、可以使用专门生产Car的工厂来为我们造出来、又因为需要达到任意性、那么我们这个专门生产Car的CarFactory也需要实现VehicleFactory——CarFactory代码:
package com.chy.dp.factory; public class CarFactory extends VehicleFactory{ @Override public Moveable create() { return new Car(); } }
6、当我们拥有了上面的先天条件之后、让VehicleFactory来为我们制造一个Car是多么简单的事情——Client代码:
package com.chy.dp.factory; public class Client { public static void main(String[] args) { VehicleFactory factory = new CarFactory(); Moveable m = factory.create(); m.run(); } }
7、到现在那么前面说好的任意定制交通工具、任意定制生产过程就已经达到了。比如我们Client中又想使用飞机了、那我们就可以使用VehicleFactory造一个飞机出来。当然、我们要指定是什么样式的飞机、指定哪家造飞机的来为我们制造、不选的话我们一条龙帮你服务、默认一个飞机制造厂。
a) Plane:
package com.chy.dp.factory; public class Plane implements Moveable { @Override public void run() { System.out.println("plan is flying..."); } }
b) PlaneFactory:
package com.chy.dp.factory; public class PlaneFactory extends VehicleFactory { @Override public Moveable create() { return new Plane(); } }
c) Client:
package com.chy.dp.factory; public class Client { public static void main(String[] args) { //VehicleFactory factory = new CarFactory(); VehicleFactory factory = new PlaneFactory(); Moveable m = factory.create(); m.run(); } }
8、到这里、我们发现可以改动很少的代码就可以实现获取我们想要的交通工具、别说什么还要写工具类、工厂类、这个问题就像没有车你非要开一样。
四:抽象工厂
1、作用:
抽象工厂是控制一系列产品的生产、如果想要替换一系列的产品就可以使用抽象工厂、比如很多的换皮肤的功能、换主题的功能、只要你选择一个主题、那么他所有的样式都会跟随主题改变、这就非常类似抽象工厂的作用。下面通过具体的实例来帮助理解。
2、问题的引出
a) 上面要么只生产一个Car、要么只生产一个Plane、那如果我要描述一个开着车、拿着AK47、边吃苹果的怎么弄?可以制定一系列产品。
b) 简单。既然使用工厂、那么就让一个工厂为我们创建一系列的产品——DefaultFactory代码:
package com.chy.dp.factory.abstractfactory; public class DefaultFactory{ public Food createFood() { return new Apple(); } public Vehicle createVehicle() { return new Car(); } public Weapon createWeapon() { return new AK47(); } }
c) 生产的具体的东西也要有一个抽象类作为其父类来统一指定——Food代码:Apple代码:
package com.chy.dp.factory.abstractfactory; public abstract class Food { public abstract void printName(); } package com.chy.dp.factory.abstractfactory; public class Apple extends Food { public void printName() { System.out.println("apple...."); } }
d) 其他几个JavaBean也是非常相似的定义。不再一一列出。
e) 当我们想换另一系列的产品的时候、比如想让工厂给我生产一套魔法扫帚、魔法棒、毒蘑菇、就创建另一个工厂来生产另一系列的产品——MagicFactory代码:
package com.chy.dp.factory.abstractfactory; public class MagicFactory { public Food createFood() { return new MushRoom(); } public Vehicle createVehicle() { return new Broom(); } public Weapon createWeapon() { return new MagicStick(); } }
f) 那么我们要使用第二个工厂的时候就要丢弃DefaultFactory、来从新new一个MagicFactory、下面的代码也要跟着变。关键是我不想改变下面的方法怎么办?
3、问题的解决过程
很随意的想到抽象出其父类或者接口、这也是我们常说的面下个接口编程。
a) 为我们所有的工厂类抽象出来一个抽象工厂、并且这个工厂的生产方式不能写死、应由子类去具体实现、所以方法的返回值也是生产产品的抽象类或者接口——AbstractFactory代码:
package com.chy.dp.factory.abstractfactory; /** * 根据具体的工厂产生具体的产品 * @author Administrator * */ public abstract class AbstractFactory { public abstract Vehicle createVehicle(); public abstract Weapon createWeapon(); public abstract Food createFood(); }
b) 那么我们所有的工厂都要从AbstractFactory来继承、并且实现自己特有的生产方式——DefaultFactory代码:MagicFactory代码:
package com.chy.dp.factory.abstractfactory; public class DefaultFactory extends AbstractFactory{ @Override public Food createFood() { return new Apple(); } @Override public Vehicle createVehicle() { return new Car(); } @Override public Weapon createWeapon() { return new AK47(); } } package com.chy.dp.factory.abstractfactory; public class MagicFactory extends AbstractFactory{ @Override public Food createFood() { return new MushRoom(); } @Override public Vehicle createVehicle() { return new Broom(); } @Override public Weapon createWeapon() { return new MagicStick(); } }
c) 上面的工厂生产的东西都是返回的他们的共同的父类的类型、这样的目的是可以动态的指向生产的实例、所以具体的产品类也需要一个抽象的父类或者接口来统一指向他们的具体的实例的索引。这样在调用工厂的时候就可以用他们共同的父类来接收。、
d) Client:
package com.chy.dp.factory.abstractfactory; public class Client { public static void main(String[] args) { //DefaultFactory f = new DefaultFactory(); AbstractFactory f = new DefaultFactory(); Vehicle c = f.createVehicle(); c.run(); Weapon ak = f.createWeapon(); ak.shoot(); Food a = f.createFood(); a.printName(); } }
五:简单工厂与抽象工厂的比较
简单工厂可以在产品的纬度上进行扩展、即可以添加一个或者多个新的产品、但是要添加一系列产品则会非常困难、并且每添加一个产品就要添加一个工厂、那一个系列会有多少个产品呢?这样工厂会越来越多、这样就产生了一个现象——工厂泛滥!
抽象工厂可以一次添加一个系列、但是能产生一个新的产品品种吗?会非常麻烦!因为你要动最上层的抽象工厂、并且还要为所有实现了他的具体个工厂提供实现!如果你工厂多的话、比方说有几万个!会不会不敢想想?没有完美的解决方案、一方面多了、另一方面就必然要做出让步。
六:模拟spring的bean工厂
这里模拟的当然不可能是spring的BeanFacotry的所有功能、而是仅仅模拟他的一个子类:ClassPathXmlApplicationContext这个类、用来解析spring的配置文件:applicationContext.xml中使用bean标签定义的一些类、并创建好实例、供我们在使用的时候获取。
其中模拟的过程中使用到一个别的技术:解析xml文件。这个可以自己去网上搜一下JDOM、不在这里解释。上网看一下、很快就能上手用。
1、 建立spring的配置文件、就放在com.chy.spring.factory包下——applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="v" class="com.chy.spring.factory.Car"></bean> </beans>
2、 创建BeanFactory:
package com.chy.spring.factory; public interface BeanFactory { public Object getBean(String id); }
3、 创建BeanFactory的子类——ClassPathXmlApplicationContext:
package com.chy.spring.factory; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.input.SAXBuilder; public class ClassPathXmlApplicationContext implements BeanFactory { //用于存放生成的bean的实例 private Map<String, Object> map = new HashMap<String, Object>(); /** * 在构造方法中解析applicationContext.xml、并存放到Map中 * @param fileName * @throws Exception */ public ClassPathXmlApplicationContext(String fileName) throws Exception { SAXBuilder builder = new SAXBuilder(); InputStream file = this.getClass().getClassLoader().getResourceAsStream(fileName); Document document = builder.build(file);// 获得文档对象 Element root = document.getRootElement();// 获得根节点 List<Element> list = root.getChildren(); System.out.println(list.size()); for(Element e : list){ String id = e.getAttributeValue("id"); String clazz = e.getAttributeValue("class"); Object o = Class.forName(clazz).newInstance(); map.put(id, o); } } /** * 提供获取bean的一个方法。 */ @Override public Object getBean(String id) { return map.get(id); } }
4、 将上面的Car、Moveable复制一份下来。
5、 配置applicationContext.xml如第一步中写好的。
6、 在Client中使用:
package com.chy.spring.factory; import java.util.Properties; public class Client { public static void main(String[] args) throws Exception { springContextMehtod(); } /** * 模拟spring的bean工厂来实现通过工厂获取bean实例 * @throws Exception */ private static void springContextMehtod() throws Exception { BeanFactory factory = new ClassPathXmlApplicationContext("com/chy/spring/factory/applicationContext.xml"); Object o = factory.getBean("v"); Moveable m = (Moveable)o; m.run(); } /** * 模拟读取资源文件来实例化指定的bean。 * @throws Exception */ @SuppressWarnings("unused") private static void propertiesMethod() throws Exception{ // 加载资源文件、获取资源文件中指定名称的值。 Properties props = new Properties(); props.load(Client.class.getClassLoader().getResourceAsStream( "com/chy/spring/factory/spring.properties")); String vehicleType = props.getProperty("VehicleType"); // 使用Class.forName(String className)将这个类的class文件load到内存中 // 再使用newInstance实例化这个类。 Object o = Class.forName(vehicleType).newInstance(); Moveable m = (Moveable) o; m.run(); } }
七:总结与补充
1、总结:
工厂是一个系列的设计模式。静态工厂、简单工厂(或者称为普通工厂)、抽象工厂。在实际的设计中、并不会仅仅的时候一种设计模式、只有更合适的设计模式、没有最合适的设计模式。spring最重要的两个特性IOC、AOP。从上面对BeanFactory的模拟也可以看出一点IOC的特性、就是使用工厂模式来帮我们生产bean。AOP则是动态代理设计模式的一个应用。最后提一句没有一种框架是没有用到反射的!
2、补充:
抽象工厂中使用的JavaBean可以自己构建、没有全部贴出来、完全可以定义一个抽象类(代表的是一类的事务、当然你也可以使用接口)、最后一个Client中贴的第二个方法简单的补充一下:是通过读取资源文件中配置信息来实例化Bean。这也意味着在实际项目中我们可以在项目已经完成之后可以动态的通过配置文件(Java代码已经被编译成class文件了)来指定我们想要的处理方式。
3、结构图: