JavaScript设计模式 -- 工厂模式

在实际开发中,我们经常会遇到需要根据不同场景生成不同对象的情况。为了解决对象创建与使用之间的耦合问题,工厂模式应运而生。本文将以 JavaScript 为例,从简单工厂到工厂方法,再到抽象工厂,介绍工厂模式的多种实现方式,并通过多个场景的示例代码,帮助大家更好地理解和应用这一设计模式。

工厂模式简介

工厂模式属于创建型设计模式,其核心思想是将对象的创建过程封装起来,让调用者只需关注产品接口,而无需了解具体实现。这样可以降低系统各模块之间的耦合度,使得扩展和维护更加方便。

根据具体需求,工厂模式常见的变体包括:

  • 简单工厂模式:由一个工厂类根据参数创建不同的产品实例;
  • 工厂方法模式:通过定义抽象工厂接口,让子类决定实例化哪一个具体产品;
  • 抽象工厂模式:用于创建一系列相关或相互依赖的产品。

接下来,我们分别介绍这些模式在 JavaScript 中的实现,并结合多种场景给出示例。


简单工厂模式

简单工厂模式将产品的创建逻辑集中在一个工厂方法中,根据传入参数返回相应的产品实例。适用于产品种类较少、逻辑简单的场景。

示例 1:产品选择

假设我们需要根据用户输入创建不同的产品对象,可以这样实现:

// 定义具体产品类
class ProductA {
  use() {
    console.log('使用产品 A');
  }
}

class ProductB {
  use() {
    console.log('使用产品 B');
  }
}

// 简单工厂类
class SimpleFactory {
  static createProduct(type) {
    switch (type.toLowerCase()) {
      case 'a':
        return new ProductA();
      case 'b':
        return new ProductB();
      default:
        throw new Error(`不支持的产品类型: ${type}`);
    }
  }
}

// 客户端调用
const product = SimpleFactory.createProduct('a');
product.use(); // 输出:使用产品 A

示例 2:几何图形

在图形绘制应用中,我们可能需要生成不同的几何图形对象,如圆形、正方形和三角形。简单工厂可以帮助我们根据形状名称动态创建对应实例。

// 定义图形基类
class Shape {
  draw() {
    throw new Error('抽象方法不能直接调用');
  }
}

class Circle extends Shape {
  draw() {
    console.log('绘制圆形');
  }
}

class Square extends Shape {
  draw() {
    console.log('绘制正方形');
  }
}

class Triangle extends Shape {
  draw() {
    console.log('绘制三角形');
  }
}

// 图形工厂
class ShapeFactory {
  static createShape(shapeType) {
    switch (shapeType.toLowerCase()) {
      case 'circle':
        return new Circle();
      case 'square':
        return new Square();
      case 'triangle':
        return new Triangle();
      default:
        throw new Error(`未知的图形类型: ${shapeType}`);
    }
  }
}

// 客户端调用
const shape = ShapeFactory.createShape('circle');
shape.draw(); // 输出:绘制圆形

工厂方法模式

工厂方法模式通过引入工厂接口,让子类负责具体对象的创建,从而更灵活地扩展产品。每个工厂子类只负责创建一种产品,符合单一职责原则。

示例 1:创建角色

考虑一个游戏场景,我们需要创建不同种类的角色,比如战士和法师。每种角色的创建逻辑可能不尽相同,这时就可以采用工厂方法模式。

// 定义角色基类
class Character {
  attack() {
    throw new Error('抽象方法不能直接调用');
  }
}

class Warrior extends Character {
  attack() {
    console.log('战士挥剑攻击');
  }
}

class Mage extends Character {
  attack() {
    console.log('法师施放魔法');
  }
}

// 定义角色工厂基类
class CharacterFactory {
  createCharacter() {
    throw new Error('抽象方法不能直接调用');
  }
}

// 具体工厂:战士工厂
class WarriorFactory extends CharacterFactory {
  createCharacter() {
    return new Warrior();
  }
}

// 具体工厂:法师工厂
class MageFactory extends CharacterFactory {
  createCharacter() {
    return new Mage();
  }
}

// 客户端调用
const warriorFactory = new WarriorFactory();
const warrior = warriorFactory.createCharacter();
warrior.attack(); // 输出:战士挥剑攻击

const mageFactory = new MageFactory();
const mage = mageFactory.createCharacter();
mage.attack(); // 输出:法师施放魔法

示例 2:生成通知

在一个消息系统中,我们可能需要生成不同类型的通知,比如邮件通知和短信通知。采用工厂方法模式,可以为每种通知定义一个专属工厂。

// 定义通知基类
class Notification {
  send(message) {
    throw new Error('抽象方法不能直接调用');
  }
}

class EmailNotification extends Notification {
  send(message) {
    console.log(`通过邮件发送通知:${message}`);
  }
}

class SMSNotification extends Notification {
  send(message) {
    console.log(`通过短信发送通知:${message}`);
  }
}

// 定义通知工厂基类
class NotificationFactory {
  createNotification() {
    throw new Error('抽象方法不能直接调用');
  }
}

// 具体工厂:邮件通知工厂
class EmailNotificationFactory extends NotificationFactory {
  createNotification() {
    return new EmailNotification();
  }
}

// 具体工厂:短信通知工厂
class SMSNotificationFactory extends NotificationFactory {
  createNotification() {
    return new SMSNotification();
  }
}

// 客户端调用
const emailFactory = new EmailNotificationFactory();
const emailNotification = emailFactory.createNotification();
emailNotification.send('您的订单已发货'); // 输出:通过邮件发送通知:您的订单已发货

const smsFactory = new SMSNotificationFactory();
const smsNotification = smsFactory.createNotification();
smsNotification.send('您的验证码是123456'); // 输出:通过短信发送通知:您的验证码是123456

抽象工厂模式

抽象工厂模式用于创建一系列相关或相互依赖的对象,而不需要指定它们具体的类。它特别适合产品族(family of products)的场景,比如跨平台 UI、主题切换等。

示例 1:UI 组件族

假设我们需要开发一个跨平台应用,根据操作系统不同生成一套不同风格的 UI 组件(按钮、输入框等)。使用抽象工厂可以统一管理不同平台的组件族。

// 定义产品:按钮和输入框的抽象接口
class Button {
  render() {
    throw new Error('抽象方法不能直接调用');
  }
}

class TextField {
  render() {
    throw new Error('抽象方法不能直接调用');
  }
}

// Windows 平台的具体组件
class WindowsButton extends Button {
  render() {
    console.log('渲染 Windows 风格按钮');
  }
}

class WindowsTextField extends TextField {
  render() {
    console.log('渲染 Windows 风格输入框');
  }
}

// macOS 平台的具体组件
class MacButton extends Button {
  render() {
    console.log('渲染 macOS 风格按钮');
  }
}

class MacTextField extends TextField {
  render() {
    console.log('渲染 macOS 风格输入框');
  }
}

// 抽象工厂接口
class UIFactory {
  createButton() {
    throw new Error('抽象方法不能直接调用');
  }
  createTextField() {
    throw new Error('抽象方法不能直接调用');
  }
}

// Windows 平台工厂
class WindowsUIFactory extends UIFactory {
  createButton() {
    return new WindowsButton();
  }
  createTextField() {
    return new WindowsTextField();
  }
}

// macOS 平台工厂
class MacUIFactory extends UIFactory {
  createButton() {
    return new MacButton();
  }
  createTextField() {
    return new MacTextField();
  }
}

// 客户端调用
function renderUI(factory) {
  const button = factory.createButton();
  const textField = factory.createTextField();
  button.render();
  textField.render();
}

// 根据当前平台选择工厂
const currentPlatform = 'mac'; // 或者 'windows'
const uiFactory = currentPlatform === 'windows' ? new WindowsUIFactory() : new MacUIFactory();
renderUI(uiFactory);

// 输出(macOS 平台):
// 渲染 macOS 风格按钮
// 渲染 macOS 风格输入框

示例 2:跨平台资源加载

在某些应用中,我们可能需要根据运行环境加载不同的资源(如配置文件、语言包等)。抽象工厂模式可以帮助我们创建一系列相关的资源加载器。

// 定义语言包加载器的接口
class LanguagePack {
  load() {
    throw new Error('抽象方法不能直接调用');
  }
}

class ConfigLoader {
  load() {
    throw new Error('抽象方法不能直接调用');
  }
}

// 英文资源
class EnglishLanguagePack extends LanguagePack {
  load() {
    console.log('加载英文语言包');
  }
}

class EnglishConfigLoader extends ConfigLoader {
  load() {
    console.log('加载英文配置文件');
  }
}

// 中文资源
class ChineseLanguagePack extends LanguagePack {
  load() {
    console.log('加载中文语言包');
  }
}

class ChineseConfigLoader extends ConfigLoader {
  load() {
    console.log('加载中文配置文件');
  }
}

// 抽象工厂接口
class ResourceFactory {
  createLanguagePack() {
    throw new Error('抽象方法不能直接调用');
  }
  createConfigLoader() {
    throw new Error('抽象方法不能直接调用');
  }
}

// 英文资源工厂
class EnglishResourceFactory extends ResourceFactory {
  createLanguagePack() {
    return new EnglishLanguagePack();
  }
  createConfigLoader() {
    return new EnglishConfigLoader();
  }
}

// 中文资源工厂
class ChineseResourceFactory extends ResourceFactory {
  createLanguagePack() {
    return new ChineseLanguagePack();
  }
  createConfigLoader() {
    return new ChineseConfigLoader();
  }
}

// 客户端调用
const locale = 'zh'; // 或者 'en'
const resourceFactory = locale === 'zh' ? new ChineseResourceFactory() : new EnglishResourceFactory();
resourceFactory.createLanguagePack().load(); // 输出:加载中文语言包
resourceFactory.createConfigLoader().load();   // 输出:加载中文配置文件

总结与应用场景

工厂模式通过封装对象创建逻辑,使得代码具有更好的解耦性和扩展性。下面是各个模式的主要特点及适用场景:

  • 简单工厂模式

    • 特点:通过一个静态方法创建不同产品,适合产品种类少、逻辑简单的情况。
    • 应用场景:工具类、简单配置管理等。
  • 工厂方法模式

    • 特点:将创建对象的责任交给子类,符合单一职责原则,便于扩展。
    • 应用场景:不同业务逻辑下的对象创建,如角色生成、通知系统等。
  • 抽象工厂模式

    • 特点:一次性创建一系列相关产品,确保产品族之间的一致性。
    • 应用场景:跨平台 UI、主题切换、资源加载等需要整体解决方案的场景。

通过本文提供的多种示例,希望能帮助你理解如何在不同场景下灵活使用工厂模式。无论是简单的对象创建还是复杂产品族的管理,工厂模式都能提供一种优雅的解决方案,使系统更易维护、扩展和测试。

欢迎在评论区分享你的使用心得与疑问!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/968543.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

因果机器学习(CausalML)前沿创新思路

结合了传统因果推断与机器学习的因果机器学习是目前AI领域的前沿研究方向,其核心优势在于将因果逻辑融入数据驱动模型,从根本上解决了传统方法的缺陷。因此,它也是突破传统机器学习瓶颈的关键方向,不仅当下热度高,在未…

网络防御高级02-综合实验

web页面: [FW]interface GigabitEthernet 0/0/0 [FW-GigabitEthernet0/0/0]service-manage all permit 需求一,接口配置: SW2: [Huawei]sysname SW2 1.创建vlan [sw2]vlan 10 [sw2]vlan 20 2.接口配置 [sw2]interface GigabitEther…

【devops】 Git仓库如何fork一个私有仓库到自己的私有仓库 | git fork 私有仓库

一、场景说明 场景: 比如我们Codeup的私有仓库下载代码 放入我们的Github私有仓库 且保持2个仓库是可以实现fork的状态,即:Github会可以更新到Codeup的最新代码 二、解决方案 1、先从Codeup下载私有仓库代码 下载代码使用 git clone 命令…

一竞技瓦拉几亚S4预选:YB 2-0击败GG

在2月11号进行的PGL瓦拉几亚S4西欧区预选赛上,留在欧洲训练的YB战队以2-0击败GG战队晋级下一轮。双方对阵第二局:对线期YB就打出了优势,中期依靠卡尔带队进攻不断扩大经济优势,最终轻松碾压拿下比赛胜利,以下是对决战报。 YB战队在天辉。阵容是潮汐、卡尔、沙王、隐刺、发条。G…

ATF系统安全从入门到精通

CSDN学院课程连接:https://edu.csdn.net/course/detail/39573

Linux内核实时机制x - 中断响应测试 Cyclictest分析1

Linux内核实时机制x - 中断响应测试Cyclitest 1 实时性测试工具 rt-test 1.1 源码下载 1.下载源码: ~/0-code/5.15$ git clone git://git.kernel.org/pub/scm/utils/rt-tests/rt-tests.git 正克隆到 rt-tests... remote: Enumerating objects: 5534, done. remot…

实现限制同一个账号最多只能在3个客户端(有电脑、手机等)登录(附关键源码)

如上图,我的百度网盘已登录设备列表,有一个手机,2个windows客户端。手机设备有型号、最后登录时间、IP等。windows客户端信息有最后登录时间、操作系统类型、IP地址等。这些具体是如何实现的?下面分别给出android APP中采集手机信…

如何获取,CPU,GPU,硬盘,网卡,内存等硬件性能监控与各项温度传感器

首先需要下载 OpenHardwareMonitorServer 这是一个基于OpenHardwareMonitor 的 Web 服务器。可以让任何语言都可以获取硬件信息和值,OpenHardwareMonitorServer 是没有UI界面的因此它可以当成控制台程序使用。 该程序可用参数如下 参数:需要管理员权限…

解锁大语言模型潜能:KITE 提示词框架全解析

大语言模型的应用日益广泛。然而,如何确保这些模型生成的内容在AI原生应用中符合预期,仍是一个需要不断探索的问题。以下内容来自于《AI 原生应用开发:提示工程原理与实战》一书(京东图书:https://item.jd.com/1013604…

C++STL容器之map的使用及复现

map 1. 关联式容器 vector、list、deque、forward_list(C11) 等STL容器,其底层为线性序列的数据结构,里面存储的是元素本身,这样的容器被统称为序列式容器。而 map、set 是一种关联式容器,关联式容器也是用来存储数据的&#xf…

网络工程师 (30)以太网技术

一、起源与发展 以太网技术起源于20世纪70年代,最初由Xerox公司的帕洛阿尔托研究中心(PARC)开发。最初的以太网采用同轴电缆作为传输介质,数据传输速率为2.94Mbps(后发展为10Mbps),主要用于解决…

30天开发操作系统 第 20 天 -- API

前言 大家早上好,今天我们继续努力哦。 昨天我们已经实现了应用程序的运行, 今天我们来实现由应用程序对操作系统功能的调用(即API, 也叫系统调用)。 为什么这样的功能称为“系统调用”(system call)呢?因为它是由应用程序来调用(操作)系统中的功能来完…

Java面试题及答案整理( 2023年 6 月最新版,持续更新)

秋招金九银十快到了,发现网上很多Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全~ 这套互联网 Java 工程师面试题包括了:MyBatis、ZK、Dubbo、EL、Redis、MySQL、并发编程、Java面试、Spring、微服务、Linux、Spri…

查询语句来提取 detail 字段中包含 xxx 的 URL 里的 commodity/ 后面的数字串

您可以使用以下 SQL 查询语句来提取 detail 字段中包含 oss.kxlist.com 的 URL 里的 commodity/ 后面的数字串&#xff1a; <p><img style"max-width:100%;" src"https://oss.kxlist.com//8a989a0c55e4a7900155e7fd7971000b/commodity/20170925/20170…

管式超滤膜分离技术都可以应用到哪些行业?

管式超滤膜分离技术由于其高效、稳定和适应性强的特点&#xff0c;在多个行业都有广泛的应用&#xff1a; 1. 生物制药与医药行业 纯化与浓缩&#xff1a;在生物药品的下游处理阶段&#xff0c;管式超滤膜被用来纯化抗体、疫苗、蛋白质等生物大分子&#xff0c;通过精确筛选分子…

基于opencv的 24色卡IQA评测算法源码-可完全替代Imatest

1.概要 利用24色卡可以很快的分析到曝光误差&#xff0c;白平衡误差&#xff0c;噪声&#xff0c;色差&#xff0c;饱和度&#xff0c;gamma值。IQA或tuning工程一般用Imatest来手动计算&#xff0c;不便于产测部署&#xff0c;现利用opencv实现了imatest的全部功能&#xff0c…

【matlab优化算法-17期】基于DBO算法的微电网多目标优化调度

基于蜣螂DBO算法的微电网多目标优化调度 一、前言 微电网作为智能电网的重要组成部分&#xff0c;其优化调度对于降低能耗、减少环境污染具有重要意义。本文介绍了一个基于Dung Beetle Optimizer&#xff08;DBO&#xff09;算法的微电网多目标优化调度项目&#xff0c;旨在通…

【多模态大模型】系列2:Transformer Encoder-Decoder——BLIP、CoCa、BEITv3

目录 1 BLIP2 CoCa3 BEITv3 1 BLIP BLIP: Bootstrapping Language-Image Pre-training for Unified Vision-Language Understanding and Generation BLIP是 ALBEF 原班人马做的&#xff0c;基本可以看做吸收了 VLMo 思想的 ALBEF。训练的 loss 和技巧都与 ALBEF一致&#xff…

算法——搜索算法:原理、类型与实战应用

搜索算法&#xff1a;开启高效信息检索的钥匙 在信息爆炸的时代&#xff0c;搜索算法无疑是计算机科学领域中熠熠生辉的存在&#xff0c;它就像一把神奇的钥匙&#xff0c;为我们打开了高效信息检索的大门。无论是在日常生活中&#xff0c;还是在专业的工作场景里&#xff0c;…

在vmd中如何渲染透明水分子

1.设置背景为白色 依次点击Graphics>>Colors... 2. 改变渲染模式 依次点击Display>>rendermode>>GLSL 3. 渲染水分子 选中水分子&#xff0c;显色方式改为ColorID, 编号10的颜色&#xff1b; 选择材质为GlassBubble; 绘图方式为QuickSurf. 若水盒子显示效…