创建篇-工厂模式


前言

制造业是一个国家工业经济发展的重要支柱,而工厂则是其根基所在。程序设计中的工厂类往往是对对象构造、实例化、初始化过程的封装,而工厂方法(Factory Method)则可以升华为一种设计模式,它对工厂制造方法进行接口规范化,以允许子类工厂决定具体制造哪类产品的实例,最终降低系统耦合,使系统的可维护性、可扩展性等得到提升。


提示:以下是本篇文章正文内容,下面案例可供参考

一、工厂的多元化与专业化

要理解工厂方法模式,我们还得从头说起。众所周知,要制造产品(实例化对象)就得用到关键字“new”,例如“Plane plane=new Plane(); ”,或许还会有一些复杂的初始化代码,这就是我们常用的传统构造方式。然而这样做的结果会使飞机对象的产生代码被牢牢地硬编码在客户端类里,也就是说客户端与实例化过程强耦合了。而事实上,我们完全不必关心产品的制造过程(实例化、初始化),而将这个任务交由相应的工厂来全权负责,工厂最终能交付产品供我们使用即可,如此我们便摆脱了产品生产方式的束缚,实现了与制造过程彻底解耦。

除此之外,工厂方法模式是基于多元化产品的构造方法发展而来的,它开辟了产品多元化的生产模式,不同的产品可以交由不同的专业工厂来生产,例如皮鞋由皮鞋工厂来制造,汽车则由汽车工厂来制造,专业化分工明确。

二、游戏角色建模

在制造产品之前,我们先得为它们建模。我们依旧以空战游戏来举例,通常这类游戏中主角飞机都拥有强大的武器装备,以应对敌众我寡的游戏局面,所以敌人的种类就应当多样化,以带给玩家更加丰富多样的游戏体验。于是我们增加了一些敌机、坦克。

1. 敌人抽象类

public abstract class Enemy {
    //敌人的坐标
    protected  int x;
    protected  int y;
    
    //初始化坐标
    public Enemy(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    //抽象方法,在地图上绘制
    public abstract void show();
}

2.敌机类

public class AirPlane extends Enemy {

    public AirPlane(int x, int y) {
        super(x, y);
    }

    @Override
    public void show() {
        System.out.println("绘制飞机于上层图层,出现坐标" + x + "," + y);
        System.out.println("飞机向玩家发起攻击......");
    }
}

3.坦克类

public class Tank extends Enemy {

    public Tank(int x, int y) {
        super(x, y);
    }

    @Override
    public void show() {
        System.out.println("绘制坦克于下层图层,出现坐标" + x + "," + y);
        System.out.println("坦克向玩家发起攻击......");
    }
}

说明:

  1. 飞机类Airplane和坦克类Tank都继承了敌人抽象类Enemy,并且分别实现了各自独特的展示方法show()

三、简单工厂

1.客户端

产品建模完成后,就应该考虑如何实例化和初始化这些敌人了。毋庸置疑,要使它们都出现在屏幕最上方,就得使其纵坐标y被初始化为0,而对于横坐标x该怎样初始化呢?如果让敌人出现于屏幕正中央的话,就得将其横坐标初始化为屏幕宽度的一半,显然,如此玩家只需要一直对准屏幕中央射击,这对游戏可玩性来说是非常糟糕的,所以我们最好让敌人的横坐标随机产生,这样才能给玩家带来更好的游戏体验。

public class Client {
    public static void main(String[] args) {
        int screenWidth = 100;//屏幕宽度
        System.out.println("游戏开始");
        Random random = new Random();
        int x = random.nextInt(screenWidth);
        Enemy airPlane = new AirPlane(x, 0);//实例化飞机
        airPlane.show();

        x = random.nextInt(screenWidth);//坦克同上
        Enemy tank = new Tank(x, 0);
        tank.show();
    }
}
输出结果:
游戏开始
绘制飞机于上层图层,出现坐标67,0
飞机向玩家发起攻击......
绘制坦克于下层图层,出现坐标11,0
坦克向玩家发起攻击......

2. 简单工厂类

制造随机出现的敌人这个动作貌似不应该出现在客户端类中,试想如果我们还有其他敌人也需要构造的话,那么同样的代码就会再次出现,尤其是当初始化越复杂的时候重复代码就会越多。如此耗时费力,何不把这些实例化逻辑抽离出来作为一个工厂类?沿着这个思路,我们来开发一个制造敌人的简单工厂类。

public class SimpleFactory {
    private int    screenWidth;
    private Random random;

    public SimpleFactory(int screenWidth) {
        this.screenWidth = screenWidth;
        this.random = new Random();
    }

    public Enemy create(String type) {
        int x = random.nextInt(screenWidth);
        Enemy enemy = null;
        switch (type) {
            case "AirPlane":
                enemy = new AirPlane(x, 0);//实例化飞机
                break;
            case "Tank":
                enemy = new Tank(x, 0);//实例化坦克
                break;
        }
        return enemy;
    }
}

3.改写客户端类

public class Client {

    public static void main(String[] args) {
        System.out.println("游戏开始");
        SimpleFactory factory = new SimpleFactory(100);
        factory.create("AirPlane").show();
        factory.create("Tank").show();
    }
}
输出结果:
游戏开始
绘制飞机于上层图层,出现坐标63,0
飞机向玩家发起攻击......
绘制坦克于下层图层,出现坐标45,0
坦克向玩家发起攻击......

说明:

  1. 客户端类的代码变得异常简单、清爽,这就是分类封装、各司其职的好处。然而,这个简单工厂的确很“简单”,但并不涉及任何的模式设计范畴,虽然客户端中不再直接出现对产品实例化的代码,但羊毛出在羊身上,制造逻辑只是被换了个地方,挪到了简单工厂中而已,并且客户端还要告知产品种类才能产出,这无疑是另一种意义上的耦合。
  2. 除此之外,简单工厂一定要保持简单,否则就不要用简单工厂。随着游戏项目需求的演变,简单工厂的可扩展性也会变得很差,例如对于那段对产品种类的判断逻辑,如果有新的敌人类加入,我们就需要再修改简单工厂。随着生产方式不断多元化,工厂类就得被不断地反复修改,严重缺乏灵活性与可扩展性,尤其是对于一些庞大复杂的系统,大量的产品判断逻辑代码会被堆积在制造方法中,看起来好像功能强大、无所不能,其实维护起来举步维艰,简单工厂就会变得一点也不简单了。

四、制定工业制造标准

其实系统中并不是处处都需要调用这样一个万能的“简单工厂”,有时系统只需要一个坦克对象,所以我们不必大动干戈使用这样一个臃肿的“简单工厂”。另外,由于用户需求的多变,我们又不得不生成大量代码,这正是我们要调和的矛盾。

针对复杂多变的生产需求,我们需要对产品制造的相关代码进行合理规划与分类,将简单工厂的制造方法进行拆分,构建起抽象化、多态化的生产模式。下面我们就对各种各样的生产方式(工厂方法)进行抽象,首先定义一个工厂接口,以确立统一的工业制造标准。

1. 工厂接口

工厂接口Factory其实就是工厂方法模式的核心了。我们在接口中声明了工业制造标准,只要传入屏幕宽度,就在屏幕坐标内产出一个敌人实例,任何工厂都应遵循此接口。

public interface Factory {
    Enemy create(int screenWidth);
}

2.产品拆分

我们重构一下之前的简单工厂类,将其按产品种类拆分为两个类。

//飞机工厂类
public class AirPlaneFactory implements Factory{
    @Override
    public Enemy create(int screenWidth) {
        Random random= new Random();
        return new AirPlane(random.nextInt(screenWidth), 0);
}
    
//坦克工厂类
public class TankFactory implements Factory{
    @Override
    public Enemy create(int screenWidth) {
        Random random= new Random();
        return new Tank(random.nextInt(screenWidth), 0);
    }
}

3.加入Boss类

除了飞机和坦克,应该还会有其他的敌人,当玩家抵达游戏关底时总会有Boss出现,这时候我们该如何扩展呢?显而易见,基于此模式继续我们的扩展即可,先定义一个继承自敌人抽象类Enemy的Boss类,相应地还有Boss的工厂类,同样实现工厂方法接口。

//Boss类
public class Boss extends Enemy {

    public Boss(int x, int y) {
        super(x, y);
    }

    @Override
    public void show() {
        System.out.println("Boss出现坐标" + x + "," + y);
        System.out.println("Boss出现坐标向玩家发起攻击......");
    }
}

//Boss工厂类
public class BossFactory implements Factory{
    @Override
    public Enemy create(int screenWidth) {
        //让Boss出现在屏幕中央
        return new Boss(screenWidth / 2, 0);
    }
}

4.改写客户端类

public class Client {

    public static void main(String[] args) {
        int screenWidth = 100;
        System.out.println("游戏开始");

        Factory factory = new TankFactory();
        for (int i = 0; i < 5; i++) {
            factory.create(screenWidth).show();
        }

        factory = new AirPlaneFactory();
        for (int i = 0; i < 5; i++) {
            factory.create(screenWidth).show();
        }

        System.out.println("抵达关底");
        factory = new BossFactory();
        factory.create(screenWidth).show();
    }
}
输出结果:
游戏开始
绘制坦克于下层图层,出现坐标76,0
坦克向玩家发起攻击......
绘制坦克于下层图层,出现坐标81,0
坦克向玩家发起攻击......
绘制坦克于下层图层,出现坐标7,0
坦克向玩家发起攻击......
绘制坦克于下层图层,出现坐标6,0
坦克向玩家发起攻击......
绘制坦克于下层图层,出现坐标73,0
坦克向玩家发起攻击......
绘制飞机于上层图层,出现坐标82,0
飞机向玩家发起攻击......
绘制飞机于上层图层,出现坐标38,0
飞机向玩家发起攻击......
绘制飞机于上层图层,出现坐标79,0
飞机向玩家发起攻击......
绘制飞机于上层图层,出现坐标24,0
飞机向玩家发起攻击......
绘制飞机于上层图层,出现坐标6,0
飞机向玩家发起攻击......
抵达关底
Boss出现坐标50,0
Boss出现坐标向玩家发起攻击......

说明:

  1. 显而易见,多态化后的工厂多样性不言而喻,每个工厂的生产策略或方式都具备自己的产品特色,不同的产品需求都能找到相应的工厂来满足,即便没有,我们也可以添加新工厂来解决,以确保游戏系统具有良好的兼容性和可扩展性。

总结

提示:这里对文章进行总结:

  1. 工厂方法模式的各角色定义如下。
  • Product(产品):所有产品的顶级父类,可以是抽象类或者接口。对应本章例程中的敌人抽象类。
  • ConcreteProduct(子产品):由产品类Product派生出的产品子类,可以有多个产品子类。对应本章例程中的飞机类、坦克类以及关底Boss类。
  • Factory(工厂接口):定义工厂方法的工厂接口,当然也可以是抽象类,它使顶级工厂制造方法抽象化、标准统一化。
  • ConcreteFactory(工厂实现):实现了工厂接口的工厂实现类,并决定工厂方法中具体返回哪种产品子类的实例。
  1. 工厂方法模式不但能将客户端与敌人的实例化过程彻底解耦,抽象化、多态化后的工厂还能让我们更自由灵活地制造出独特而多样的产品。其实工厂不必万能,方便面工厂不必生产汽车,手机工厂也不必生产牛仔裤,否则就会通而不精,妄想兼备所有产品线的工厂并不是好的工厂。反之,每个工厂都应围绕各自的产品进行生产,专注于自己的产品开发,沿用这种分工明确的工厂模式才能使各产业变得越来越专业化,而不至于造成代码逻辑泛滥,从而降低产出效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhixuChen200

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值