-
/ 前言 /
我会按照创建型、结构型、行为型来分类讲解这 23 中设计模式,我当初在学习的时候是随机学习的,这样总是让我感觉混乱,如果我们按照类型来学习设计模式,相同的类型你学习了 4、5 个,那么你只需要记住它们之间的不同你就可以全部记住它们,首先我们先从创建型开始
工厂模式可能是大家最早接触到的设计模式之一了,被广泛运用于各大框架,如 Spring、Mybatis, 并且它也是我们最常用的设计模式之一
纠结了很久最后决定将工厂方法与抽象工厂分开写,并且在最后我会着重描述工厂方法与抽象工厂之间的区别,这也是我初学设计模式时碰到的迷惑,因为它们俩个太像了
有的朋友可能看见标题会纳闷,简单工厂又是个什么,其实简单工厂不属于 23 种之一,它相当于工厂模式的一个初始版本,虽然简陋,却是学习工厂模式中很重要的一环,我们可以从简单工厂 → 工厂方法 → 抽象工厂的演化中学习到设计 / 架构的思想,这对于提升我们的编程水平来说是很重要的
-
/ 1 / 为什么要使用工厂模式
相信大家在编程的时候一定会涉及到对象的操作,比如 new 一个对象,那如果我在多个地方都用到了这个对 象怎么办,我们想的第一个方式就是抽象,那难不成为了创建对象我创建一个父类来让所以要使用该对象的类来继承吗,不是说不可以,只不过这样耦合度就高了,为了解决这一现象大家就设计出了工厂模式
还有一种场景,这个对象也许我们只需要用到大约 2~3 次,但是该对象的构造器非常复杂,而且很多数据不是用户输入的,是需要程序来设置的,此时我们无法保证每次创建对象的过程都是无误的,所以我们将创建该对象的过程抽取到一个单独的类中,然后直接通过调用该类来获取对象,那个类就是工厂
通过上述介绍大家应该明白了工厂的作用,减少类之间的耦合,将重复的代码抽取出来,形成独立的体系
-
/ 2 / 工厂模式怎么用
-
简单工厂
我们先来看一下我们不使用设计模式的代码
import lombok.AllArgsConstructor; @AllArgsConstructor public class Keyboard { private String name; private String desc; } public class Demo { // 客户端 A class ClientA {public static void main(String[] args) {Keyboard keyboard = new Keyboard("Cherry","我是行业标准!"); } } // 客户端 B class ClientB {public static void main(String[] args) {Keyboard keyboard = new Keyboard("Razer","游戏不二之选!"); } } // 客户端 C class ClientC {public static void main(String[] args) {Keyboard keyboard = new Keyboard("Cherry","我是行业标准!"); } } }
受限于篇幅,我们只是简单的举例子,但是在实际开发中,客户端可能有 10 几 20 个,你这代码不就是重复的问题吗,我抽出去行了吧,于是变成了如下
public class Demo {public Keyboard getKeyboard (String name, String desc) {return new Keyboard(name,desc); } // 客户端 A class ClientA {public Keyboard getKeyboardA() {return getKeyboard("Cherry","我是行业标准!"); } } // 客户端 B class ClientB {public Keyboard getKeyboardB() {return getKeyboard("Razer","游戏不二之选!"); } } // 客户端 C class ClientC {public Keyboard getKeyboardC() {return getKeyboard("Cherry","我是行业标准!"); } } }
或者可能会改成这样
public class Demo {Map<String,Keyboard> keyboardCacheMap = new ConcurrentHashMap<>(); public Keyboard getKeyboard (String name, String desc) {if (keyboardCacheMap.get(name) != null) {return keyboardCacheMap.get(name); } Keyboard keyboard = new Keyboard(name,desc); keyboardCacheMap.put(name,keyboard); return keyboard; } // 客户端 A class ClientA {public Keyboard getKeyboardA() {return getKeyboard("Cherry","我是行业标准!"); } } // 客户端 B class ClientB {public Keyboard getKeyboardB() {return getKeyboard("Razer","游戏不二之选!"); }s } // 客户端 C class ClientC {public Keyboard getKeyboardC() {return getKeyboard("Cherry","我是行业标准!"); } } }
but : 如果此时我的 Cherry 的
desc
属性需要修改呢,我们就需要同时修改ClientA
及ClientC
俩处的代码,还是那句话,如果我们有 10 几 20 多个呢,难不成一个一个改吗,最好的处理方式就是彻底将创建Keyboard
的这一动作彻底和业务代码隔离,如下// 简单工厂 public class SimpleFactory {public static Keyboard getKeyboard (String name) {if (StringUtils.isEmpty(name)) {return null;} Keyboard keyboard = null; if ("cherry".equals(name)) {keyboard = new Keyboard(name,"我是行业标准!"); } else if ("razer".equals(name)) {keyboard = new Keyboard(name,"游戏不二之选!"); } return keyboard; } }
我们将创建
Keyboard
对象的过程放到了简单工厂中,当客户端需要时只需要告诉工厂你需要哪个Keyboard
即可,我们解除了客户端和业务代码之间直接的耦合,改为将客户端与工厂耦合在一起,这样我们在修改Keyboard
的具体属性时只需要修改工厂中的即可看到这里,大家应该都明白了工厂模式所起到的作用
1. 抽取重复创建对象的代码
2. 解除业务代码与客户端之间的耦合
3. 简单工厂满足我们对扩展的需求,它对 扩展开放 , 但是它也对修改开放
但是简单工厂一定是有问题的,不然就不会有后来的工厂方法了
我们可以看到在工厂类中我们用到了大量的
if else
,现在看着没什么问题,如果后续我们需要增加其它操作系统,我们就需要继续加else if
,代码的可读性和维护性就会持续的降低,在后期维护时绝对是灾难,那我们就来看一下工厂方法是怎么解决这个问题的 -
工厂方法
工厂方法模式解决简单工厂问题的方式其实很简单,那就是再次进行细分(抽象),既然将所有的产品都放到了一个工厂中生产会产生对修改开放的问题,那我们就改成每个工厂生产单独的产品,你需要新增产品那就再建一个工厂来生产,不要影响我现在已有的工厂
我们先看代码
场景如下 : 目前有一家工厂主要键盘,cherry 和 razer, 老板为了解决简单工厂的问题,将俩种不同品牌的键盘分别放到了不同的车间去生产
首先是一个产品的抽象类,定义了一个介绍的功能以及介绍语句的属性
public abstract class Keyboard { protected String desc; abstract void introduce();}
具体的俩个产品
public class Cherry extends Keyboard{public Cherry(String desc) {super.desc = desc;} @Override public void introduce() {System.out.println(desc); } } public class Razer extends Keyboard {public Razer(String desc) {super.desc = desc;} @Override void introduce() {System.out.println(desc); } }
工厂的抽象类,将工厂共有的功能抽取出来
public interface KeyboardFactory {Keyboard createKeyboard(); }
具体的工厂(车间),生产不同品牌的电脑
public class CherryFactory implements KeyboardFactory{ @Override public Keyboard createKeyboard() {return new Cherry("我是行业标准!"); } } public class RazerFactory implements KeyboardFactory{ @Override public Keyboard createKeyboard() {return new Razer("游戏不二之选!"); } }
客户端(订单),告诉指挥室我要哪个品牌的电脑,指挥室传达给具体的车间开始生产
public class Demo {public static void main(String[] args) { // 生产 cherry KeyboardFactory factory = new CherryFactory(); Keyboard keyboard = factory.createKeyboard(); keyboard.introduce(); // 生产 raser factory = new RazerFactory(); keyboard = factory.createKeyboard(); keyboard.introduce();} }
我是行业标准!
游戏不二之选!我们将产品的共同特性抽取出来 —> 都是键盘,然后将键盘必须有的属性封装到了
keyboard
类中,为了方便统一控制,后续所有的键盘产品都必须继承这个类此时我们已经解决了简单工厂所存在的问题,虽然这使我们的代码量提升了,但是我们保证了程序对扩展开放,对修改关闭
有的朋友可能会认为对修改开放没什么大问题呀,还方便开发,在前期一定是方便开发是,但是如果你此时的业务代码逻辑十分复杂,在你写完自后我保证你不想再次去改动它了,但是你无法保证以后不需要扩展,在使用简单工厂的情况下你只能去修改工厂中的逻辑代码,结果可能就会有一个接一个的 bug 向你冲来,相信我,那时候你的心情一定会非常暴躁的,所以我们宁愿前期多写一写代码将系统设计的更易扩展且更健壮,也不愿在很久之后去改动一些逻辑复杂的代码
我们总结一下工厂方法
1. 实现了对扩展开放,对修改关闭
2. 对比简单工厂扩展性更强,可读性也较高有的朋友不明白对扩展开放,对修改关闭到底是什么意思,那我在这里单独解释一下
- 对扩展开放 : 此时我们的工厂只是生产 Cherry 的键盘,随着 Cherry 的市场份额逐年增加,为了利润我们要引进 Razer 的生产线,此时我们就又扩展了一条生产线,多了一条盈利路线
我们的系统要允许我们新增新的产品,但是必须要保证产品有共同的特性,你不能说我一个工厂即生产键盘又生产棉花,二者的生产环境是完全不同的 - 对修改关闭 : 我们后期增加 Razer 生产线的时候,要避免影响到 Cherry 的生产,最好的方式就是为 Razer 单独建立一个工厂
我们的系统要禁止新增的逻辑对已有的逻辑产生影响
- 对扩展开放 : 此时我们的工厂只是生产 Cherry 的键盘,随着 Cherry 的市场份额逐年增加,为了利润我们要引进 Razer 的生产线,此时我们就又扩展了一条生产线,多了一条盈利路线
-
-
/ 3 / 结语
那我们在开发中要如何选择简单工厂和工厂方法呢
在 逻辑较简单 ,并且 确信在近期不会添加太多逻辑,那不妨先使用简单工厂来解决问题,可不可以直接使用工厂方法模式呢,可以,但是没必要,对,就是没必要,我们使用设计模式是为了简化我们的开发,而不是为了炫技,你当然可以使用一些比较厉害的方式来解决问题,但我希望你能选择性价比最高的那个,可能有的朋友会说我这不是为了防止以后突然增加一大堆逻辑吗,但是在目前这一大堆逻辑是不存在的,而且在逻辑简单的情况下从简单工厂 → 工厂方法模式的转化非常简单,希望大家不要为了使用设计模式而使用,而是为了解决问题