设计模式 - 工厂方法
工厂方法是一种设计模式,对工厂制造方法进行接口规范化,允许子类工厂决定具体知道哪类产品的实例,最终降低系统耦合,使系统的可维护性、可扩展性等得到提升。
一、工厂的多元化与专业化
要实例化对象,就得用到关键词“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();
}
Tips:真实的游戏长江不止这么简单,敌机绘图线程会在下一帧擦除画板并重绘到下一个坐标实现动画效果,敌人抽象类可能还会有move、attack、die等方法,本文先忽略这些细节。
2. 敌机类 Airplane
public class Airplane extends Enemy {public Airplane(int x, int y){super(x, y); // 调用父类构造方法初始化坐标}@Overridepublic void show() {System.out.println("绘制飞机于上层图层,出现坐标: " + x + "," + y);System.out.println("飞机向玩家发起攻击……");}
3. 坦克类 Tank
public class Tank extends Enemy {public Tank(int x, int y){super(x, y); // 调用父类构造方法初始化坐标}@Overridepublic void show() {System.out.println("绘制坦克于下层图层,出现坐标: " + x + "," + y);System.out.println("坦克向玩家发起攻击……");}
}
三、简单工厂不简单
产品建模完整后,就该考虑如何实例化和初始化这些敌人了。要使他们都出现在屏幕最上方,就要使纵坐标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 airplan = new Airplane(x, 0); // 实例化飞机airplan.show(); // 显示飞机x = random.nextInt(screenWidth); // 坦克同上Enemy tank = new Tank(x, 0);tank.show();}/* 输出结果:游戏开始飞机出现坐标: 94,0飞机向玩家发起攻击……坦克出现坐标: 89,0坦克向玩家发起攻击……*/
}
然而,制造敌机出现的动作,不应该出现在客户端类中。
如果还有很多种敌人,同样的代码会再次出现,尤其是初始化越复杂的时候,重复代码就越多。
如此耗时费力,何不把这些实例化逻辑抽离出来一个工厂类?
按照这个思路,我们来开发一个制造敌人的简单工厂类:
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;}
}
简单工厂类SImpleFactory将之前在客户端类里制造敌人的代码挪过来,并封装在第10行的制造方法create方法中,这里我们加了一些逻辑判断,使其可以根据传入的敌机种类(飞机或坦克)生产出相应的对象实例,并随机初始化其位置。
如此以来,制造敌人这个任务就全权交由简单工厂来负责了,客户端可以直接从简单工厂取用敌人了
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();}
}
如代码所示,客户端类的代码变得异常简单、清爽。
这就是分类封装、各司其职的好处。
虽然客户端中不再直接出现对产品实例化的代码,但羊毛出在羊身上,制造逻辑只是被换了一个地方,挪到了简单工厂中而已,并且客户端还要告知产品种类才能产出,这无疑是另一种意义上的耦合。
除此之外,简单工厂一定要保持简单,否则就不要用简单工厂。
随着游戏项目需求的演变,简单工厂的可扩展性也会变得很差。
例如,对于产品种类的判断逻辑,如果有新的敌人类加入,我们就需要再修改简单工厂。随着生产方式不断多元化,工厂类就得不断反复修改,严重缺乏灵活性与可扩展性,尤其对于一些庞大复杂的系统。大量产品逻辑堆积在制造方法中,看起来好像功能强大、无所不能,实际上维护起来举步维艰,简单工厂就会一点也不简单了。
四、制定工业制造标准
系统中并不是处处都需要调用这样一个万能的“简单工厂”,有时系统只是需要一个坦克对象,所以不必这样大动干戈使用一个这样臃肿的“简单工厂”。
针对复杂多变的生产需求,我们需要对产品制造的相关代码进行合理规划与分类,将简单工厂的制造方法进行拆分,构建起抽象化、多态化的生产模式。下面我们就对各种各样的生产方式(工厂方法)进行抽象。
首先定义一个工厂接口,以确立统一的工业制造标准
public interface Factory {Enemy create(int screenWidth);}
接下来重构一下之前的简单工厂类:
1. 飞机工厂类 AirplaneFactory
public class AirplaneFactory implements Factory {@Overridepublic Enemy create(int screenWidth) {Random random = new Random();return new Airplane(random.nextInt(screenWidth), 0);}
}
2. 坦克工厂类 TankFactory
public class TankFactory implements Factory {@Overridepublic Enemy create(int screenWidth) {Random random = new Random();return new Tank(random.nextInt(screenWidth), 0);}
}
飞机工厂类与坦克工厂类的代码简洁、明了。
都以关键字implements声明了本类是实现工厂接口Factory的工厂实现类,并且在第4行给出了工厂方法create()的具体实现,其中飞机工厂制造飞机,坦克工厂制造坦克,各自有其独特的生产方式。
关键字implements声明了本类是实现工厂接口Factory的工厂实现类,并且在第4行给出了工厂方法create()的具体实现,其中飞机工厂制造飞机,坦克工厂制造坦克,各自有其独特的生产方式。
除了飞机和坦克,应该还会有其他的敌人,当玩家抵达游戏关底时总会有Boss出现,这时候我们该如何扩展呢?显而易见,基于此模式继续我们的扩展即可,先定义一个继承自敌人抽象类Enemy的Boss类,相应地还有Boss的工厂类,同样实现工厂方法接口,请分别参看代码清单
1. 关底Boss类:
// 代码清单 4-10 关底 Boss 类 Boss
public class Boss extends Enemy {public Boss(int x, int y){super(x, y);}@Overridepublic void show() {System.out.println("Boss出现坐标: " + x + "," + y);System.out.println("Boss向玩家发起攻击……");}
}
2. 关底Boss工厂类
// 代码清单 4-11 关底 Boss 工厂类 BossFactory
public class BossFactory implements Factory {@Overridepublic Enemy create(int screenWidth) {// 让Boss出现在屏幕中央return new Boss(screenWidth / 2, 0);}
}
显而易见,多态化后的工厂多样性不言而喻,每个工厂的生产策略或方式都具备自己的产品特色,不同的产品需求都能找到相应的工厂来满足。
之后若要加入新的敌人类,只需添加相应的工厂类,无须再对现有代码做任何更改。
-- 秒懂设计模式学习笔记
-- 工厂模式