制造业是一个国家工业经济发展的重要支柱,而工厂则是其根基所在。程序设计中的工厂类往往是对对象构造、实例化、初始化过程的封装,而工厂方法(Factory Method)则可以升华为一种设计模式,它对工厂制造方法进行接口规范化,以允许子类工厂决定具体制造哪类产品的实例,最终降低系统耦合,使系统的可维护性、可扩展性等得到提升。
要理解工厂方法模式,我们还得从头说起。众所周知,要制造产品(实例化对象)就得用到关键字“new”,例如“Plane plane = new Plane();”,或许还会有一些复杂的初始化代码,这就是我们常用的传统构造方式。然而这样做的结果会使飞机对象的产生代码被牢牢地硬编码在客户端类里,也就是说客户端与实例化过程强耦合了。而事实上,我们完全不必关心产品的制造过程(实例化、初始化),而将这个任务交由相应的工厂来全权负责,工厂最终能交付产品供我们使用即可,如此我们便摆脱了产品生产方式的束缚,实现了与制造过程彻底解耦。
除此之外,工厂方法模式是基于多元化产品的构造方法发展而来的,它开辟了产品多元化的生产模式,不同的产品可以交由不同的专业工厂来生产,例如皮鞋由皮鞋工厂来制造,汽车则由汽车工厂来制造,专业化分工明确
在制造产品之前,我们先得为它们建模。我们依旧以空战游戏来举例,通常这类游戏中主角飞机都拥有强大的武器装备,以应对敌众我寡的游戏局面,所以敌人的种类就应当多样化,以带给玩家更加丰富多样的游戏体验。于是我们增加了一些敌机、坦克
游戏中敌人的种类有飞机和坦克,虽然它们之间的区别比较大,但总有一些共同的属性或行为,例如一对用来描述位置状态的坐标,以及一个展示(绘制)方法,以便将自己绘制到相应的地图位置上。好了,现在我们使用抽象类来定义所有敌人的父类
Java
package factory;
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();
}
真正的游戏不止这么简单,敌机绘图线程会在下一帧擦除画板并重绘到下一个坐标以实现动画效果,敌人抽象类可能还会有move()(移动)、attack()(攻击)、die()(死亡)等方法,我们忽略这些细节
Airplane
Java
package factory;
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("飞机向玩家发起攻击......");
}
}
Tank
Java
package factory;
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("坦克向玩家发起攻击......");
}
}
简单工厂
Java
package factory;
import java.util.Random;
public class SimpleFactory {
private int screenWidth;
private Random random;
public SimpleFactory(int screenWidth){
this.screenWidth = screenWidth;
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;
}
}
package factory;
public class Client {
public static void main(String[] args) {
System.out.println("game start.....");
SimpleFactory factory = new SimpleFactory(100);
factory.create("Airplane").show();
factory.create("Tank").show();
}
}
客户端类的代码变得异常简单、清爽,这就是分类封装、各司其职的好处。然而,这个简单工厂的确很“简单”,但并不涉及任何的模式设计范畴,虽然客户端中不再直接出现对产品实例化的代码,但羊毛出在羊身上,制造逻辑只是被换了个地方,挪到了简单工厂中而已,并且客户端还要告知产品种类才能产出,这无疑是另一种意义上的耦合。
除此之外,简单工厂一定要保持简单,否则就不要用简单工厂。随着游戏项目需求的演变,简单工厂的可扩展性也会变得很差,例如对于那段对产品种类的判断逻辑,如果有新的敌人类加入,我们就需要再修改简单工厂。随着生产方式不断多元化,工厂类就得被不断地反复修改,严重缺乏灵活性与可扩展性,尤其是对于一些庞大复杂的系统,大量的产品判断逻辑代码会被堆积在制造方法中,看起来好像功能强大、无所不能,其实维护起来举步维艰,简单工厂就会变得一点也不简单了。
制定工业制造标准
其实系统中并不是处处都需要调用这样一个万能的“简单工厂”,有时系统只需要一个坦克对象,所以我们不必大动干戈使用这样一个臃肿的“简单工厂”。另外,由于用户需求的多变,我们又不得不生成大量代码,这正是我们要调和的矛盾。
针对复杂多变的生产需求,我们需要对产品制造的相关代码进行合理规划与分类,将简单工厂的制造方法进行拆分,构建起抽象化、多态化的生产模式。下面我们就对各种各样的生产方式(工厂方法)进行抽象,首先定义一个工厂接口,以确立统一的工业制造标准
Java
package Factory;
public interface Factory {
Enemy create(int screenWidth);
}
工厂接口Factory其实就是工厂方法模式的核心了。我们在第3行中声明了工业制造标准,只要传入屏幕宽度,就在屏幕坐标内产出一个敌人实例,任何工厂都应遵循此接口。接下来我们重构一下之前的简单工厂类,将其按产品种类拆分为两个类
AirplaneFactory
Java
package Factory;
import java.util.Random;
public class AirplaneFactory implements Factory{
@Override
public Enemy create(int screenWidth) {
Random random = new Random();
return new Airplane(random.nextInt(screenWidth),0);
}
}
TankFactory
Java
package Factory;
import java.util.Random;
public class TankFactory implements Factory {
@Override
public Enemy create(int screenWidth) {
Random random = new Random();
return new Tank(random.nextInt(screenWidth),0);
}
}
飞机工厂类AirplaneFactory与坦克工厂类TankFactory的代码简洁、明了,它们都以关键字implements声明了本类是实现工厂接口Factory的工厂实现类,并且在第4行给出了工厂方法create()的具体实现,其中飞机工厂制造飞机,坦克工厂制造坦克,各自有其独特的生产方式
客户端
Java
package Factory;
public class client {
public static void main(String[] args) {
int screenWidth = 100;
System.out.println("game start");
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();
}
}
}
显而易见,多态化后的工厂多样性不言而喻,每个工厂的生产策略或方式都具备自己的产品特色,不同的产品需求都能找到相应的工厂来满足,即便没有,我们也可以添加新工厂来解决,以确保游戏系统具有良好的兼容性和可扩展性。
GO 的实现模式
Enemy.go
package factory
type enemy struct {
x int
y int
}
type Enemy interface {
Show()
}
Airplane.go
package factory
import (
"fmt"
"strconv"
)
type airplane struct {
enemy
}
func (e *airplane) Show() {
fmt.Println("绘制飞机与上层图层,出现坐标:" + strconv.Itoa(e.x) + "," + strconv.Itoa(e.y))
fmt.Println("飞机向玩家发起攻击......")
}
Tank.go
package factory
import (
"fmt"
"strconv"
)
type tank struct {
enemy
}
func (e *tank) Show() {
fmt.Println("绘制坦克与上层图层,出现坐标:" + strconv.Itoa(e.x) + "," + strconv.Itoa(e.y))
fmt.Println("坦克向玩家发起攻击......")
}
Factory.go
package factory
type Factory interface {
Create() Enemy
}
AirplaneFactory.go
package factory
import "math/rand"
type AirplaneFactory struct {
}
func (factory AirplaneFactory) Create() Enemy {
return &airplane{enemy: enemy{rand.Intn(100), 0}}
}
TankFacotry.go
package factory
import "math/rand"
type TankFacotry struct {
}
func (e TankFacotry) Create() Enemy {
return &tank{
enemy{
x: rand.Intn(100),
y: 0,
},
}
}
main.go
package main
import (
Factory "desginPatterns/Factory"
"fmt"
)
func main() {
fmt.Println("game start")
var fact Factory.Factory
fact = Factory.TankFacotry{}
for i := 0; i < 5; i++ {
fact.Create().Show()
}
fact = Factory.AirplaneFactory{}
for i := 0; i < 5; i++ {
fact.Create().Show()
}
}
结果展示
劳动分工
至此,以工厂方法模式构建的空战游戏就完成了,之后若要加入新的敌人类,只需添加相应的工厂类,无须再对现有代码做任何更改。不同于简单工厂,工厂方法模式可以被看作由简单工厂演化而来的高级版,后者才是真正的设计模式。在工厂方法模式中,不仅产品需要分类,工厂同样需要分类,与其把所有生产方式堆积在一个简单工厂类中,不如把生产方式放在具体的子类工厂中去实现,这样做对工厂的抽象化与多态化有诸多好处,避免了由于新加入产品类而反复修改同一个工厂类所带来的困扰,使后期的代码维护以及扩展更加直观、方便。下面我们来看工厂方法模式的类结构
Product(产品):所有产品的顶级父类,可以是抽象类或者接口。对应本章例程中的敌人抽象类。
ConcreteProduct(子产品):由产品类Product派生出的产品子类,可以有多个产品子类。对应本章例程中的飞机类、坦克类。
Factory(工厂接口):定义工厂方法的工厂接口,当然也可以是抽象类,它使顶级工厂制造方法抽象化、标准统一化。
ConcreteFactory(工厂实现):实现了工厂接口的工厂实现类,并决定工厂方法中具体返回哪种产品子类的实例。
工厂方法模式不但能将客户端与敌人的实例化过程彻底解耦,抽象化、多态化后的工厂还能让我们更自由灵活地制造出独特而多样的产品。其实工厂不必万能,方便面工厂不必生产汽车,手机工厂也不必生产牛仔裤,否则就会通而不精,妄想兼备所有产品线的工厂并不是好的工厂。反之,每个工厂都应围绕各自的产品进行生产,专注于自己的产品开发,沿用这种分工明确的工厂模式才能使各产业变得越来越专业化,而不至于造成代码逻辑泛滥,从而降低产出效率。正所谓“闻道有先后,术业有专攻”,正如英国经济学家亚当·斯密提出的劳动分工理论一样,如图4-4所示,明确合理的劳动分工才能真正地促进生产效率的提升。