文章系列目录(持续更新中):
【设计模式】第一篇:概述、耦合、UML、七大原则,详细分析总结(基于Java)
之前讲解 Spring 的依赖注入的文章时,我们就已经有提到过工厂这种设计模式,我们直接先通过一个例子来看一下究竟工厂模式能用来做什么?
【万字长文】Spring框架 层层递进轻松入门 (IOC和DI)
首先,我们简单的模拟一个对账户进行添加的操作,我们先采用我们以前常常使用的方式进行模拟,然后再给出改进方案
首先,按照我们常规的方式先模拟,我们先将一套基本流程走下来
/**
* 账户业务层接口
*/
public interface AccountService {
void addAccount();
}
/**
* 账户业务层实现类
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
public void addAccount() {
accountDao.addAccount();
}
}
/**
* 账户持久层接口
*/
public interface AccountDao {
void addAccount();
}
/**
* 账户持久层实现类
*/
public class AccountDaoImpl implements AccountDao {
public void addAccount() {
System.out.println("添加用户成功!");
}
}
由于,我们创建的Maven工程并不是一个web工程,我们也只是为了简单模拟,所以在这里,创建了一个 Client 类,作为客户端,来测试我们的方法
public class Client {
public static void main(String[] args) {
AccountService as = new AccountServiceImpl();
as.addAccount();
}
}
运行的结果,就是在屏幕上输出一个添加用户成功的字样
上面的这段代码,应该是比较简单也容易想到的一种实现方式了,但是它的耦合性却是很高的,其中这两句代码,就是造成耦合性高的根由,因为业务层(service)调用持久层(dao),这个时候业务层将很大的依赖于持久层的接口(AccountDao)和实现类(AccountDaoImpl)
private AccountDao accountDao = new AccountDaoImpl();
AccountService as = new AccountServiceImpl();
这种通过 new 对象的方式,使得不同类之间的依赖性大大增强,其中一个类的问题,就会直接导致出现全局的问题,如果我们将被调用的方法进行错误的修改,或者说删掉某一个类,执行的结果就是:
在编译期就出现了错误,而我们作为一个开发者,我们应该努力让程序在编译期不依赖,而运行时才可以有一些必要的依赖(依赖是不可能完全消除的)
所以,我们应该想办法进行解耦,要解耦就要使调用者和被调用者之间没有什么直接的联系,那么工厂模式就可以帮助我们很好的解决这个问题
具体怎么实现呢?在这里可以将 serivice 和 dao 均配置到配置文件中去(xml/properties),通过一个类读取配置文件中的内容,并使用反射技术创建对象,然后存起来,完成这个操作的类就是我们的工厂
注:在这里我们使用了 properties ,主要是为了实现方便,xml还涉及到解析的一些代码,相对麻烦一些,不过我们下面要说的 Spring 就是使用了 xml做配置文件
accountService=cn.ideal.service.impl.AccountServiceImpl
accountDao=cn.ideal.dao.impl.AccountDaoImpl
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 方法其中有两句核心代码的意义就是:
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;
}
public class Client {
public static void main(String[] args) {
AccountService as = (AccountService)BeanFactory.getBean("accountService");
as.addAccount();
}
}
当我们按照同样的操作,删除掉被调用的 dao 的实现类,可以看到,这时候编译期错误已经消失了,而报出来的只是一个运行时异常,这样就解决了前面所思考的问题
我们应该努力让程序在编译期不依赖,而运行时才可以有一些必要的依赖(依赖是不可能完全消除的)
为什么使用工厂模式替代了 new 的方式?
打个比方,在你的程序中,如果一段时间后,你发现在你 new 的这个对象中存在着bug或者不合理的地方,或者说你甚至想换一个持久层的框架,这种情况下,没办法,只能修改源码了,然后重新编译,部署,但是如果你使用工厂模式,你只需要重新将想修改的类,单独写好,编译后放到文件中去,只需要修改一下配置文件就可以了
我分享下我个人精简下的理解就是:
【new 对象依赖的是具体事物,而不 new 则是依赖抽象事物】
Break it down:
看完前面的例子,我想大家已经已经对工厂模式有了一个非常直观的认识了
说白了,工厂模式就是使用一种手段,代替了 new 这个操作
以往想要获取一个实例的时候要 new 出来,但是这种方式耦合性就会很高,我们要尽量的减少这种可避免的耦合负担,所以工厂模式就来了
工厂就是在调用者和被调用者之间起一个连接枢纽的作用,调用者和被调用者都只与工厂进行联系,从而减少了两者之间直接的依赖
工厂模式一共有三种 ① 简单工厂模式,② 工厂方法模式 ③ 抽象工厂模式
下面我们一个一个来说
下面我们以一个车的例子来讲,首先我们有一个抽象的 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();
}
}
先说一下优点:
简单工厂模式的优点就在于其工厂类中含有必要的逻辑判断(例如 CarFactory 中判断是宝马还是奔驰),客户端只需要通过传入参数(例如传入 “宝马”),动态的实例化想要的类,客户端就免去了直接创建产品的职责,去除了与具体产品的依赖(都不需要知道具体类名了,反正我不负责创建)
但是其缺点也很明显:
简单工厂模式的工厂类职责过于繁重,违背了高聚合原则,同时其内容多的情况下,逻辑太复杂。最关键的是,当我想要增加一个新的内容的时候,例如增加一个保时捷,我就不得不去修改 CarFactory 工厂类中的代码,这很显然违背了 “开闭原则”
所以,工厂模式他就来了
依旧是一个汽车抽象类,一个宝马类和一个奔驰类是其子类
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();
工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类
看其结构图
优点:
缺点:
抽象工厂模式是一种比较复杂的工厂模式,下面先直接通过代码了解一下
还是说车,我们将车分为两种,一种是普通轿车,一种是卡车,前面的工厂方法模式中,如果不断的增加车的类型,这势必会造成工厂过多,但是对于常见的车来说,还可以寻找可抽取的特点,来进行抽象
所以在此基础之上,我们又分别设定了自动挡和手动挡两种类型,所以两两搭配,就有四种情况了(eg:自动挡卡车,手动挡轿车等等)
public abstract class CommonCar {
// 所有车都能,停车
abstract void parking();
// 所有车都能,换挡
abstract void shiftGear();
}
public abstract class Truck {
// 所有车都能,停车
abstract void parking();
// 所有车都能,换挡
abstract void shiftGear();
}
说明: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");
}
}
public interface CarFactory {
// 创建普通轿车
CommonCar createCommonCar();
// 创建货车
Truck createTruckCar();
}
通过自动挡手动挡这两个抽象概念,创建出这两个工厂,创建具有特定实现类的产品对象
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();
}
}
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
补充两个概念
看着结构图,我们再捋一下
首先 AbstractProductA 和 AbstractProductB 是两个抽象产品,分别对应我们上述代码中的 CommonCar 和 Truck,为什么是抽象的,因为它们可以都有两种不同的实现,即自动挡轿车和自动货车,手动挡轿车和手动挡卡车
ProductA1 和 ProductA2 和 ProductB1 和 ProductB2 就是具体的实现,代表 CommonCarA 和 CommonCarH 和 TruckA 和 TruckH
抽象工厂 AbstractFactory 里包含了所有产品创建的抽象方法,ConcreteFactory1 和 ConcreteFactory2 就是具体的工厂,通常是在运行时再创建一个 ConcreteFactory 的实例,这个工厂再创建具有特定实现的产品对象,也就是说为了创建不同的产品对象,客户端应该使用不同的具体工厂
抽象工厂说白了就是通过内容抽象的方式,减少了工厂的数量,同时在具体工厂我们可以这么用
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
方法,就不需要修改多个内容了
邮箱:ideal_bwh@163.com
如果帮到你的话,那就来关注我吧!
如果您更喜欢微信文章的阅读方式,可以关注我的公众号
如果您更加喜欢PC端的阅读方式,可以访问我的个人博客
域名:www.ideal-20.cn
在这里的我们素不相识,却都在为了自己的梦而努力 ❤ 一个坚持推送原创开发技术文章的公众号:理想二旬不止