基础加固-工厂模式

   随着工作年限的增加,对工作中的代码结构和质量的追求也在增加。之前看书学习过的设计模式,当时也就是看看,看过或许有的忘记,或许不会使用。入职新公司以来,看团队高级工程师的代码,为了实现一个需求,可以使用合适的设计模式去规范代码,使代码的可读性和可扩展性都大大提升,我意识到是时候巩固一下基础,系统学习一下设计模式了。从这边文章开始,我将从创建型模式中的工厂模式开始复习,实现简单的demo,对比各个模式的UML类图,希望提升自己的编码能力,同时也便于阅读各种开源框架的源码。闲话少许,我们开始吧~

一、简单工厂模式

1.问题引入

   首先来看一个场景,我有一个抽象类Game,标识这个类为一个游戏,游戏可以有很多种,比如“马里奥”、“塞尔达”等等。创建一个具体的游戏,一种最简单的方式就是创建一个具体游戏类继承Game抽象类,然后重写里面的建造方法。代码如下:

Game.java
1
2
3
public abstract class Game {
public abstract void produce();
}

Mario游戏

MarioGame.java
1
2
3
4
5
6
public class MarioGame extends Game{
@Override
public void produce() {
System.out.println("Mario game produced!");
}
}

Zelda游戏

ZeldaGame.java
1
2
3
4
5
6
public class ZeldaGame extends Game {
@Override
public void produce() {
System.out.println("Zelda game produced!");
}
}

这样我们在需要创建某种游戏的时候,直接new对应的具体游戏类,调用其中的建造方法即可。
ZeldaGame.java
1
2
3
4
5
6
public class Test {
public static void main(String[] args) {
Game game = new MarioGame();
game.produce();
}
}

可以看到控制台中打印的结果,创建了Mario游戏
1
2
3
Mario game produced!

Process finished with exit code 0

   看到这里,大家肯定已经发现了这种写法的弊端。每次需要直接new出来想要的具体游戏类,我们如果能使用一个工具去创建我们想要的具体游戏类,不需要关心创建的过程,那岂不是很爽吗?这时候简单工厂模式就登场了。

2.代码实现

   和上面一样,还是有我们的抽象游戏Game类,不同的游戏去继承Game类,实现自己的produce方法。不同的是,这次我们创建一个简单工厂类GameFactory,根据参数通过这个工厂去创建我们需要的具体游戏类。具体代码如下:

GameFactory.java
1
2
3
4
5
6
7
8
9
10
11
public class GameFactory {
public Game getGame(String type){
if ("mario".equalsIgnoreCase(type)){
return new MarioGame();
}else if ("zelda".equalsIgnoreCase(type)){
return new ZeldaGame();
}else {
return null;
}
}
}

这样,我们在使用工厂类创建具体游戏类的时候就不用直接new出来具体的游戏,直接使用工厂类就可以了。
GameFactory.java
1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
GameFactory gameFactory = new GameFactory();
Game game = gameFactory.getGame("zelda");
if (game == null){
return;
}
game.produce();
}
}

结果如下,我们成功创建了zelda游戏
1
2
3
Zelda game produced!

Process finished with exit code 0

3.UML

我们先看一下未引入简单工厂时候的UML类图。

   可以看到我们创建Mario和Zelda两个游戏,都分别通过具体的类进行创建,以后如果要创建更多的游戏,那时的UML大家应该可以想象,从应用方向不同的具体游戏类都会有create联系,整个关系就非常杂乱。我们使用了简单工厂模式之后的UML是什么样的呢?

   这样应用方使用简单工厂创建不同的游戏时,只需要告诉工厂我需要什么游戏即可,就不必关心我应该具体new什么游戏了。
   当然这里实现的简单工厂是最简单的形式,其实完全可以在工厂类中使用java反射创建不同的游戏,在应用方使用的时候,传入对应的Class即可

二、工厂方法模式

1.问题引入

   通过上面简单工厂模式,我们已经可以通过工厂来创建具体的对象。但是这时候我需要增加一个新的“精灵宝可梦”的游戏,我们怎么操作呢?也很简单,新建一个Pokemon游戏的类继承Game,实现自己的produce方法,在工厂类中修改逻辑,使工厂可以创建新的游戏。你可能会说,这样也很好啊,创建新游戏的时候我只需要去改工厂里的逻辑就好了,但是当类越来越多,逻辑越来越复杂,你的工厂类就会变得特别庞大,每次创建新的游戏都要修改这个工厂类,是不符合软件设计中的开闭原则的。这时候,如果按不同游戏的类型把工厂分为不同的工厂,创建的时候只需要使用对应的工厂生产我们需要的游戏就好了。

2.代码实现

   首先我们新创建“精灵宝可梦”游戏类,同样让它继承Game。

PokemonGame.java
1
2
3
4
5
6
public class PokemonGame extends Game {
@Override
public void produce() {
System.out.println("Pokemon game produced!");
}
}

刚刚说到,要按不同游戏的类型创建不同的工厂,这里我们首先把工厂抽象出来,建一个抽象工厂类
GameFactory.java
1
2
3
public abstract class GameFactory {
public abstract Game getGame();
}

之后,通过继承这个抽象工厂类,创建“马里奥”、“塞尔达”、“精灵宝可梦”的工厂
MarioGameFactory.java
1
2
3
4
5
6
public class MarioGameFactory extends GameFactory {
@Override
public Game getGame() {
return new MarioGame();
}
}

ZeldaGameFactory.java
1
2
3
4
5
6
public class ZeldaGameFactory extends GameFactory {
@Override
public Game getGame() {
return new ZeldaGame();
}
}

PokemonGameFactory.java
1
2
3
4
5
6
public class PokemonGameFactory extends GameFactory {
@Override
public Game getGame() {
return new PokemonGame();
}
}

这些不同的工厂创建后,我们就可以使用各自的工厂来生产各自的游戏了,这里以生产Pokemon游戏为例
Test.java
1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
GameFactory gameFactory = new PokemonGameFactory();
Game game = gameFactory.getGame();
game.produce();
}
}

执行代码,控制台输出如下:
1
2
3
Pokemon game produced!

Process finished with exit code 0

我们已经成功将简单工厂升级成了工厂方法模式,提高了代码的可复用性。

3.UML

这时候的UML类图如下:

三、抽象工厂模式

1.问题引入

   假如每个游戏都有一个游戏社区,比如Mario有游戏还有游戏社区,Pokemon也有游戏和游戏社区,这里Mario的游戏和社区属于同一个产品族,Pokemon的游戏和社区也属于同一个产品族;而Mario的游戏社区和Pokemon的游戏社区属于同一产品等级,Mario游戏和Pokemon游戏属于同一产品等级。有类似于这种关系的场景,我们使用一种怎样的模式呢?

2.代码实现

   首先我们创建一个接口,抽象出来一个工厂GameFactory,这个抽象工厂定义了可以获取游戏和游戏社区的方法。

GameFactory.java
1
2
3
4
5
public interface GameFactory {
Game getGame();

Community getCommunity();
}

其中Game和Community是抽象类,具体的游戏和社区需要继承这两个抽象类自己去实现。
Game.java
1
2
3
public abstract class Game {
public abstract void produce();
}

Community.java
1
2
3
public abstract class Community {
public abstract void produce();
}

MarioGame.java
1
2
3
4
5
6
public class MarioGame extends Game {
@Override
public void produce() {
System.out.println("Mario game produced!");
}
}

MarioCommunity.java
1
2
3
4
5
6
public class MarioCommunity extends Community {
@Override
public void produce() {
System.out.println("Mario community produced.");
}
}

之后定义不同的具体工厂,实现GameFactory接口,实现接口中的获取游戏和游戏社区的方法。这里的游戏和社区属于同一产品族,使用具体的工厂可以获得同一产品族的对象。
Mario产品族的工厂
MarioFactory.java
1
2
3
4
5
6
7
8
9
10
11
public class MarioFactory implements GameFactory {
@Override
public Game getGame() {
return new MarioGame();
}

@Override
public Community getCommunity() {
return new MarioCommunity();
}
}

Pokemon产品族的工厂
PokemonFactory.java
1
2
3
4
5
6
7
8
9
10
11
public class PokemonFactory implements GameFactory {
@Override
public Game getGame() {
return new PokemonGame();
}

@Override
public Community getCommunity() {
return new PokemonCommunity();
}
}

我们写一个测试类测试一下。
Test.java
1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) {
//创建抽象工厂,指定是Mario工厂
GameFactory gameFactory = new MarioFactory();
//调用抽象工厂的方法获取游戏和游戏社区
Game game = gameFactory.getGame();
Community community = gameFactory.getCommunity();
//会根据具体工厂的不同获得不同的游戏和游戏社区,这里是Mario产品族
game.produce();
community.produce();
}
}

3.UML

我们来看一下现在的UML类图。

   关系很清晰,大家都可以理解。现在想一个问题,如果我们的业务场景需要经常增加产品族内的产品,那么我们总是需要增加抽象工厂里的方法,进而修改抽象工厂的实现,这么一来就不符合软件设计的开闭原则了;如果我们的业务场景需要经常增加产品等级,这时候我们仅需要添加相应的产品工厂和产品类即可。

   以上介绍了三种工厂相关的模式,不能说哪种模式更优于哪种,只能根据具体的业务场景,选择合适的模式。没有最好的设计模式,只有最适合的设计模式。