工厂模式进化之路(从简单工厂到工厂方法再到抽象工厂)
要弄懂一个设计模式我认为不是去看这个模式的定义,应该去了解它解决了什么问题。使用它可以给我带来什么好处。下面从场景开始切入,使用代码进行分析、解析。深入了解工厂模式的进化过程,真正明白,工厂的各个模式的意义。
场景体验
1、项目初期
联想电脑。使用键盘
此时只有“【自带键盘】一种键盘”
class Lianxiang{
...
private void useKeyboard(){
...
自带键盘 键盘实例 = new 自带键盘();
...
}
...
}
2、增加电脑种类
戴尔电脑,小米电脑,华为电脑,惠普电脑,苹果电脑,宏基电脑
class Daier{
...
private void useKeyboard(){
...
自带键盘 键盘实例 = new 自带键盘();
...
}
...
}
class Xiaomi{
...
private void useKeyboard(){
...
自带键盘 键盘实例 = new 自带键盘();
...
}
...
}
...等等各种电脑。
3、更换键盘
新需求:自带键盘有缺陷,全部更换为“自带键盘plus”
此时需要将所有电脑的使用键盘方法中的创建语句更改为:
自带键盘plus 键盘实例 = new 自带键盘plus();
更改位置:7个;每次更换键盘都是更改7个,而且因为实例类型发生变化所以下面代码也有可能需要连带改动。如果增加电脑种类则每次需要更改的位置会更多。键盘创建过程与客户端强耦合。
4、扩展键盘
新需求:小米、苹果、华为。联合推出了个性键盘:“联创键盘” 并在自家电脑开始使用。
此时需要针对上述三家电脑的键盘的创建进行改动。
class Xiaomi{
...
private void useKeyboard(){
...
联创键盘 键盘实例 = new 联创键盘();
...
}
...
}
...等另外两家电脑。
更改位置:3个;但是之后每个电脑都有可能会使用自己推出的键盘。改动量将会很大。
问题:使用现有代码完全可以应对上述需求,但是会导致在后面的维护过程中,每次的需求都需要进行大量重复的改动。增加维护难度。
简单工厂模式
未解决上述 “键盘创建过程与客户端强耦合” 的问题,推出简单工厂模式。
定义
简单工厂模式又称为静态工厂模式,实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。
解决“场景体验”中的问题
事前分析
事前分析:电脑用到了键盘,尽管现在键盘只有一种自带键盘,但是未来可能会出现各种键盘。所以将键盘抽象出来。再创建一个工厂类,用来创建并返回具体的工厂类。
//键盘父类
class Keyboard{
...
}
class 自带键盘 extends Keyboard {
...
}
class Factory{
public Keyboard createKeyboard(String type){
Keyboard 键盘实例;
switch(type){
case "自带键盘":
键盘实例 = new 自带键盘();
break;
}
return 键盘实例;
}
}
场景1、2
场景1、2中useKeyboard中的创建语句:
自带键盘 键盘实例 = new 自带键盘();
改为:
Factory factory = new Factory();
Keyboard 键盘实例 = factory.createKeyboard("自带键盘");
/**
* “自带键盘”这个标识还可以由更上层去传递
*/
private void useKeyboard(String type){
...
Factory factory = new Factory();
Keyboard 键盘实例 = factory.createKeyboard(type);
...
}
自此实现了实例创建过程与客户端代码解耦的目的。
场景3
只需要增加自带键盘plus类,在工厂中增加case。
class 自带键盘plus extends Keyboard{
...
}
class Factory{
public Keyboard createKeyboard(String type){
Keyboard 键盘实例;
switch(type){
case "自带键盘":
键盘实例 = new 自带键盘();
break;
case "自带键盘plus":
键盘实例 = new 自带键盘plus();
break;
}
return 键盘实例;
}
}
改动:1处;
场景4
增加联创键盘类,在工厂中增加case。
class 联创键盘 extends Keyboard{
...
}
class Factory{
public Keyboard createKeyboard(String type){
Keyboard 键盘实例;
switch(type){
case "自带键盘":
键盘实例 = new 自带键盘();
break;
case "自带键盘plus":
键盘实例 = new 自带键盘plus();
break;
case "联创键盘":
键盘实例 = new 联创键盘();
break;
}
return 键盘实例;
}
}
改动:1处。(改动字符串不算改动,因为对代码逻辑没有真正的改动,而且可以通过传参,或者定义类全局变量的方式解决。)
简单工厂模式的优点
- 只需要传入 正确的参数 , 就可以 获取需要的对象 , 无需知道创建细节 ;
- 工厂类中可以有必要的 判断逻辑 , 可以决定 根据当前的参数 创建对应的产品实例 , 客户端可以免除直接创建产品对象的责任 ;
- 通过该模式 , 实现了对创建实例 和使用实例 的责任分割 ;
- 通过工厂类实现对象创建的统一管理,提高代码的可维护性;
简单工厂模式的缺点
- 工厂类职责过重 , 当工厂类负责创建的实例过多的时候也会导致维护困难。
- 如果要增加新的产品 , 需要修改工厂类的判断逻辑 , 违背 " 开闭原则 " ;
理解了简单工厂模式后发现它有一定的缺点,为了解决它的缺点诞生了工厂方法模式
工厂方法模式
问题分析
简单工厂模式缺点的产生是由于工厂方法中聚集了所有对象的实例化,所以导致该方法会不断变大,变得难以维护;也在新增或修改时违背了开闭原则。
解决方案/定义
我们需要将工厂方法拆开,但是又需要他可以统一返回类型,以保证在变化时不影响实例化后的逻辑。做法:将工厂类抽象出来成为一个工厂接口。具体工厂类实现工厂接口。
/**
* 抽象键盘工厂
*/
interface Factory{
Keyboard createKeyboard();
}
/**
* 戴尔键盘工厂
*/
class DEKeyboardFactory implements Factory{
@Override
public Keyboard createKeyboard(){
return new 自带键盘();
}
}
/**
* 小米键盘工厂
*/
class XMKeyboardFactory implements Factory{
@Override
public Keyboard createKeyboard(){
return new 联创键盘();
}
}
//小米类中使用 联创键盘:
class Xiaomi{
...
private void useKeyboard(){
...
Factory factory = new XMKeyboardFactory();
Keyboard 键盘实例 = factory.createKeyboard();
...
}
...
}
//戴尔类中使用 自带键盘:
class Daier{
...
private void useKeyboard(){
...
Factory factory = new DEKeyboardFactory();
Keyboard 键盘实例 = factory.createKeyboard();
...
}
...
}
/**
* 如果小米电脑要改回自带键盘
* 只需要修改工厂类返回的具体键盘即可。
* 小米键盘工厂
*/
class XMKeyboardFactory implements Factory{
@Override
public Keyboard createKeyboard(){
return new 自带键盘();
}
}
对于客户端来说,它根本感知不到返回实例的变化。
优点
自此,如果需要新增一个键盘,需要创建一个类(新键盘类)修改键盘工厂类。在使用时修改具体工厂类即可。解决了简单工厂的两个问题,同时继承了简单工厂的优点。
缺点
- 随着业务增加,类会变得越来越多(类数量的增长比较快)。
- 对于一些形成产品族的情况处理比较复杂。(继续看抽象工厂就会明白)
抽象工厂模式
目的解决:工厂方法模式的产品族问题。
新需求:上面都是笔记本原生键盘,此时每个品牌的外接机械键盘的设计师也发现工厂模式是一个不错的选择,决定将每个品牌机械键盘的创建也交给你的工厂。
实现
所以你需要在工厂的抽象接口中增加创建机械键盘的方法,并让实现它的类实现该方法。
代码改动:
interface Factory{
Keyboard createKeyboard();
MechanicalKeyboard createMechanicalKeyboard();
}
class MechanicalKeyboard{
...
}
//小米机械键盘
class XMMechanicalKeyboard extends MechanicalKeyboard{
...
}
//华为机械键盘
class HWMechanicalKeyboard extends MechanicalKeyboard{
...
}
...等其他品牌
//小米机械键盘的工厂类
class XMKeyboardFactory implements Factory{
public Keyboard createKeyboard(){
return new 联创键盘();
}
public MechanicalKeyboard createMechanicalKeyboard(){
return new XMMechanicalKeyboard();
}
}
//华为机械键盘的工厂类
class HWKeyboardFactory implements Factory{
public Keyboard createKeyboard(){
return new 联创键盘();
}
public MechanicalKeyboard createMechanicalKeyboard(){
return new HWMechanicalKeyboard();
}
}
...等其他品牌的工厂
客户端代码:
//小米类中使用 机械键盘:
class xiaomi{
...
private void useKeyboard(){
...
Factory factory = new XMMechanicalKeyboardFactory();
//创建机械键盘实例
MechanicalKeyboard 机械键盘实例 = factory.createMechanicalKeyboard();
...
//创建自带键盘实例
Keyboard keyboard = factory.createKeyboard();
...
}
...
}
自此,在使用时,就优化了品类的扩展。
优点:
- 最大的好处是:易于交换产品系列,因为具体的工厂类只在客户端中只有初始化的时候出现一次,所有要改变具体工厂就很简单(只需要改一个地方)。
- 使具体的实例创建的过程与客户端分离,通过接口连接。产品的具体类名也不会出现在客户端代码中。
缺点:
- 要是再增加一种蓝牙键盘,就需要再增加蓝牙键盘类、各个品牌的蓝牙键盘类。修改各个品牌蓝牙键盘的工厂类、工厂接口。
- 因为这个初始化的两句代码会在多个客户端中使用,所以如果要改就需要在每个客户端中都要修改。
所以:在进行增加系列时,会增加和修改很多类。很是繁琐。而且修改时也可能要修改多个客户端。
看到这里是不是很多人觉得:工厂模式进化到此,还不如用简单工厂呢。简单工厂实现了解耦,工厂方法满足了:“对扩展开放,对修改关闭”,抽象工厂又完成了对产品族的优化。但是进化到抽象工厂后,要扩展一下发现:类的增量却更大了,要修改的地方变多了,我的工作量变大了。
要想解答上面的问题,就要弄明白:设计模式的出现是着眼于未来的,并不全在当下。所以就导致了我们感觉对于我的短期任务来说,改起来了好像更费劲了。
下集预告
尽管抽象工厂带给我们的利益是长远的,但是短期它让我们不开心了,那怎么行。而且编程是一门艺术,像上面那样大批量的改动,怎么能算是艺术呢?对吧。那我们肯定要解决上面的问题。
如何解决?提醒一下,任何优秀的定义都要和现实结合才能有意义,否则就是空中楼阁。
请看下集:
工厂模式提升之路(无痴迷,不成功)