设计模式之工厂模式:简单工厂&工厂方法&抽象工厂

背景

设计模式有3大类,分为:创建型模式结构型模式行为型模式。工厂模式属于创建型模式,创建型模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

简单工厂

描述:定义一个类用于创建父类相同的子类对象,由传入参数决定创建哪个子类。
简单工厂
举个例子,我喜欢玩游戏。定义一个Game接口,让具体的游戏去实现这个接口

interface Game {
    void play();
}

class HeartStone implements Game {
    @Override
    public void play() {
        System.out.println("炉石传说,启动!");
    }
}

class Gwent implements Game {
    @Override
    public void play() {
        System.out.println("昆特牌,启动!");
    }
}

现在我们要开始玩了

public class SimpleFactory {
    public static void main(String[] args) {
        Game game = new HeartStone();
        game.play();
    }
}

现在假设我们的程序是个大型系统,提供游戏启动的代理功能,一开始我们玩的是炉石传说,因此项目中有很多地方的代码都使用 Game game = new HeartStone(); 这样的语句来启动游戏。
某天,我觉得炉石传说随机性太大了,辣鸡炉石!咱们去玩昆特牌吧!那么问题来了,想更换启动的游戏,我就得将整个系统的每一处Game game = new HeartStone(); 改成 Game game = new Gwent(); 可见工作量是十分庞大的。
简单工厂模式就是为了解决这类问题的:具体玩什么游戏应该是随时变动的需求,不应该在程序中写死具体实例化哪个游戏子类。
现在添加一个游戏工厂类

class GameFactory {
    private static final String HeartStone = "HeartStone";
    private static final String Gwent = "Gwent";

    public static Game playGame(String game) {
        Game myGame = null;
        switch (game) {
            case HeartStone:
                myGame = new HeartStone();
                break;
            case Gwent:
                myGame = new Gwent();
                break;
        }
        return myGame;
    }
}

主程序改为

public class SimpleFactory {
    public static void main(String[] args) {
        Game game = GameFactory.playGame("Gwent");
        game.play();
    }
}

现在,我们想启动什么游戏,只需要改动playGame中的参数,而这个参数是一个字符串变量,这就意味着,我们还可以以配置文件的方式为这个字符串变量赋值,最终做到,不改动任何一处代码,只修改配置文件中的游戏信息,就可以切换具体实例化哪个游戏。

简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断(switch),根据客户端的选择动态实例化相关的 类,对于客户端来说,去除了与具体产品的依赖。

工厂方法

描述:定义一个接口用于创建对象,但是让子类决定初始化哪个类。工厂方法把一个类的初始化下放到子类。
工厂方法
在简单工厂模式中,我们发现在添加子类的时候,相应的也需要在工厂类中添加一个判断分支(多加一个case),是违背了开放-封闭原则的。而工厂方法模式就是主要解决这个问题的。

开放-封闭原则:软件实体(类、模块、函数等)应该可以扩展,但是不可修改。

回到玩游戏的例子,现在我又想玩LOL了,现在我需要添加一个LOL类

class LOL implements Game {
    @Override
    public void play() {
        System.out.println("英雄联盟,启动!");
    }
}

在简单工厂模式下,还需要改动GameFactory的代码,添加一个Case,这样修改了源代码,违背了开闭原则。现在将简单工厂模式改成工厂方法模式,把GameFactory改成接口

interface GameFactory {
    Game playGame();
}

class HeartStoneFactory implements GameFactory {
    @Override
    public Game playGame() {
        return new HeartStone();
    }
}

class GwentFactory implements GameFactory {
    @Override
    public Game playGame() {
        return new Gwent();
    }
}

这样,想添加LOL这个游戏,只需要再添加一个工厂类即可,不用修改代码,而是扩展代码。

class LOLFactory implements GameFactory {
    @Override
    public Game playGame() {
        return new LOL();
    }
}

主程序为

public class FactoryMethod {
    public static void main(String[] args) {
        GameFactory gameFactory = new LOLFactory();
        Game game = gameFactory.playGame();
        game.play();
    }
}

可以看到使用工厂方法模式之后,扩展性变高了,如果想增加一个游戏,只要扩展一个游戏工厂类就可以。但是随之而来的是在系统中增加了复杂度,每增加一个游戏时,都需要增加一个游戏类和工厂类。

抽象工厂

描述:为一个产品族提供了统一的创建接口。当需要这个产品族的某一系列的时候,可以从抽象工厂中选出相应的系列创建一个具体的工厂类。
抽象工厂
抽象工厂模式是一种特殊的工厂方法模式。在上面的玩游戏例子中,游戏工厂接口(GameFactory)的子类,只实例化一种游戏父类(Game)。这时工厂方法模式就可以满足需求。但我们知道,游戏是分为很多种类型的,如MOBA、RPG、TCG等等。
现在我们把Game接口拆分成RPGGame接口和CardGame接口,需求变为:GameFactory的子类可以实例化多种游戏父类了(RPGGame、CardGame)。这时候就要用到抽象工厂模式。

原先我们讨论游戏时,说的是一个很宽泛的概念,我只知道你想玩游戏,不知道你想玩什么类型的游戏。现在给你两个选项:角色扮演游戏和卡牌游戏。有两家知名游戏厂商,波兰蠢驴和暴雪,他们都有角色扮演类游戏和卡牌类游戏。
那么众所周知,蠢驴的RPG游戏有巫师系列,卡牌游戏是昆特牌,暴雪的RPG游戏有魔兽世界,卡牌游戏有炉石传说。这些具体的游戏都叫做产品 而游戏这个大类则是产品族,巫师和昆特牌是蠢驴的产品族;魔兽世界和炉石传说是暴雪的产品族。
抽象工厂模式就是描述它们之间的关系的:将同一类的产品子类归为一类,让他们继承同一个接口,(巫师和魔兽世界都是RPG,让它们都继承RPG接口),然后将不同类的产品归为一族,让不同类的产品都可以被一个工厂子类实例化(魔兽世界和炉石传说是不同类的游戏,但都可以被暴雪公司实例化)。
通过代码直观展示:
游戏类

interface RPGGame {
    void play();
}

interface CardGame {
    void play();
}

class HeartStone implements CardGame {
    @Override
    public void play() {
        System.out.println("炉石传说,启动!");
    }
}

class Gwent implements CardGame {
    @Override
    public void play() {
        System.out.println("昆特牌,启动!");
    }
}

class WOW implements RPGGame {
    @Override
    public void play() {
        System.out.println("魔兽世界,启动!");
    }
}

class Witcher implements RPGGame {
    @Override
    public void play() {
        System.out.println("巫师,启动!");
    }
}

游戏工厂类

interface GameFactory {
    RPGGame playRPGGame();

    CardGame playCardGame();
}

class CDProjektRed implements GameFactory {
    @Override
    public RPGGame playRPGGame() {
        return new Witcher();
    }

    @Override
    public CardGame playCardGame() {
        return new Gwent();
    }
}

class Blizzard implements GameFactory {
    @Override
    public RPGGame playRPGGame() {
        return new WOW();
    }

    @Override
    public CardGame playCardGame() {
        return new HeartStone();
    }
}

主程序

public class AbstractFactory {
    public static void main(String[] args) {
        GameFactory gameFactory1 = new CDProjektRed();
        GameFactory gameFactory2 = new Blizzard();
        RPGGame game1 = gameFactory1.playRPGGame();
        CardGame game2 = gameFactory1.playCardGame();
        RPGGame game3 = gameFactory2.playRPGGame();
        CardGame game4 = gameFactory2.playCardGame();
        game1.play();
        game2.play();
        game3.play();
        game4.play();
    }
}

现在,我们的游戏启动代理系统的功能就被进一步完善了,如果你是蠢驴的舔狗,你只想代理启动蠢驴的游戏,只需要修改一处代码GameFactory gameFactory = new CDProjektRed(); 就可以一键设置为蠢驴游戏全家桶,对于客户端来说,它们并不知道自己会启动哪个厂商的游戏,因为这对它们是透明的,客户端只知道自己启动了RPGGame和CardGame. 如果哪天你又变成暴雪舔狗了,也只需要改动一处代码,客户端就会启动暴雪的游戏族。

参考资料:
https://blog.csdn.net/qazwsxpcm/article/details/81141325
– 《大话设计模式》

发表评论

电子邮件地址不会被公开。 必填项已用*标注