工厂模式我个人认为其实比较难理解的,如果有接触过|听过|见过该模式的同学很可能就会想:我自己new
一个对象出来就好了,简单快捷。用得着你这个工厂模式吗?搞一个工厂出来还要写一大堆的代码呢~
网上的很多资料都是在阐述着:工厂模式的好处就是解耦。相信大家对解耦这个词也不陌生,那解耦究竟有什么好处呢?
本文章试图去解释为什么要用工厂模式,用了工厂模式的好处是什么,以及工厂模式衍生出的三种形式究竟有什么区别~~
那么接下来就开始吧,如果有错的地方希望能多多包涵,并不吝在评论区指正!
一、工厂模式概述
在《设计模式之禅》这本书中分了两章节讲工厂模式:
- 工厂方法模式
- (ps:其中里面讲到了简单工厂模式)
- 抽象工厂模式
网上的大部分资料都是将工厂模式分成三种:
- 简单/静态工厂模式
- 工厂方法模式
- 抽象工厂模式
看完上面的叙述是不是想打死我,什么鸟玩意?不急哈,下面我会一一讲到~~
1.1为什么要用工厂模式?
想想我们为什么要用工厂模式?下面我就简单举例子:
文件IO的操作我们会经常用得到吧,所以BufferedReader对象经常要创建的:
// 创建一个BufferedReader对象 BufferedReader bf = new BufferedReader(new FileReader(new File("aa.txt")));
你说麻烦吗?其实也不麻烦,就一行代码嘛,哪里麻烦了~如果不太熟悉IO流的同学就没有那么机灵了,创建一个BufferedReader可能就是以下的代码了:
File file = new File("aa.txt"); FileReader fileReader = new FileReader(file); BufferedReader bufferedReader = new BufferedReader(fileReader);
你说麻烦吗?其实也不麻烦,不就是三行代码嘛,哪里麻烦了~如果这个应用很多的类上都用到了BufferedReader对象的话,那每个类都写上这三行代码了。那你说麻烦吗?那肯定麻烦啊,还用想啊….
可以看出来,创建一个BufferReader对象里面需要一个FileReader对象,而FileReader对象又要File对象。那创建这个BufferReader对象还是比较麻烦的(代码上看不麻烦,从构造上看还是挺麻烦的)!
虽然比较麻烦,但我们还能用,能用就行!于是乎,我们就去写代码了,现在有三个类都要进行文件的读写操作,于是他们就有这样的代码:
public class FileOperateA { public static void main(String[] args) throws FileNotFoundException { File file = new File("aa.txt"); FileReader fileReader = new FileReader(file); BufferedReader bufferedReader = new BufferedReader(fileReader); // 读写文件.... } }
此时:上头说,我要换成LineNumberReader来读写,有这个需求!那我们作为一个写代码的,能怎么办?很绝望也需要去完成呀。
- 不熟悉IDE的小伙子就一个一个将BufferedReader改成LineNumberReader,现在就3个类用到了BufferedReader,也就改6次而已。(ps:那如果很多地方都用到了呢?)
- 熟悉IDE的小伙子就全局替换重构,妥妥的!
哎,写个代码屁事真多…那有没有一种方法能够让创建对象变得简单而且修改对象时能很方便呢?
- 哎,工厂模式就行了。
再说从面向对象的角度来看:我一个操作文件的类还要我会创建BufferReader是不是有点过分了?(职责没有分工好)
- 交给工厂来创建对象这就很面向对象了!
1.2体验工厂模式
何为工厂?将我们的产品都交由工厂来生产!我现在用的iphone5s,从哪来?从富士康组装而来,富士康是工厂。我用得着知道iphone5s在富士康是怎么组装起来的吗?不需要。
来,我们来改造一下上面的例子。首先我们创建一个工厂类,它可以生产Reader对象!
// 创建Reader对象的工厂 public class ReaderFactory { public static Reader getReader() throws FileNotFoundException { File file = new File("aa.txt"); FileReader fileReader = new FileReader(file); BufferedReader reader = new BufferedReader(fileReader); return reader; } }
那么我们要得到BufferReader对象就贼简单了:
public class FileOperateA { public static void main(String[] args) throws FileNotFoundException { //-------我有工厂了,还用自己搞吗?不用了! //File file = new File("aa.txt"); //FileReader fileReader = new FileReader(file); //BufferedReader bufferedReader = new BufferedReader(fileReader); //-------我有工厂了,还用自己搞吗?不用了! // 用工厂来创建出对象 Reader reader = ReaderFactory.getReader(); // 读写文件.... } }
工厂将我们创建的对象过程给屏蔽了!
此时我要改成LineNumberReader怎么玩?在工厂上改一下就好了:
我们的调用方FileOperateA|FileOperateB|FileOperateC
这些类完全就不用变!
1.3使用工厂方法的好处
从上面的工厂模式体验我们就可以看到:
- 我们修改了具体的实现类,对客户端(调用方)而言是完全不用修改的。
- 如果我们使用
new
的方式来创建对象的话,那么我们就说:new
出来的这个对象和当前客户端(调用方)耦合了!
- 也就是,当前客户端(调用方)依赖着这个
new
出来的对象!
这就是解耦的好处!
我再放下我之前练习的时候写过的代码吧:
我有一个DaoFactory,逻辑很简单就是专门创建Dao对象的~
那么在Service层就可以使用工厂将想要的Dao对象初始化了~
此时我们的Service与Dao的对象低耦合的~
- 大家可能看不出有什么好处,还弄了一大堆的字符串啥的~~
在Service与Controller层我也弄了一个ServiceFactory,根据当时业务的需要(添加权限),我创建Service时就非常灵活了:
二、如何使用工厂模式
在一开始我就说了,工厂模式可以分成三类:
- 简单/静态工厂模式
- 工厂方法模式
- 抽象工厂模式
下面我就逐一来介绍一下每一种工厂模式有什么不一样~
三种模式都以:Java3y要买宠物的例子来讲解~
2.1工厂方法模式
很多博客都是以简单/静态工厂模式,工厂方法模式,抽象工厂模式这个顺序来讲解工厂模式的。我认为按书上的顺序比较好理解~因为简单/静态工厂模式是在工厂方法模式上缩减,抽象工厂模式是在工厂方法模式上再增强。
- 所以我就先讲工厂方法模式了。
Java3y每天写代码很无聊,想要买只宠物来陪陪自己。于是乎就去宠物店看宠物啦~~~
作为一间宠物店,号称什么宠物都有!于是乎,店主宣传的时候就说:我的宠物店什么宠物都有!
于是构建宠物的工厂就诞生了~
// 号称什么宠物都有 public interface AnimalFactory { // 可以获取任何的宠物 Animal createAnimal(); }
当然了,主流的宠物得进货一些先放在店里充充门面,一些特殊的宠物就告诉顾客要时间进货~
- 所以,我们就有了构建猫和狗的工厂(继承着所有宠物的工厂)
猫工厂:
// 继承着宠物工厂 public class CatFactory implements AnimalFactory { @Override // 创建猫 public Animal createAnimal() { return new Cat(); } }
狗工厂也是一样的:
// 继承着宠物工厂 public class DogFactory implements AnimalFactory { // 创建狗 @Override public Animal createAnimal() { return new Dog(); } }
嗯,还有我们的实体类:猫、狗、动物(多态:猫和狗都是动物,可以直接用动物来表示了)
动物实体类:
public abstract class Animal { // 所有的动物都会吃东西 public abstract void eat(); }
猫实体类:
public class Cat extends Animal { // 猫喜欢吃鱼 @Override public void eat() { System.out.println("猫吃鱼"); } }
狗实体类:
public class Dog extends Animal { // 狗喜欢吃肉 @Override public void eat() { System.out.println("狗吃肉"); } }
那么现在Java3y想要一只狗,跟了宠物店老板说,宠物店老板就去找狗回来了:
// 去找狗工厂拿一只狗过来 AnimalFactory f = new DogFactory(); // 店主就拿到了一只狗给Java3y Animal a = f.createAnimal(); a.eat(); System.out.println("关注公众号:Java3y");
那么现在Java3y想要一只猫,跟了宠物店老板说,宠物店老板就去找猫回来了:
// 去找猫工厂拿一只猫过来 AnimalFactory ff = new CatFactory(); // 店主就拿到了一只猫给Java3y Animal aa = ff.createAnimal(); aa.eat(); System.out.println("关注公众号:Java3y");
如果这个时候Java3y说想要一只蜥蜴怎么办啊?没问题啊,店主搞个蜥蜴工厂就好了~~
// 要买蜥蜴.. AnimalFactory fff = new LizardFactory(); Animal aaa = ff.createAnimal(); aaa.eat();
优点:
- 1:客户端不需要在负责对象的创建,明确了各个类的职责
- 2:如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可
- 3:不会影响已有的代码,后期维护容易,增强系统的扩展性
缺点:
- 1:需要额外的编写代码,增加了工作量
工厂方法类图: