共计 8976 个字符,预计需要花费 23 分钟才能阅读完成。
在编码一段时间之后,对框架、库又有了新的了解,于是打算重新学习下框架、库。
重回 Servlet
让我回顾下我的 Java 之路,刚开始是学 Java SE,在控制台外面输入看后果,过后感觉这个控制台还是比拟枯燥的,不如动画桌面活泼,原本打算学习下 Swing,起初发现这玩意有点丑,起初发现 Java 的市场在 WEB 端,也就是浏览器这里,而后就开始学习 JavaScript、CSS、HTML、Jquery。学完这三个,还是没有感应到 Java 的用途,我那个时候在想浏览器中输入框的值该怎么到 Java 外面呢?答案就是 Servlet,Servlet 是浏览器和服务端的桥梁。像上面这样:
而后接到值之后,咱们还是通常须要存储一下,也就是跟数据库打交道,也就是 JDBC,起初发现 JDBC 有些步骤是反复的,不用每次和数据库打交道的时候都反复写一遍,也就是 DBUtils。这仿佛是一个残缺的流程了,从页面接收数据,存储到数据库,而后页面发动申请再将数据库中的数据返回给数据库。然而这个过程并不完满,第一个就是 Java 和数据库的单干,每次发送 SQL 语句都是新建一个连贯,这相当耗费资源,于是在这里咱们又引入了数据库连接池。那个时候还不怎么回用 maven,解决依赖,那个时候总是会去找许多 jar 包粘贴在 lib 目录下。
那个时候的一个数据处理链条大抵是这样的:
而后很快咱们就发现代码越写越多了,于是咱们就是开始划分构造,也就是 MVC,下面的图就被切成了上面这样:
view 是视图层提供给用户,供用户操作,是程序的外壳。Controller 依据用户输出的指令,选取对应的 model,读取,而后进行相应的操作,返回给用户。
这三层是紧密联系在一起的,但又是相互独立的,每一层外部的变动不影响其余层。每一层都对外提供接口(Interface),供下面一层调用,这样一来,软件就能够实现模块化,批改外观和变更数据都不必批改其余层,大大不便了软件的保护和降级。
然而还不是很欠缺,当初来看咱们的代码构造变得清晰了。
软件设计的一个指标就是写的代码越来越少,原生的 JDBC 还是有些繁琐,那么是否更进一步,更加简洁一些呢? 这是许多 Java 界的开发者提出的问题,答案就是 ORM(Object Relational Mapping)框架, 比拟有代表性的就是 MyBatis、Hibernate。
这是在缩小模型层的代码量,然而目前整合 ORM 框架、连接池还是编码,整合的优劣受制于编写代码人的程度。当初咱们引入的框架之后,咱们的引入的框架之间的关系就变成了就变成了上面这样:
看起来仿佛构造很参差的样子,但并不是咱们想要的指标,因为通常一个 WEB 工程并不止会引入一个框架,咱们会陆陆续续引入其余框架,比方定时工作框架,日志框架,监控框架,权限管制框架。像上面这样:
凌乱而无序,兴许会为每个框架筹备一个工具类,封装罕用的办法。然而这仍不是咱们想要的指标,因为代码不是变化无穷的,比方 ORM 框架,可能你在和数据库连接池整合的时候是一个版本,然而后续发现要降级,这个时候如果你是硬编码的话,你就得该代码,可能还须要考虑一番,那么咱们能不能不硬编码呢?对立治理这些框架之间的依赖关系,做到可配置化。
这也就是工厂模式,Spring 的前身,当然 Spring 不只是工厂模式。
如何取对象 – IOC 与 DI 绪论
在 Java 的世界里,咱们取得对象的形式次要是 new,对于简略的对象来说,这一点问题都没有。然而有些对象创立起来,比较复杂,咱们心愿可能暗藏这些细节。留神强调一遍,咱们心愿对于对象的使用者来说,暗藏这些细节。
这是框架采纳工厂模式的一个次要目标,咱们来看一下 MyBatis 中的工厂:
咱们通过 SqlSessionFactory 对象很容易就拿到了 SqlSession 对象,然而 SqlSession 对象的就比较复杂, 咱们来看下源码:
这是工厂模式的一种典型的利用场景。
Java 的世界是对象的世界,咱们关注的问题很多时候都是在创建对象、应用对象上。如果你想用一个 HashMap,大可不必用上设计模式,间接 new 就能够了。然而不是所有的对象都像 HashMap 那样,简略。在我对设计模式和 Spring 框架还不是很理解之前,没用 Spring 系的框架,在 Service 层调用 Dao 层的时候,还是通过 new,每个 Service 的办法调用 Dao,都是通过 new,事实上在这个 Service 类,同样类的对象能够只用一个的,也就是单例模式。不懂什么叫单例模式的,能够参看我这篇博客:
明天咱们来聊聊单例模式和枚举
那么这个时候咱们的愿景是什么呢? 在面向对象的世界里,咱们次要的关注的就是取 (创立) 对象和存对象,就像数据结构一样,咱们次要关注的两点是如何存进去,如何取出来。创立的对象也有着不同的场景,下面咱们提到的有些对象创立起来比较复杂,咱们心愿对对象的使用者屏蔽掉这类细节。这类场景在 Java 中是很常见的,比方线程池的创立:
尽管咱们并不举荐间接应用这种形式创立线程池。那么还有一种状况就是扩展性设计,比如说刚开始我定义了一种对象,将该对象对应的操作方法搁置到了一个接口中,这也就是面向对象的设计理念,做什么用接口,是什么用对象。然而随着倒退,原来的对象曾经无奈满足我的要求了,然而这个对象从属于某一类库,原来的对象对应的事实实体还在应用,为了向前兼容,我只得又建设了一个类,之前的操作还是独特的。你可能感觉我说的有点形象,这里举一个比拟典型的例子,Java 的 POI 库,用于操纵 excel,应用频率是很高的,excel 是有几种不同的版本的,Excel 2003 文件后缀为 .xls, Excel 2007 及以上版本文件后缀为.xlsx。excel-2003 的最大行数是 65536 行,这个行数有些时候是无奈满足需要的,在 2007 版冲破了 65536 行,最大为 1048576 行。POI 也思考到了这个,扩展性还是比拟高的,将对 excel 的操作形象进去放在 workbook 中,专属于某个版本的操作放在对应的 excel 类中,像上面这样:
HSSFWorkbook 对应 03 版,Xssfworkbook 对应 07 版,SXSSFWorkbook 为了解决读取大型 excel 数据内存溢出而推出的。少数状况下,咱们要应用的其实是 workbook 中的办法,没必要还要去看看以后读取的 excel 是哪个版本的,这也就是 WorkbookFactory 的初衷,你读文件须要文件对象,你将这个文件对象交给我,我来向你返回对应的 Workbook 对象。
这里形象一点的形容就是,应用该对象不再通过 new,而是向工厂传递标识符,由工厂来返回对应的对象。这也就是简略工厂模式。很多解说简略工厂模式的博客或者视频 (讲 Spring 框架入门的或者讲设计模式的) 通常会从解耦合的角度登程,我在看视频或者看博客的时候,心里就会有一个问题,你解的是谁和谁的耦合,如果是从下面的介绍的 WorkbookFactory 来说的话,没用这种简略工厂模式,那么我认为就是心愿应用 WorkBook 对象的编码者和具体的 Excel 版本的类耦合了起来,像上面这样:
public class ExcelDemo {public static void main(String[] args) throws IOException {XSSFWorkbook xssfWorkbook = new XSSFWorkbook("");
HSSFWorkbook hssfWorkbook = new HSSFWorkbook();
SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook();}
}
我在刚开始做 Excel 导入的时候,就是先去依据 Excel 版本去找对应的类,而后做读取数据工作。在写完之后我又想了想,这样不是强制限定了用户上传的 Excel 版本了吗?假如用户上传的不是这个版本,我应该给个异样提醒?这样仿佛有点不敌对吧,03 版本的 Excel 也有人再用啊。我想了想,把我的代码改成了上面这样:
public void uploadExcel(MultipartFile file) throws IOException {
Workbook workbook = null;
if (file.getName().endsWith(".xls")){workbook = new HSSFWorkbook(file.getInputStream());
}else if (file.getName().endsWith(".xlsx")){workbook = new XSSFWorkbook(file.getInputStream());
}
// 做真正的业务解决
}
这样我的代码就和用户上传的 excel 版本解除了耦合,用户上传哪个版本我都能做导入,那个时候我还不晓得工厂模式,只是下意识的实现需要的时候,想让上传的 Excel 版本不受限制。而后某天我偶尔看到了 WorkbookFactory 这个类,这个类解除了 WorkBook 对象和具体 Excel 版本文件对象的耦合。
用代码来演示下面介绍的工厂模式就是:
public interface AnimalBehavior {
/**
* 所有动物都会睡觉
*/
void sleep();
/**
* 都要吃饭
*/
void eat();}
public class Cat implements AnimalBehavior {
@Override
public void sleep() {System.out.println("我是猫, 喜爱睡觉");
}
@Override
public void eat() {System.out.println("我是猫, 喜爱吃鱼");
}
}
public class Dog implements AnimalBehavior {
@Override
public void sleep() {System.out.println("我是狗, 睡觉喜爱在狗窝睡");
}
@Override
public void eat() {System.out.println("我是狗, 喜爱啃骨头");
}
}
public class AnimalBehaviorFactory {public static AnimalBehavior create(String name){
AnimalBehavior animalBehavior = null;
if ("dog".equals(name)){animalBehavior = new Dog();
}else if ("cat".equals(name)){animalBehavior = new Cat();
}
return animalBehavior;
}
}
public class Test {public static void main(String[] args) {AnimalBehavior dog = AnimalBehaviorFactory.create("dog");
dog.eat();
AnimalBehavior cat = AnimalBehaviorFactory.create("cat");
cat.eat();}
}
我刚开始学习简略工厂模式的时候就是有人写了相似的例子,通知我这叫解耦合,我过后心里的想法是,这是啥呀,齐全看不出来这个设计模式好在哪里啊?这就是设计模式? 这就是解耦合?你倒是通知我,谁跟谁解耦合了啊? 就这? 我学了这玩意之后,齐全感触不到用武之地啊?甚至我跟他人解释简略工厂模式是这?我都感觉不好意思。
在写了一些代码之后,代码量之后,我能够给出答案,谁和谁解耦的答案,AnimalBehavior 实例的调用者和具体的动物对象 (Cat、Dog) 解除了耦合,只须要将某些标识传递给工厂即可,工厂向你返回对应的 AnimalBehavior 实例,同时能够屏蔽对应对象创立的简单细节。实在的应用场景就是 POI 的 WorkBookFactory。简略工厂模式咱们讲的差不多了,这里 UML 图我也不画了,因为我感觉有同学可能还不会看 UML 图(前面会专门出介绍 UML 的文章),另一方面我感觉我讲的曾经足够清晰了。
在面向对象的世界里,咱们始终关怀的是如何取对象,简略点,再简略点。在取对象的路上咱们面临的另一个比较复杂的场景就是,对象之间有简单的依赖关系,比方咱们的 ORM 框架 MyBatis,依赖于连接池,而连接池又依赖于对应的数据库驱动,于是创立一个 SqlSession 对象就是一组对象的创立和注入,本人一个的去 new 吗?这台麻烦了吧?能不能对立治理呢?因为哪天我可能更换连接池啊?这就是 Spring IOC 思路,你将对象先放入 IOC 容器中,配置好他们之间的依赖关系,而后 IOC 容器帮你 new 就能够了,你就间接只关怀取就能够了。
还有一种状况就是你晓得怎么创立一个对象,然而无奈把控创立的机会,你须要把 ” 如何创立 ” 的代码塞给 ” 负责什么时候创立 ” 的代码。后者在适当的理论,就调用对应的创建对象的函数。
除此之外,在一些语言比方 C ++ 的构造函数是不容许抛出异样的,因为这将产生一个没有被残缺结构的对象,从而导致对应的内存没有正确开释。在 Java 中,尽管语言上反对在构造函数中抛异样,然而这是一种并不举荐的做法,因为这不是本篇的主题,这里不做探讨,具体的能够参考这篇博客:
- [改善 Java 代码]不要在构造函数中抛出异样
然而业务上要求在创建对象的时候,抛出一个异样,咱们该如何解决,咱们也能够通过工厂模式,未必是下面的简略工厂模式,但这也是工厂模式思路的一种利用。
工厂模式的实质就是对获取对象过程的形象,所以接下来介绍的工厂办法模式和形象工厂模式会比拟形象,咱们须要缓缓的领会这两种设计模式的益处,须要肯定的代码量能力领会。
工厂办法模式
工厂办法模式 Factory Method,在工厂办法模式中,外围的工厂类不再负责所有的产品的创立,而是将具体创立工作交给子类去做。该外围类称为一个形象工厂角色,仅仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类该当被实例化这种细节。还是下面的例子,咱们就能够转变成了上面:
// 定义顶层的外围工厂类
public interface AbstractAnimalFactory {AnimalBehavior getBehavior();
}
// 具体的子类负责对应的创立
public class CatFactory implements AbstractAnimalFactory {
@Override
public AnimalBehavior getBehavior() {return new Cat();
}
}
public class DogFactory implements AbstractAnimalFactory {
@Override
public AnimalBehavior getBehavior() {return new Dog();
}
}
public class Test {public static void main(String[] args) {AbstractAnimalFactory abstractAnimalFactory = new DogFactory();
AnimalBehavior dog = abstractAnimalFactory.getBehavior();
dog.eat();}
}
益处就是,不必硬编码,假如在下面的工厂模式中,咱们要增加个猪对象,而后下面的简略工厂就得改,须要再加个判断。
咱们并不心愿动老代码,即便你小心翼翼的去批改,也无奈做到十拿九稳。那么对于这种工厂办法模式来说,咱们只须要在加个猪厂就行了,复合设计准则,对批改敞开,对增加凋谢。
再加一个猪场:
public class Pig implements AnimalBehavior {
@Override
public void sleep() {System.out.println("猪睡了");
}
@Override
public void eat() {System.out.println("猪啥都吃");
}
}
public class PigFactory implements AbstractAnimalFactory {
@Override
public AnimalBehavior getBehavior() {return new Pig();
}
}
形象工厂模式
工厂模式的实质就是对获取对象过程的形象,咱们再强调一遍,然而不是一种形象就能够应酬所有创建对象的场景,因为站在不同的角度,你就会看见不同的场景。
从很多博客对形象工厂模式的实现上来说,绝对于工厂办法模式,顶层的外围工厂不再只是一个办法,顶层的外围工厂会呈现若干个形象办法,也就是顶层的外围工厂所能生产的对象是一族的产品,每个子工厂生产的产品也不止有一个,这样的例子在事实世界是常见的,比方小米不仅出品手机,还出品只能家居和笔记本电脑。不仅是小米,苹果也做智能家居和电脑。
从这个角度来看的话,工厂办法模式更像是形象工厂模式的减弱版本。咱们首先咱们筹备两类产品,第一个是手机接口,形容了生产手机的标准,第二个是 PC 接口,形容了生产 PC 的标准。你当然也能够用其余去举例,比方高端口罩和低端口罩,但最终表白的意思是统一的。
public interface PersonalComputer {
// 晓得电脑
void makePc();}
public interface Phone {
// 制作手机
void makePhone();}
而后上面是外围工厂:
public interface AbstractEmFactory {PersonalComputer createPersonalComputer();
Phone createPhone();}
而后是对应生产产品的工厂:
public class MiEmFactory implements AbstractEmFactory {
@Override
public PersonalComputer createPersonalComputer() {return new MiPC();
}
@Override
public Phone createPhone() {return new MiPhone();
}
}
public class OppoEmFactory implements AbstractEmFactory {
@Override
public PersonalComputer createPersonalComputer() {return new OppoPC();
}
@Override
public Phone createPhone() {return new OppoPhone();
}
}
测试类:
public class Test {public static void main(String[] args) {OppoEmFactory oppoEmFactory = new OppoEmFactory();
PersonalComputer personalComputer = oppoEmFactory.createPersonalComputer();
personalComputer.makePc();
Phone oppoPhone = oppoEmFactory.createPhone();
oppoPhone.makePhone();}
}
绝对于工厂办法模式来说,形象工厂办法模式的长处是不用没多一类产品,我就来个工厂去制作它,咱们能够依据特点将他们演绎成一族,这样的话也缩小了工厂子类,更容易保护。
总结一下
工厂模式并不是一个独立的设计模式,而是三种性能靠近的设计模式的统称,这三种设计模式别离是简略工厂模式、工厂办法模式、形象工厂模式。事实上在《设计模式之禅》这本书分了两章节讲工厂模式,并没有将咱们下面讲的简略工厂模式独自拎进去讲,而是将咱们下面提到的简略工厂模式算在工厂办法模式中了。然而往上大部分材料都是将工厂模式分为三种:
- 简略 / 动态工厂模式
- 工厂办法模式
- 形象工厂模式
在《Effective Java》中作者提倡应用动态工厂模式代替结构器,为什么这种提倡呢,这种提倡是建设在你有多个构造函数的前提下的,咱们晓得构造函数咱们是没有方法改名的,咱们能不能通过见名知义准则向调用该对象的人裸露更多的信息呢?
也就是通知调用者,此时产生的是什么对象。这是动态工厂模式的另一种应用场景。
咱们再来扫视以下: 简略工厂模式、工厂办法模式、形象工厂模式。独特的作用还是心愿调用者可能更不便的拿到须要应用的对象,也就是解除耦合,同时也不便集中管理。咱们心愿的是应用对象的人尽可能简略的获取到想要应用的对象,而不是去间接尝试去寻找这个类,而后用构造函数去产生,在面向对象的世界,遍地都是对象,尽可能的演绎对象,收拢对象,这是工厂模式要解决的问题。
简略工厂模式依据调用者传递的标识符来向调用者返回对应的对象,解除了调用者与理论类的耦合,但并不合乎开闭准则 (对批改敞开,对增加凋谢)。假如我再要多一类,那么简略工厂模式就可能须要再多一个判断,长处是绝对于工厂办法模式来说,咱们就不须要晓得对应的工厂了。但咱们切记不要生吞活剥,只是单纯的应用某一类模式,要依据状况去组合应用。
工厂办法模式复合开闭准则,然而对于调用者来说要去尝试理解对应的工厂,能力产生对应的对象。那么随着产品的增多,这种只能生产一种产品的工厂很快就不能在满足咱们的须要,因为每种产品都须要一个厂子,这么多的厂子对于保护的人来说也是一个累赘,咱们依据产品的特点将他们划分为一族,形象工厂生产的就是一族的产品。
没有完满的设计模式,没有哪种设计模式适应所有的情况,咱们须要依据理论状况去抉择对应的模式,然而也能够在实践中对对应的设计模式加以革新,以便达到最佳的解耦合成果。比方 WorkbookFactory 这个类也不是一开始就增加到了 POI 中,也是在 4.0 版本引入的。
不论你用什么语言,创立什么资源。当你开始为“创立”自身写代码的时候,就是在应用 ” 工厂模式 ” 了。
参考资料:
- 谈谈 MVC 模式 阮一峰
- 工厂模式(factory Method)的实质是什么?为什么引入工厂模式?
- Spring 视频教程 颜群
- 漫画:设计模式之“工厂模式”
- 工厂模式了解了没有?
- 为什么 C ++ 构造函数中不能抛出异样
- 设计模式之工厂模式(factory pattern))