三言两语说透设计模式的艺术-工厂方法模式

写在前面

前面写到简单工厂模式虽然比较简单,将实例的创建和使用分类,客户端只需使用由工厂类创建的对象即可,无需关心对象的创建过程。但是这个系统仍然存在问题:

1)工厂类过于庞大,包含了大量的if判断语句代码,导致维护和测试难度增加;

2)当前只存在一个工厂类,在需要添加新产品时,由于静态工厂方法通过传入参数创建不同的产品,必须修改工厂了的源码,违背了开闭原则。

对此,需要对简单工厂模式进行优化,使其具有更好的灵活性和扩展性。这也是工厂方法模式的由来。

工厂方法模式

工厂方法模式(Factory Method Pattern)是简单工厂模式的进一步抽象和推广。在工厂方法模式中,不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同产品提供不同的工厂,使每个工厂只负责创建对应的产品。

工厂方法模式,是对简单工厂模式进行重构,即定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。

工厂方法模式包含以下主要角色:

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问产品。
  • 具体工厂(ConcreteFactory):实现了抽象工厂接口,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间往往存在依赖关系。

工厂方法模式的主要优点:

  • 封装了产品创建过程,调用者只需关心所需产品类型。
  • 实现了开闭原则,增加新产品无需修改之前工厂类代码。
  • 调用者无需知道产品类名,实现解耦,符合依赖倒转原则。
  • 易于扩展新产品,满足开闭原则,增加新产品仅需新增一个具体产品类和具体工厂类,无需修改现存代码。

可能的缺点:

  • 每增加一个产品就需要增加一个具体工厂,导致系统中类的个数成倍增加。
  • 复杂产品需要对应复杂工厂类,不易维护。

工厂方法模式的实现

我们使用 Typescript 代码来实现一个简单的工厂方法模式:

首先定义抽象产品类和具体产品类:

ts 复制代码
interface Food {
  getType(): string;
}

class Hamburger implements Food {
  getType() {
    return 'Hamburger';
  }
}

class Hotdog implements Food {
  getType() {
    return 'Hotdog';
  }
}

然后是抽象工厂类和具体工厂类:

ts 复制代码
abstract class FoodFactory {
  abstract createFood(): Food;
}

class HamburgerFactory extends FoodFactory {
  createFood() {
    return new Hamburger();
  }
}

class HotdogFactory extends FoodFactory {
  createFood() {
    return new Hotdog();
  }
}

客户端代码:

ts 复制代码
const hamburgerFactory = new HamburgerFactory();
const hamburger = hamburgerFactory.createFood();

const hotdogFactory = new HotdogFactory();
const hotdog = hotdogFactory.createFood();

客户端通过具体工厂来获取需要的产品,不关心实际产品类名。

在抽象工厂中使用泛型

我们可以使用泛型来定义产品类型:

ts 复制代码
interface FoodFactory<T extends Food> {
  createFood(): T; 
}

// 实现时指定泛型
class HamburgerFactory implements FoodFactory<Hamburger> {
  // ...
}

这样可以使工厂方法返回类型更加明确。

将工厂抽象成函数

工厂方法也可以简单实现为函数:

ts 复制代码
function createFood(type: 'Hamburger' | 'Hotdog') {
  switch(type) {
    case 'Hamburger': 
      return new Hamburger();
    case 'Hotdog':
      return new Hotdog();
  }
}

这种方式更简单,降低了代码的复杂度,但缺少面向对象的灵活性。

工厂方法模式 vs 简单工厂模式

简单工厂模式中工厂类负责所有产品的创建;而工厂方法模式中每一个具体工厂类只负责创建对应的一个产品,它将产品的创建推迟到子类。

两者区别主要在:

  • 简单工厂中,工厂类负责所有产品创建。
  • 工厂方法中,每个具体工厂只负责对应的产品。
  • 工厂方法模式更加灵活,易扩展,但创建对象较多。

简单工厂适合产品种类少的情况,工厂方法适合产品不断扩展的场景。

应用实例:游戏工厂

我们可以使用工厂方法模式实现一个游戏工厂,用于生成不同类型的游戏对象。

首先是游戏基类和具体游戏类:

ts 复制代码
interface Game {
  start();
}

class RPG implements Game {
  start() {
    console.log('Starting RPG game');
  } 
}

class MMORPG implements Game {
  start() {
    console.log('Starting MMORPG game');
  }
}

然后是抽象工厂和具体工厂:

ts 复制代码
abstract class GameFactory {
  abstract createGame(): Game;
}

class RPGFactory extends GameFactory {
  createGame() {
    return new RPG();
  }
}

class MMORPGFactory extends GameFactory {
  createGame() {
   return new MMORPG(); 
  }
}

客户端代码:

ts 复制代码
const rpgFactory = new RPGFactory();
const rpgGame = rpgFactory.createGame();
rpgGame.start();

const mmorpgFactory = new MMORPGFactory();
const mmorpgGame = mmorpgFactory.createGame();
mmorpgGame.start();

客户端只需要关心游戏类型,而不关心具体类名。

总结

工厂方法模式是一种广泛使用的设计模式,它具有以下核心特点:

  • 抽象工厂类负责定义创建对象的接口,而由子类实现CreateObject方法,实现了责任分解。
  • 每个具体工厂类只负责创建对应的一个产品,一个工厂类对应一个产品类。
  • 调用者只需要关心所需产品的类型,无需知道产品类名,实现了解耦。
  • 易于扩展新产品,满足开闭原则,当新增产品时只要增加一个具体工厂和产品类,无需修改之前代码。
  • 典型应用场景是针对同一抽象产品类有多个具体产品类的情况,而系统需要根据环境情况动态获得不同的具体产品对象。
  • 相比简单工厂模式,工厂方法模式更加灵活,易扩展,但创建对象较多。

综上,工厂方法模式通过面向对象封装了对象创建过程,实现低耦合、高内聚的代码,给系统提供了灵活的产品扩展方式,是非常流行与常用的设计模式。

一川说

觉得文章不错的读者,不妨点个关注,收藏起来上班摸鱼的时候品尝。

欢迎关注笔者公众号「宇宙一码平川」,助你技术路上一码平川。

相关推荐
nakyoooooo11 分钟前
【设计模式】工厂模式、单例模式、观察者模式、发布订阅模式
观察者模式·单例模式·设计模式
罗_三金13 分钟前
前端框架对比和选择?
javascript·前端框架·vue·react·angular
Fan_web1 小时前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html
叫我:松哥1 小时前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
严文文-Chris2 小时前
【设计模式-享元】
android·java·设计模式
好名字08212 小时前
monorepo基础搭建教程(从0到1 pnpm+monorepo+vue)
前端·javascript
c#上位机2 小时前
C#事件的用法
java·javascript·c#
万物得其道者成2 小时前
React Zustand状态管理库的使用
开发语言·javascript·ecmascript
小白小白从不日白2 小时前
react hooks--useReducer
前端·javascript·react.js
下雪天的夏风3 小时前
TS - tsconfig.json 和 tsconfig.node.json 的关系,如何在TS 中使用 JS 不报错
前端·javascript·typescript