工厂模式,顾名思义就是我们不用再关心我们需要的成品是怎么来的,只需要告诉工厂我们需要什么产品,然后拿到工厂返回给我们的成品就好了。举个例子,单身老司机想定做个硅胶娃娃(也可以是充气的),他不需要知道是怎么生产的,只需要给制造工厂说我要个165CM高的,然后工厂做好交给他,当然现实中钱还是要付的。
工厂模式隐藏了生产的一系列细节和过程(创建对象的过程),让我们只需要关注成品(对象),接过来就能用。
为什么要用工厂模式呢?
如果创建对象的过程比较复杂,需要写很多代码来获得我们需要的对象,而我们又要在很多地方获取这个对象,就需要写很多重复的代码,如果我们把这个过程封装到工厂里面,只需要把需求提出(我们要什么样的对象),然后工厂把我们要的对象返回给我们,这就实现了代码的复用。
举个简单的例子,我们需要把一段信息持久化,写入到文件里面,这时候我们需要写几行代码:
File file = new File("D:\\log.txt");
FileWriter writer = new FileWriter(file);
BufferedWriter out = new BufferedWriter(writer);
如果我们需要在很多地方写入文件,重复写这段代码,就造成了工作量的加大,当然这里只是举了个例子,看起来并没有太增加工作量。
这时候如果我们有十处地方写了这段代码,此时因为某些原因,我们需要改变写入文件的方法,变成了这样:
File file = new File("D:\\log.txt");
FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
这时候我们就需要更改这十处地方,一不小心还可能改漏了几处,你觉得你好难啊。。
如果我们改成如下的样子:
public class UtilFactory{
public static Writer getWriterObj(String path) throws IOException{
File file = new File(path);
FileWriter writer = new FileWriter(file);
BufferedWriter out = new BufferedWriter(writer);
return out;
}
}
//在其他地方调用
public static void main(String[] args){
Writer writer = UtilFactory.getWriterObj("D:\\log.txt");
}
此时如果我们需要改写入文件的方法,只需要改变getWriterObj()方法就可以了,调用的地方完全不用改变:
public class UtilFactory{
public static Writer getWriterObj(String path) throws IOException{
File file = new File(path);
FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
return osw;
}
}
调用的地方完全不用关心对象是怎么来的,是否产生对象的过程发生了变化,只需要知道这就是我想要的对象(成品)就可以了,把整个生产的过程包装起来,对外提供一致不变的使用接口,把重复性的工作统一起来,这就是工厂模式。
工厂模式分为三种类型:
- 简单/静态工厂模式
- 工厂方法模式
- 抽象工厂模式
简单/静态工厂模式是在工厂方法模式的基础上简化来的,抽象工厂模式又比工厂方法模式复杂一些。
1. 简单/静态工厂模式
简单/静态工厂模式适用于产品类型固定,种类简单,不再产生变化的场景。比如一个卖水果的老板请我们设计了一台机器自动卖水果,也卖水果汁,而且综合考虑了成本和收益之后决定只卖苹果和橙子,以后也不会添加种类了,这时候我们可以使用简单/静态工厂模式。
首先我们建立一个水果工厂:
/*
* 简单/静态工厂模式
*/
public class FruitFactory {
public static Fruit getFruit(String name) {
if("apple".equalsIgnoreCase(name)){
return new Apple();
}else if("orange".equalsIgnoreCase(name)){
return new Orange();
}else{
return null;
}
}
}
然后需要抽象出苹果和橙子的共同操作(可以直接吃,也可以榨汁)抽象出共同操作的作用是为了上面的水果工厂的返回值可以共同使用Fruit类型,里面无论实现多少个水果实现类,都是属于Fruit的子类,无需再更改:
public abstract class Fruit {
public abstract void eat();
public abstract void drink();
}
然后让苹果和橙子分别继承抽象类,实现抽象方法:
public class Apple extends Fruit {
@Override
public void eat() {
System.out.println("直接吃苹果");
}
@Override
public void drink() {
System.out.println("打苹果汁");
}
}
public class Orange extends Fruit {
@Override
public void eat() {
System.out.println("直接吃橙子");
}
@Override
public void drink() {
System.out.println("打橙汁");
}
}
这时候一个水果工厂就做好了:
public class Test {
public static void main(String[] args){
FruitFactory.getFruit("Orange").drink();
FruitFactory.getFruit("Orange").eat();
FruitFactory.getFruit("Apple").drink();
FruitFactory.getFruit("Apple").eat();
}
}
打印结果:
打橙汁
直接吃橙子
打苹果汁
直接吃苹果
然而此时黑心的老板告诉我们,又找到了一种利益更可观的水果想添加进来,软磨硬泡非要改需求,那我们在原有简单工厂模式的情况下,就需要对我们原有的代码进行改动(FruitFactory ),增加了系统维护的复杂度(简单工厂模式增加了耦合度,违背了开闭原则,即对扩展开放,对修改关闭),如果我们使用工厂方法模式就可以避免改动原有代码。
2. 工厂方法模式
我们不再使用一个水果工厂列举出我们能做的所有水果(增加水果就要改代码),而是抽象出一个水果工厂的招牌(接口),里面对每一种水果开一个工厂(实现接口),就像承包食堂一样,里面的卖饭窗口又承包给更小的摊贩,有卖饼的,有卖粥的,有卖菜的…我们不再需要关心我们会不会做这种饭,只要他符合我们食堂的规范,我们就可以给他个摊位经营。
接下来上代码实现:
/*
* 工厂方法模式
*/
public interface FruitFactory {
/*public static Fruit getFruit(String name) {
if("apple".equalsIgnoreCase(name)){
return new Apple();
}else if("orange".equalsIgnoreCase(name)){
return new Orange();
}else{
return null;
}
} */
//我们不再一揽子全包把所有水果列举出来,而是让每一个水果都有一个自己的工厂。接口抽象出一个方法,所有水果工厂都要实现这个方法。
public Fruit getFruit();
}
我们先新建一个苹果工厂、一个橙子工厂:
public class AppeFactory implements FruitFactory {
@Override
public Fruit getFruit() {
return new Apple();
}
}
public class OrangeFactory implements FruitFactory {
@Override
public Fruit getFruit() {
return new Orange();
}
}
其他的Fruit类、Apple类、Orange类都和简单工厂模式一样,此时我们再调用时就是这样:
public class Test {
public static void main(String[] args){
//获取苹果工厂
FruitFactory appleFactory = new AppeFactory();
//苹果工厂制造苹果
Fruit apple = appleFactory.getFruit();
//拿到苹果直接吃
apple.eat();
//或者榨汁
apple.drink();
//获取橙子工厂
FruitFactory orangeFactory = new OrangeFactory();
//橙子工厂制造橙子
Fruit orange = orangeFactory.getFruit();
//拿到橙子直接吃
orange.eat();
//或者榨汁
orange.drink();
}
}
打印结果:
直接吃苹果
打苹果汁
直接吃橙子
打橙汁
此时如果黑心老板再找我们要增加水果,我们只需要增加一个水果工厂的实现类就可以了(FruitFactory),比如增加梨:
先增加Pear类继承Fruit类:
public class Pear extends Fruit {
@Override
public void eat() {
System.out.println("直接吃梨");
}
@Override
public void drink() {
System.out.println("打梨汁");
}
}
然后增加制造梨的工厂(实现水果工厂接口)
public class PearFactory implements FruitFactory {
@Override
public Fruit getFruit() {
return new Pear();
}
}
然后直接调用就可以了:
public class Test {
public static void main(String[] args){
//获取苹果工厂
//....苹果和橙子直接吃或榨汁
//或者榨汁
orange.drink();
//添加梨的调用
FruitFactory pearFactory = new PearFactory();
Fruit pear= pearFactory.getFruit();
pear.eat();
pear.drink();
}
}
控制台输出:
直接吃苹果
打苹果汁
直接吃橙子
打橙汁
直接吃梨
打梨汁
这时候我们就可以在不需要改动原有代码的情况下添加了新的水果,而且便于后期维护,以及其他开发人员的扩展,但是也在编写的时候增加了代码量,所以需要针对不同的使用场景进行不同的选择。
3. 抽象工厂模式
接下来是抽象工厂模式,抽象工厂模式是在工厂方法模式的基础上再次封装,工厂方法模式解决的是一种问题,而抽象工厂模式解决的是多种问题结合在一起的一类问题,比如此时黑心老板想到把水果划分等级来售卖,比如有普通苹果,也有糖心苹果,有普通橙子,也有冰糖橙…我们势必要建立更多的类,代码会越来越庞大越来越难以管理,这时候我们把这些工厂的特征抽象出来,下面上代码:
//抽象出来水果共有的食用方式
public interface Fruit {
void eat();
void drink();
}
//苹果抽象类Apple
public abstract class Apple implements Fruit {
public abstract void eat();
public abstract void drink();
}
//橙子抽象类Orange
public abstract class Orange implements Fruit {
public abstract void eat();
public abstract void drink();
}
具体实现类划分等级
//品质好的苹果
public class BestApple extends Apple {
public void eat() {
System.out.println("直接吃糖心苹果");
}
public void drink() {
System.out.println("榨糖心苹果汁");
}
}
//一般的苹果
public class OrdinaryApple extends Apple {
public void eat() {
System.out.println("直接吃普通苹果");
}
public void drink() {
System.out.println("榨普通苹果汁");
}
}
//品质好的橙子
//......
//一般的橙子
//......
接下来创建工厂
//创建总的工厂,还是获取苹果和橙子
public interface FruitFactory {
public Fruit getApple();
public Fruit getOrange();
}
//分等级实现工厂类
public class BestFruit implements FruitFactory{
public Fruit getApple(){
return new BestApple();
}
public Fruit getOrange(){
return new BestOrange();
}
}
public class OrdinaryFruit implements FruitFactory{
public Fruit getApple(){
return new OrdinaryApple();
}
public Fruit getOrange(){
return new OrdinaryOrange();
}
}
然后调用代码:
public class Test {
public static void main(String[] args){
//获取最好的水果工厂
FruitFactory bestFactory = new BestFruit();
//从工厂生产最好的苹果
Fruit bestApple = bestFactory.getApple();
//从工厂生产最好的橙子
Fruit bestOrange = bestFactory.getOrange();
//喝苹果汁
bestApple.drink();
//喝橙汁
bestOrange .drink();
//获取普通水果工厂
//......
}
}
抽象工厂模式的好处是当一个产品的多个特征(对象)需要放到一起设计时(水果分为好多种,每种又分为好的和坏的),用户可以只从一个角度来使用产品中的对象,比如从水果等级来设计,最好的水果工厂,只生产最好的苹果和最好的橙子,普通水果工厂,只生产普通的苹果和橙子。这时候增加种类难,增加等级容易。
而当我们从水果种类设计,苹果工厂,只生产最好的苹果和普通苹果,橙子工厂,只生产最好的橙子和普通橙子,这时候增加种类容易,增加等级难。
我们客户端每次只需要面对产品族的一种特性就可以了,使改变每种场景的具体工厂变得容易。