共计 10791 个字符,预计需要花费 27 分钟才能阅读完成。
一 为什么要用工厂模式
之前解说 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.AccountServiceImpl
accountDao=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
办法,就不须要批改多个内容了