一、目的:为你揭开Factory的神秘面纱,其实很简单。
本案例适用人群:
1、对面向对象有深入了解。
2、接口、抽象类有所了解。
3、对反射了解。
可以帮你:
1、爱上工厂(只用关注自己的业务,生产新类、给类创建新方法,通过配置文件xml通知Spring就可以了(读取XML文件,动态调用想要的组合))。
2、学会读取配置文件。
3 、学会解析XML文件。
注:如果你时间有限,请跳过前4个步骤,直接看五。(六是对五的延伸,提供读取xml)
二、工厂模式的前世今生
举例说明:有一个Person
1、他可以有很多交通工具(Vehicle),都有一个Run的方法:Car、Tank、Train、Plane、Broom等等。
2、他也可以吃很多食物(Food),都有一个PrintFoodName的方法:Orange、Apple、MushRoom等等。
3、他还可以有很多武器(Weapon)都有一个Shoot的方法:Gun、MagicStick、等等。
要求:
Person 在用驾驶一个Vehicle的时候、同时拿着Weapon进行射击、同时嘴里还吃着Food。
而且,还不停的交换着交通工具、武器、食物。
实现代码的时候,是不是很痛苦???
初步解决方案:
1、给所有武器,都实现Vehicle接口,统一方法run();
2、给所有食物,都统一实现Food接口,统一方法:PrintFoodName();
3、给所有武器,都统一实现Weapon接口,统一方法:shoot();
4、提供:VehicleFactory 只生产武器、FoodFactory只生产食物、WeaponFactory只生产武器。所有的武器,在对应的工厂都能被 getInstance()到。
每发明一种新的武器、食物、交通工具,比如:游艇等,对应工厂就要添加新的生产游艇的方法。食物工厂要新增面包:对应工厂就需要,新增生产面包的方法。
弊端:
1、person 在不停的交换武器使用、不停的更换交通工具、不停的吃食物。此时的工厂类的代码,要不停的改来改去。
//第一次(不采用工厂):
Moveable car=new Car1();
car.run();
Weapon gun=new Gun();
gun.shot();
Food food=new Apple();
Apple.printFoodName();
//第二次(不采用工厂):
Moveable tank=new Tank();
tank.run();
Weapon stick=new MagicStick();
stick.shot();
Food orangle=new Orangle();
orangle.printFoodName();
//第三次(采用工厂,每次新增新的类,对应工厂类都要改动):
Moveable plane=VehicleFactory.getPlaneInstance();
plane.run();
Weapon stick=WeaponFactory.getMagicStickInstance();
stick.shot();
Food bread=FoodFactory.getBreadInstance();
bread.printFoodName();
三、工厂模式的雏形
1、通过案例一,发现需求总是变动,代码写起来不灵活。
2、我们能否创建一个工厂呢,穷举所有可能的组合方式(类似于酷狗或其他软件的一键换肤,提供多种皮肤)?至少至少有一个默认的DefaultFactory 生成固定的Vehicle、Weapon、Food。MagicFactory,统一生产魔法交通工具、食物、武器……
这样,我们只需要直接操作工厂就可以了,每切换一个工厂,工厂自动帮我们生成新的组合方式。
弊端:工厂类太多,也很繁琐。
代码如下:
//1、定义抽象类 工厂
public abstract class AbstractFactory {
public abstract Vehicle creatVehicle();
public abstract Weapon creatWeapon();
public abstract Food creatFood();
}
//2、提供默认工厂
public class DefaultFactory extends AbstractFactory{
@Override
public Vehicle creatVehicle() {
return new Car();
}
@Override
public Weapon creatWeapon() {
return new AK47();
}
@Override
public Food creatFood() {
return new Apple();
}
}
//3、再提供一个魔法工厂
public class MagicFactory extends AbstractFactory {
@Override
public Vehicle creatVehicle() {
return new Broom();//返回扫帚
}
@Override
public Weapon creatWeapon() {
return new MagicStick();//返回魔法棒
}
@Override
public Food creatFood() {
return new MushRoom();//返回蘑菇
}
}
//测试类:main方法,进行调用
Moveable broom=MagicFactory.getBroom();
broom.run();
Weapon stick=MagicFactory.getMagicStick();
mush.shot();
Food orangle=MagicFactory.getMushroom();
mush.printFoodName();
四、工厂模式的转机(从配置文件读取)
1、新建:springFactory.properties,每次变更组合时,只需要修改配置文件(后半部分)。不新增类(武器、交通工具、食物)的话,不需要修改java代码
内容如下:
VehicleType=com.xp.test3.springFactory.Car
FoodType=com.xp.test3.springFactory.Bread
WeaponType=com.xp.test3.springFactory.Gun
读取配置文件代码如下:
Properties properties=new Properties();
properties.load(new Test().getClass().getResourceAsStream("/com/xp/test3/springFactory/springFactory.properties"));//读取配置文件中的内容
String vName=properties.getProperty("VehicleType");
String vFood=properties.getProperty("FoodType");
String vWeapon=properties.getProperty("WeaponType");
Moveable vehicle=(Moveable)Class.forName(vName).newInstance();//用反射生成实体类
Food food=(Food)Class.forName(vName).newInstance();
Weapon weapon=(Weapon)Class.forName(vName).newInstance();
vehicle.run();
food.printName();
weapon.shoot();
}
注:读到这里,是不是放松些了。美滋滋,代码几乎不需要改动,每次修改properties配置文件,就可以得到自己想要的组合了。
五、工厂模式的归宿(Spring自带)
原理:你把自己的JavaBean 类,配置到Spring配置文件中。 在Java代码中,通过BeanFactory管理实例管理你的Bean。需要哪个,顺手直接取。
注:为了让你的代码能正常运行,本案例采用最精简模式。尽可能少的创建类(核心代码就一行:ClassPathXmlApplicationContext)。
1、准备工作,新建xml文件。
工程根目录,不是Src下,新建applicationContext.xml,内容如下(代码已做精简,需要其他Bean的自行添加):
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="car" class="com.xp.test3.springFactory.Car"></bean>
<bean id="train" class="com.xp.test3.springFactory.Train"></bean>
</beans>
2、测试案例
//1、声明交通工具接口
public interface Moveable {
void run();
}
//2、创建交通工具的实现类Car
public class Train implements Moveable {
@Override
public void run() {
System.out.println(" wu wu wu 火车来了");
}
}
//3、测试:
package com.xp.test3.springFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test3 {
public static void main(String[] args) throws Exception, InstantiationException, IllegalAccessException, ClassNotFoundException {
BeanFactory factory=new ClassPathXmlApplicationContext("applicationContext.xml");//使用Spring自带的Bean工厂,读取工程跟目录XML
//Moveable moveable=(Moveable)factory.getBean("car");;
Moveable moveable=(Moveable)factory.getBean("train");//所有的交通工具,都实现了moveable接口
moveable.run();
}
}
运行结果:
wu wu wu 火车来了
六、延伸:模拟Spring工厂模式
小节:以上方法,之所以如此短小精悍,是spring帮我们自动管理了bean,并通过ClassPathXmlApplicationContext类,帮我们生成新的bean。
原理:ClassPathXmlApplicationContext里有个map容器,在加载这个类的时候,Spring会把对应xml中的bean都从内存中load到这个map中,你就可以随用随取了。
具体实现如下:
//1、创建BeanFactory(模拟Spring的BeanFactory)
public interface BeanFactory {
Object getBean(String key);
}
//2、创建(模拟Spring的),需要自己导入读取XML的Jdom的jar包。
package com.xp.test3.springFactory;
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 {
Map<String,Object> container=new HashMap<String,Object>();//存放读取Xml文件中对象的容器
@Override
public Object getBean(String key) {
return container.get(key);
}
//默认构造方法
public ClassPathXmlApplicationContext(String xmlFileName) throws Exception{
SAXBuilder sb = new SAXBuilder();//获取文档解析器
Document doc = sb.build(xmlFileName);//写法1:默认是从项目根目录找(不是项目下的src,是项目根目录)
//Document doc = sb.build(this.getClass().getClassLoader().getResourceAsStream(xmlFileName));//写法2:读取某个java类同级目录下的文件
Element root = doc.getRootElement();
List<Element> list = root.getChildren("bean");//写法1:jdom自带的(建议)
//List<Element> list = (List<Element>) XPath.selectNodes(root, "/beans/bean");//写法2:xpath自带的(不建议,老版本过时了)
for (Element element : list) {
String id = element.getAttributeValue("id");//获取XML中单个bean节点的ID
String className = element.getAttributeValue("class");//获取XML中单个bean节点的Class字符串
container.put(id, Class.forName(className).newInstance());//通过反射转换为对象,存到BeanMap中
}
}
}
//3、用自己模拟类进行测试。
package com.xp.test3.springFactory;
import java.util.Properties;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.xp.test2.Food;
import com.xp.test2.Test;
import com.xp.test2.Weapon;
public class Test3 {
//纯手工,用自己写的ClassPathXmlApplicationContext进行测试
public static void myselfSpring()throws Exception {
//这里两个类都要引用自己创建的,因为和Spring的类同名,不要引用错了。
com.xp.test3.springFactory.BeanFactory factory=new com.xp.test3.springFactory.ClassPathXmlApplicationContext("applicationContext.xml");
//Moveable moveable=(Moveable)factory.getBean("car");
Moveable moveable=(Moveable)factory.getBean("train");
moveable.run();
}
public static void main(String[] args) throws Exception, InstantiationException, IllegalAccessException, ClassNotFoundException {
myselfSpring();
}
}
运行结果:
wu wu wu 火车来了
结语:
1、至此,你会发现,Spring的工厂模式,如此简单。配置,调用就完了。
2、XML解析需要导入Jar包,找不到Jdom的jar包,或者需要进一步了解的,请参考:XML解析,简单易学
3、爱学习的你,我们一起强大,欢迎吐槽,交流。