一 为什么要用工厂模式
之前解说 Spring 的依赖注入的文章时,咱们就曾经有提到过工厂这种设计模式,咱们间接先通过一个例子来看一下到底工厂模式能用来做什么?
【万字长文】Spring框架 层层递进轻松入门 (IOC和DI)
首先,咱们简略的模仿一个对账户进行增加的操作,咱们先采纳咱们以前经常应用的形式进行模仿,而后再给出改良计划
(一) 举一个模仿 Spring IOC 的例子
(1) 以前的程序
首先,依照咱们惯例的形式先模仿,咱们先将一套根本流程走下来
A:Service 层
/** * 账户业务层接口 */public interface AccountService { void addAccount();}/** * 账户业务层实现类 */public class AccountServiceImpl implements AccountService { private AccountDao accountDao = new AccountDaoImpl(); public void addAccount() { accountDao.addAccount(); }}
B:Dao 层
/** * 账户长久层接口 */public interface AccountDao { void addAccount();}/** * 账户长久层实现类 */public class AccountDaoImpl implements AccountDao { public void addAccount() { System.out.println("增加用户胜利!"); }}
C:调用
因为,咱们创立的Maven工程并不是一个web工程,咱们也只是为了简略模仿,所以在这里,创立了一个 Client 类,作为客户端,来测试咱们的办法
public class Client { public static void main(String[] args) { AccountService as = new AccountServiceImpl(); as.addAccount(); }}
运行的后果,就是在屏幕上输入一个增加用户胜利的字样
D:剖析:new 的问题
下面的这段代码,应该是比较简单也容易想到的一种实现形式了,然而它的耦合性却是很高的,其中这两句代码,就是造成耦合性高的根由,因为业务层(service)调用长久层(dao),这个时候业务层将很大的依赖于长久层的接口(AccountDao)和实现类(AccountDaoImpl)
private AccountDao accountDao = new AccountDaoImpl();AccountService as = new AccountServiceImpl();
这种通过 new 对象的形式,使得不同类之间的依赖性大大加强,其中一个类的问题,就会间接导致呈现全局的问题,如果咱们将被调用的办法进行谬误的批改,或者说删掉某一个类,执行的后果就是:
在编译期就呈现了谬误,而咱们作为一个开发者,咱们应该致力让程序在编译期不依赖,而运行时才能够有一些必要的依赖(依赖是不可能齐全打消的)
所以,咱们应该想方法进行解耦,要解耦就要使调用者和被调用者之间没有什么间接的分割,那么工厂模式就能够帮忙咱们很好的解决这个问题
(2) 工厂模式改良
A:BeanFactory
具体怎么实现呢?在这里能够将 serivice 和 dao 均配置到配置文件中去(xml/properties),通过一个类读取配置文件中的内容,并应用反射技术创建对象,而后存起来,实现这个操作的类就是咱们的工厂
注:在这里咱们应用了 properties ,次要是为了实现不便,xml还波及到解析的一些代码,绝对麻烦一些,不过咱们上面要说的 Spring 就是应用了 xml做配置文件
- bean.properties:先写好配置文件,将 service 和 dao 以 key=value 的格局配置好
accountService=cn.ideal.service.impl.AccountServiceImplaccountDao=cn.ideal.dao.impl.AccountDaoImpl
- BeanFactory
public class BeanFactory { //定义一个Properties对象 private static Properties properties; //应用动态代码块为Properties对象赋值 static { try{ //实例化对象 properties = new Properties(); //获取properties文件的流对象 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); properties.load(in); }catch (Exception e){ throw new ExceptionInInitializerError("初始化properties失败"); } } }
简略的解释一下这部分代码(当然还没写完):首先就是要将配置文件中的内容读入,这里通过类加载器的形式操作,读入一个流文件,而后从中读取键值对,因为只须要执一次,所以放在动态代码块中,又因为 properties 对象在前面的办法中还要用,所以写在成员的地位
接着在 BeanFactory 中持续编写一个 getBean 办法其中有两句外围代码的意义就是:
- 通过办法参数中传入的字符串,找到对应的全类名门路,实际上也就是通过方才获取到的配置内容,通过key 找到 value值
- 下一句就是通过 Class 的加载办法加载这个类,实例化后返回
public static Object getBean(String beanName){ Object bean = null; try { //依据key获取value String beanPath = properties.getProperty(beanName); bean = Class.forName(beanPath).newInstance(); }catch (Exception e){ e.printStackTrace(); } return bean;}
B:测试代码:
public class Client { public static void main(String[] args) { AccountService as = (AccountService)BeanFactory.getBean("accountService"); as.addAccount(); }}
C:执行成果:
当咱们依照同样的操作,删除掉被调用的 dao 的实现类,能够看到,这时候编译期谬误曾经隐没了,而报进去的只是一个运行时异样,这样就解决了后面所思考的问题
咱们应该致力让程序在编译期不依赖,而运行时才能够有一些必要的依赖(依赖是不可能齐全打消的)
(3) 小总结:
为什么应用工厂模式代替了 new 的形式?
打个比方,在你的程序中,如果一段时间后,你发现在你 new 的这个对象中存在着bug或者不合理的中央,或者说你甚至想换一个长久层的框架,这种状况下,没方法,只能批改源码了,而后从新编译,部署,然而如果你应用工厂模式,你只须要从新将想批改的类,独自写好,编译后放到文件中去,只须要批改一下配置文件就能够了
我分享下我集体精简下的了解就是:
【new 对象依赖的是具体事物,而不 new 则是依赖形象事物】
Break it down:
- 依赖具体事物,这个很好了解,你依赖的是一个具体的,实实在在内容,它与你系相干,所以有什么问题,都是连环的,可能为了某个点,咱们须要批改 N 个中央,失望
- 依赖形象事物,你所调用的并不是一个间接就能够触手可及的货色,是一个形象的概念,所以不存在下面那种状况下的连环反馈
二 三种工厂模式
看完后面的例子,我想大家曾经曾经对工厂模式有了一个十分直观的意识了
说白了,工厂模式就是应用一种伎俩,代替了 new 这个操作
以往想要获取一个实例的时候要 new 进去,然而这种形式耦合性就会很高,咱们要尽量的缩小这种可防止的耦合累赘,所以工厂模式就来了
工厂就是在调用者和被调用者之间起一个连贯枢纽的作用,调用者和被调用者都只与工厂进行分割,从而缩小了两者之间间接的依赖
工厂模式一共有三种 ① 简略工厂模式,② 工厂办法模式 ③ 形象工厂模式
上面咱们一个一个来说
(一) 简略工厂模式
(1) 实现
上面咱们以一个车的例子来讲,首先咱们有一个形象的 Car 类
public abstract class Car { // 任何汽车都会跑 public abstract void run();}
接着就是它的子类,咱们先来两个,一个宝马类,一个飞驰类(为浏览不便写成了拼音命名,请勿模拟,不倡议)
public class BaoMa extends Car { @Override public void run() { System.out.println("【宝马】在路上跑"); }}
public class BenChi extends Car { @Override public void run() { System.out.println("【飞驰】在路上跑"); }}
那如果我想要实例化这个类,实际上最原始的写法能够这样(也就是间接 new 进去)
public class Test { public static void main(String[] args) { Car baoMa = new BaoMa(); baoMa.run(); Car benChi = new BenChi(); benChi.run(); }}
如果应用简略工厂模式,就须要创立一个专门的工厂类,用来实例化对象
public class CarFactory { public static Car createCar(String type) { if ("宝马".equals(type)) { return new BaoMa(); } else if ("飞驰".equals(type)) { return new BenChi(); } else { return null; } }}
真正去调用的时候,我只须要传入一个正确的参数,通过 CarFactory 创立出想要的货色就能够了,具体怎么去创立就不须要调用者操心了
public class Test { public static void main(String[] args) { Car baoMa = CarFactory.createCar("宝马"); baoMa.run(); Car benChi = CarFactory.createCar("飞驰"); benChi.run(); }}
(2) 优缺点
先说一下长处:
简略工厂模式的长处就在于其工厂类中含有必要的逻辑判断(例如 CarFactory 中判断是宝马还是飞驰),客户端只须要通过传入参数(例如传入 “宝马”),动静的实例化想要的类,客户端就免去了间接创立产品的职责,去除了与具体产品的依赖(都不须要晓得具体类名了,反正我不负责创立)
然而其毛病也很显著:
简略工厂模式的工厂类职责过于沉重,违反了高聚合准则,同时其内容多的状况下,逻辑太简单。最要害的是,当我想要减少一个新的内容的时候,例如减少一个保时捷,我就不得不去批改 CarFactory 工厂类中的代码,这很显然违反了 “开闭准则”
所以,工厂模式他就来了
(二) 工厂模式
(1) 实现
仍旧是一个汽车抽象类,一个宝马类和一个飞驰类是其子类
public abstract class Car { // 任何汽车都会跑 public abstract void run();}
public class BaoMa extends Car { @Override public void run() { System.out.println("【宝马】在路上跑"); }}
public class BenChi extends Car { @Override public void run() { System.out.println("【飞驰】在路上跑"); }}
如果是简略工厂类,就会 有一个总的工厂类来实例化对象,为了解决其毛病,工厂类首先须要创立一个汽车工厂接口类
public interface CarFactory { // 能够获取任何车 Car createCar();}
而后宝马和飞驰类别离实现它,内容就是创立一个对应宝马或者飞驰(实例化宝马类或者飞驰类)
public class BaoMaFactory implements CarFactory { @Override public Car createCar() { return new BaoMa(); }}
public class BenChiFactory implements CarFactory { @Override public Car createCar() { return new BenChi(); }}
想要获取车的时候,只须要通过多态创立出想要取得的那种车的工厂,而后通过工厂再创立出对应的车,例如我别离拿到飞驰和宝马就能够这样做:
public class Test { public static void main(String[] args) { // 先去飞驰工厂拿到一台飞驰 CarFactory benChiFactory = new BenChiFactory(); // 4S店拿到一台飞驰,给了你 Car benChi = benChiFactory.createCar(); benChi.run(); // 先去宝马工厂拿到一台宝马 CarFactory baoMaFactory = new BaoMaFactory(); // 4S店拿到一台宝马,给了你 Car baoMa = baoMaFactory.createCar(); baoMa.run(); }}
这种状况下,如果我还想要减少一台保时捷类型的车,创立出对应的保时捷类(继承 Car)以及对应保时捷工厂类后后,仍只须要通过以上办法调用即可
// 先去保时捷工厂拿到一台保时捷CarFactory baoShiJieFactory = new BaoShiJieFactory();// 4S店拿到一台保时捷,给了你Car baoShiJie = baoShiJieFactory.createCar();baoShiJie.run();
(2) 定义
工厂办法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂办法使一个类的实例化提早到其子类
看其结构图
(3) 优缺点
长处:
- 对象的创立,被明确到了各个子工厂类中,不再须要在客户端中思考
- 新内容减少十分不便,只须要减少一个想生成的类和创立其的工厂类
- 不违反 “开闭准则”,前期保护,扩大不便
毛病:
- 代码量显著减少
(三) 形象工厂模式
形象工厂模式是一种比较复杂的工厂模式,上面先间接通过代码理解一下
还是说车,咱们将车分为两种,一种是一般轿车,一种是卡车,后面的工厂办法模式中,如果一直的减少车的类型,这势必会造成工厂过多,然而对于常见的车来说,还能够寻找可抽取的特点,来进行形象
所以在此基础之上,咱们又别离设定了自动挡和手动挡两种类型,所以两两搭配,就有四种状况了(eg:自动挡卡车,手动挡轿车等等)
(1) 创立形象产品
- 首先别离创立一般轿车和卡车的抽象类,而后定义两个办法(这里我就写成一样的了,能够依据轿车和卡车的特点写不同的办法)
public abstract class CommonCar { // 所有车都能,停车 abstract void parking(); // 所有车都能,换挡 abstract void shiftGear();}
public abstract class Truck { // 所有车都能,停车 abstract void parking(); // 所有车都能,换挡 abstract void shiftGear();}
(2) 实现形象产品
阐明: A是主动的意思,H是手动的意思,eg:CommonCarA 代表一般自动挡轿车
- 实现形象产品——小轿车(自动挡)
public class CommonCarA extends CommonCar{ @Override void parking() { System.out.println("自动挡轿车A,停车挂P档"); } @Override void shiftGear() { System.out.println("自动挡轿车A,可换挡 P N D R"); }}
- 实现形象产品——小轿车(手动挡)
public class CommonCarH extends CommonCar { @Override void parking() { System.out.println("手动挡轿车H,停车挂空挡,拉手刹"); } @Override void shiftGear() { System.out.println("手动挡轿车H,可换挡 空 1 2 3 4 5 R"); }}
- 实现形象产品——货车(自动挡)
public class TruckA extends Truck { @Override void parking() { System.out.println("自动挡货车A,停车挂P档"); } @Override void shiftGear() { System.out.println("自动挡货车A,可换挡 P N D R"); }}
- 实现形象产品——货车(手动挡)
public class TruckH extends Truck { @Override void parking() { System.out.println("手动档货车H,停车挂空挡,拉手刹"); } @Override void shiftGear() { System.out.println("手动档货车H,可换挡 空 1 2 3 4 5 R"); }}
(3) 创立形象工厂
public interface CarFactory { // 创立一般轿车 CommonCar createCommonCar(); // 创立货车 Truck createTruckCar();}
(4) 实现形象工厂
通过自动挡手动挡这两个抽象概念,创立出这两个工厂,创立具备特定实现类的产品对象
- 自动挡汽车工厂类
public class AutomaticCarFactory implements CarFactory { @Override public CommonCarA createCommonCar() { return new CommonCarA(); } @Override public TruckA createTruckCar() { return new TruckA(); }}
- 手动挡汽车工厂类
public class HandShiftCarFactory implements CarFactory { @Override public CommonCarH createCommonCar() { return new CommonCarH(); } @Override public TruckH createTruckCar() { return new TruckH(); }}
(5) 测试一下
public class Test { public static void main(String[] args) { // 主动挡车工厂类 CarFactory automaticCarFactory = new AutomaticCarFactory(); // 手动挡车工厂类 CarFactory handShiftCarFactory = new HandShiftCarFactory(); System.out.println("=======自动挡轿车系列======="); CommonCar commonCarA = automaticCarFactory.createCommonCar(); commonCarA.parking(); commonCarA.shiftGear(); System.out.println("=======自动挡货车系列======="); Truck truckA = automaticCarFactory.createTruckCar(); truckA.parking(); truckA.shiftGear(); System.out.println("=======手动挡轿车系列======="); CommonCar commonCarH = handShiftCarFactory.createCommonCar(); commonCarH.parking(); commonCarH.shiftGear(); System.out.println("=======手动挡货车系列======="); Truck truckH = handShiftCarFactory.createTruckCar(); truckH.parking(); truckH.shiftGear(); }}
运行后果:
=======自动挡轿车系列=======自动挡轿车A,停车挂P档自动挡轿车A,可换挡 P N D R=======自动挡货车系列=======自动挡货车A,停车挂P档自动挡货车A,可换挡 P N D R=======手动挡轿车系列=======手动挡轿车H,停车挂空挡,拉手刹手动挡轿车H,可换挡 空 1 2 3 4 5 R=======手动挡货车系列=======手动档货车H,停车挂空挡,拉手刹手动档货车H,可换挡 空 1 2 3 4 5 R
补充两个概念
- 产品等级构造:产品的等级构造就是其继承构造,例如上述代码中,CommonCar(一般轿车) 是一个抽象类,其子类有 CommonCarA (自动挡轿车)和 CommonCarH(手动挡轿车),则 一般轿车抽象类就与具体自动挡或者手动挡的轿车形成一个产品等级构造。
- 产品族:产品族是同一个工厂生产,位于不同产品等级构造中的一组产品,例如上述代码中,CommonCarA(自动挡轿车)和 TruckA(自动挡货车),都是AutomaticCarFactory(自动挡汽车工厂)这个工厂生成的
(6) 结构图
看着结构图,咱们再捋一下
首先 AbstractProductA 和 AbstractProductB 是两个形象产品,别离对应咱们上述代码中的 CommonCar 和 Truck,为什么是形象的,因为它们能够都有两种不同的实现,即自动挡轿车和主动货车,手动挡轿车和手动挡卡车
ProductA1 和 ProductA2 和 ProductB1 和 ProductB2 就是具体的实现,代表 CommonCarA 和 CommonCarH 和 TruckA 和 TruckH
形象工厂 AbstractFactory 里蕴含了所有产品创立的形象办法,ConcreteFactory1 和 ConcreteFactory2 就是具体的工厂,通常是在运行时再创立一个 ConcreteFactory 的实例,这个工厂再创立具备特定实现的产品对象,也就是说为了创立不同的产品对象,客户端应该应用不同的具体工厂
(7) 反射+配置文件实现优化
形象工厂说白了就是通过内容形象的形式,缩小了工厂的数量,同时在具体工厂咱们能够这么用
CarFactory factory = new AutomaticCarFactory();
具体工厂只须要在初始化的时候呈现一次,这也使得批改一个具体工厂也是比拟容易的
然而毛病也是非常明显,当我想扩大一,比方加一个拖拉机类型,我就须要批改 CarFactory接口,AutomaticCarFactory 类 HandShiftCarFactory 类,(当然,拖拉机貌似没有什么自动挡,我只是为了举例子),还须要减少拖拉机对应的内容
也就是说,减少的根底上,我还须要批改原先的三个类,这是一个十分显著的毛病
除此之外还有一个问题,如果很多中央都申明了
CarFactory factory = new AutomaticCarFactory();
并且进行了调用,如果我更换了这个工厂,就须要大量的进行批改,很显然这一点是有问题的,咱们上面来应用反射优化一下
public class Test { public static void main(String[] args) throws Exception { Properties properties = new Properties(); // 应用ClassLoader加载properties配置文件生成对应的输出流 InputStream in = Test.class.getClassLoader().getResourceAsStream("config.properties"); // 应用properties对象加载输出流 properties.load(in); //获取key对应的value值 String factory = properties.getProperty("factory"); CarFactory automaticCarFactory = (CarFactory) Class.forName(factory).newInstance(); System.out.println("======轿车系列======="); CommonCar commonCarA = automaticCarFactory.createCommonCar(); commonCarA.parking(); commonCarA.shiftGear(); System.out.println("=======货车系列======="); Truck truckA = automaticCarFactory.createTruckCar(); truckA.parking(); truckA.shiftGear(); }}
config.properties
factory=cn.ideal.factory.abstractFactory.AutomaticCarFactory#factory=cn.ideal.factory.abstractFactory.HandShiftCarFactory
运行后果:
=======轿车系列=======自动挡轿车A,停车挂P档自动挡轿车A,可换挡 P N D R=======货车系列=======自动挡货车A,停车挂P档自动挡货车A,可换挡 P N D R
通过反射+配置文件咱们就能够使得应用配置文件中的键值对(字符串)来实例化对象,而变量是能够更换的,也就是说程序由编译时转为运行时,增大了灵活性,去除了判断的麻烦
回到后面的问题,如果咱们当初要减少一个新的内容,内容的减少没什么好说的,这是必须的,这是扩大,然而对于批改咱们却要尽量敞开,当初咱们能够通过批改配置文件来达到实例化不同具体工厂的形式
然而还须要批改三个类,以增加新内容,这里还能够通过简略工厂来进行优化,也就是去掉这几个工厂,应用一个简略工厂,其中写入createCommonCar();
等这些办法, 再配合反射+配置文件也能实现方才的成果,这样如果新增内容的时候只须要批改配置文件后,再批改这一个类就能够,即减少一个 createXXX
办法,就不须要批改多个内容了