秒懂设计模式--学习笔记(4)【创建篇-工厂方法模式】

发布于:2024-07-05 ⋅ 阅读:(40) ⋅ 点赞:(0)

3、工厂方法模式

3.1 介绍
  • 程序设计中的工厂类往往是对对象构造、实例化、初始化过程的封装,而工厂方法(Factory Method)则可以升华为一种设计模式
  • 它对工厂制造方法进行接口规范化,以允许子类工厂决定具体制造哪类产品的实例,最终降低系统耦合,使系统的可维护性、可扩展性等得到提升
  • 测试类文件,下面开始进行示例(空战游戏示例)。
    测试类文件目录
3.2 工厂的多元化与专业化
  • 要制造产品(实例化对象)就得用到关键字“new”,例如“Plane plane = newPlane();”,或许还会有一些复杂的初始化代码(硬编码)
  • 当我们不必关心产品的制造过程(实例化、初始化),而将这个任务交由相应的工厂来全权负责时,工厂最终能交付产品供我们使用即可
  • 工厂内部封装的生产逻辑对外部来说像一个黑盒子,外部不需要关心工厂内部细节,外部类只管调用即可
  • 多元化的生产模式,不同的产品可以交由不同的专业工厂来生产,例如皮鞋由皮鞋工厂来制造,汽车则由汽车工厂来制造,专业化分工明确
3.3 游戏角色建模(建模)
  • 敌人的种类有飞机和坦克:有共同的属性或行为,描述位置状态的坐标,以及一个展示(绘制)方法
  • 使用抽象类Enemy来定义所有敌人的父类
package factory.entity;

/**
 * 敌人抽象类
 **/
public abstract class Enemy {
    // 敌人坐标
    protected int x;
    protected int y;

    /**
     * 构造方法:初始化坐标
     * @param x
     * @param y
     */
    public Enemy(int x, int y) {
        this.x = x;
        this.y = y;
    }

    /**
     * 抽象方法,交给子类去实现:在地图上绘制
     */
    public abstract void show();
}

  • 具体子类实现,也就是敌机类Airplane和坦克类Tank
package factory.entity;

import factory.entity.Enemy;

/**
 * 敌机类:继承了敌人抽象类Enemy
 **/
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("飞机向玩家发起攻击……");
    }
}
package factory.entity;

import factory.entity.Enemy;

/**
 * 坦克类:继承了敌人抽象类Enemy
 **/
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("坦克向玩家发起攻击……");
    }
}
3.4 简单工厂不简单(实例化、初始化)
  • 产品建模完成后,就应该考虑如何实例化和初始化这些敌人了
  • 客户端测试类,new 实例化和初始化敌机类
package factory.simpleFactory;

import factory.entity.Airplane;
import factory.entity.Tank;
import java.util.Random;

/**
 * 客户端测试类
 **/
public class Client {
    public static void main(String[] args) {
          test1_default();
    }

    /**
     * 输出结果:
     *      游戏开始
     *      绘制飞机于上层图层,出现坐标:22,0
     *      飞机向玩家发起攻击……
     *      绘制坦克于下层图层,出现坐标:52,0
     *      坦克向玩家发起攻击……
     */
    private static void test1_default() {
        // 屏幕宽度:假设屏幕宽度为100
        int screenWidth = 100;
        System.out.println("游戏开始");

        // 准备随机数
        Random random = new Random();

        // 生成敌机横坐标随机数:生成一个从0到“屏幕宽度”的随机数
        int x = random.nextInt(screenWidth);
        // 实例化敌机:以此为横坐标构造并初始化
        Airplane airplane = new Airplane(x, 0);
        airplane.show(); // 显示敌机

        // 坦克横坐标随机数
        x = random.nextInt(screenWidth);
        Tank tank = new Tank(x, 0);
        tank.show();

        // 如果我们还有其他敌人也需要构造的话,那么同样的代码就会再次出现
        // 这些实例化逻辑抽离出来作为一个工厂类
    }
}


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

import factory.entity.Airplane;
import factory.entity.Enemy;
import factory.entity.Tank;
import java.util.Random;

/**
 * 简单工厂类
 *    简单工厂类SimpleFactory将之前在客户端类里制造敌人的代码挪过来
 **/
public class SimpleFactory {
    // 屏幕宽度
    private int screenWidth;
    // 随机数
    private Random random;

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

    /**
     * 根据传入类型,创建对应的对象实例
     * @param type
     * @return
     */
    public Enemy create(String type) {
        int i = random.nextInt(screenWidth);
        Enemy en = null;
        switch (type) {
            case "Airplane":
                en = new Airplane(i, 0);
                break;
            case "Tank":
                en = new Tank(i, 0);
                break;
        }
        return en;
    }
}
  • 客户端测试类使用简单工厂
	/**
     * 使用简单工厂:客户端测试类的代码变得异常简单、清爽
     * 然而,这个简单工厂的确很“简单”,但并不涉及任何的模式设计范畴。制造逻辑只是被换了个地方
     */
    private static void test2_simpleFactory() {
        System.out.println("游戏开始");
        SimpleFactory simpleFactory = new SimpleFactory(100);
        simpleFactory.create("Airplane").show();
        simpleFactory.create("Tank").show();
    }

  • 简单工厂类SimpleFactory将之前在客户端测试类里制造敌人的代码挪过来
    • 如此一来,制造敌人这个任务就全权交由【简单工厂】来负责了,于是客户端便可以直接从简单工厂取用敌人了
    • 虽然客户端中不再直接出现对产品实例化的代码,但羊毛出在羊身上,制造逻辑只是被换了个地方,挪到了简单工厂中而已
    • 并且客户端还要告知产品种类才能产出,这无疑是另一种意义上的耦合
3.5 制定工业制造标准
  • 针对复杂多变的生产需求,将简单工厂的制造方法进行拆分,构建起抽象化、多态化的生产模式
  • 对各种各样的生产方式(工厂方法)进行抽象,首先定义一个工厂接口,以确立统一的工业制造标准
  • 工厂接口: 确立统一标准,是工厂方法模式的核心
package factory.factoryMethod;

import factory.entity.Enemy;

/**
 * 工厂接口: 确立统一标准,是工厂方法模式的核心
 **/
public interface Factory {
    /**
     * 声明了标准,传入屏幕宽度,任何工厂都应遵循此接口
     * @param screenWidth
     * @return
     */
    Enemy createEnemy(int screenWidth);
}

  • 实现工厂类,关键字implements实现工厂接口Factory
    • 飞机工厂制造飞机
    • 坦克工厂制造坦克
    • 如果需要扩展:基于此模式继续我们的扩展即可: 创建工厂类,同样实现工厂方法接口
      • 关底Boss
package factory.factoryMethod;

import factory.entity.Airplane;
import factory.entity.Enemy;
import java.util.Random;

/**
*  飞机工厂类:以关键字implements声明了本类是实现工厂接口Factory的工厂实现类
**/
public class AirplaneFactory implements Factory{

    /**
     * 具体实现: 飞机工厂制造飞机
     * @param screenWidth
     * @return
     */
    @Override
    public Enemy createEnemy(int screenWidth) {
        Random random = new Random();
        return new Airplane(random.nextInt(screenWidth), 0);
    }
}
package factory.factoryMethod;

import factory.entity.Enemy;
import factory.entity.Tank;

import java.util.Random;

/**
 * 坦克工厂类: 以关键字implements声明了本类是实现工厂接口Factory的工厂实现类
 **/
public class TankFactory implements Factory{

    /**
     * 具体实现:坦克工厂制造坦克
     * @param screenWidth
     * @return
     */
    @Override
    public Enemy createEnemy(int screenWidth) {
        Random random = new Random();
        return new Tank(random.nextInt(screenWidth), 0);
    }
}

package factory.factoryMethod;

import factory.entity.Boss;
import factory.entity.Enemy;

/**
 * 关底BOSS工厂类
 **/
public class BossFactory implements Factory{
    @Override
    public Enemy createEnemy(int screenWidth) {
        // 让Boss出现在屏幕中央
        return new Boss(screenWidth/2, 0);
    }
}
  • 每个工厂的生产策略或方式都具备自己的产品特色,不同的产品需求都能找到相应的工厂来满足,即便没有,我们也可以添加新工厂来解决
  • 使系统具有良好的兼容性和可扩展性
3.6劳动分工
  • 只需添加相应的工厂类,无须再对现有代码做任何更改
  • 工厂方法模式可以被看作由简单工厂演化而来的高级版,后者才是真正的设计模式
  • 在工厂方法模式中,不仅产品需要分类,工厂同样需要分类
  • 工厂方法模式的各角色定义
    • Product(产品):所有产品的顶级父类,可以是抽象类或者接口。如:Enemy
    • ConcreteProduct(子产品):由产品类Product派生出的产品子类,可以有多个产品子类。如:Airplane、Tank、Boss
    • Factory(工厂接口):定义工厂方法的工厂接口,当然也可以是抽象类它使顶级工厂制造方法抽象化、标准统一化。如:Factory
    • ConcreteFactory(工厂实现):实现了工厂接口的工厂实现类,并决定工厂方法中具体返回哪种产品子类的实例。如:AirplaneFactory、AirplaneFactory、BossFactory
  • 明确合理的劳动分工才能真正地促进生产效率的提升

网站公告

今日签到

点亮在社区的每一天
去签到