前端常用设计模式

news/2024/12/24 2:36:56/文章来源:https://www.cnblogs.com/zimengxiyu/p/18625380

在开发中,设计模式是解决常见软件设计问题的经典方法。设计模式通过抽象化的解决方案来帮助开发者写出可维护、可扩展和灵活的代码。本文将介绍几种常见的前端设计模式,并讨论它们的应用场景和优势。

一. 单例模式

定义:通过控制类的实例化过程,确保全局只有一个实例存在,并提供全局访问点。

应用场景

  • 全局共享状态:当多个部分需要访问相同的数据或资源时,使用单例模式可以避免数据的冗余拷贝和不一致性。例如,配置管理器、数据库连接池、日志记录器等。
  • 控制访问:例如,线程池管理器、缓存管理器等,需要对资源进行有效控制和分配,避免创建多个实例带来的不必要开销。
  • 懒加载:只有在需要时才创建实例,避免了不必要的资源消耗。

示例:

class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;  // 返回已存在的实例
    }this.data = [];Singleton.instance = this;}addData(item) {this.data.push(item);}getData() {return this.data;}
}const instance1 = new Singleton();
instance1.addData('item1');
console.log(instance1.getData());  // ['item1']

const instance2 = new Singleton();
console.log(instance2.getData());  // ['item1']
console.log(instance1 === instance2);  // true

简化说明:

  • 第一次创建实例:实例化 Singleton 类,并保存到 Singleton.instance
  • 第二次及之后创建实例:直接返回保存的 Singleton.instance,不会重新创建实例。

最终 instance1 === instance2true,因为它们实际上指向同一个实例。

优点

  • 控制实例数量,节省内存。
  • 全局访问点,方便管理全局状态。

二、工厂模式

定义:隐藏 new 关键字,通过工厂函数创建实例。每次调用工厂函数时,都会创建一个新的实例

应用场景

  • 需要根据不同的条件创建不同类型的对象。
  • 避免直接使用 new,隐藏对象创建的复杂性。
class Button {render() {console.log('Rendering a button');}
}class Input {render() {console.log('Rendering an input');}
}class WidgetFactory {static createWidget(type) {switch(type) {case 'button':return new Button();case 'input':return new Input();default:throw new Error('Unknown widget type');}}
}const button = WidgetFactory.createWidget('button');
button.render();  // Rendering a button

优点

  • 可以动态决定创建的对象类型。
  • 适用于复杂对象的创建,解耦客户端与具体类的依赖。

三、观察者模式

定义:一个对象(被观察者)维护一系列依赖于它的对象(观察者),并在自身状态发生变化时通知所有观察者。

实际应用场景:

  • 消息订阅系统
  • 事件处理系统
  • UI控件状态更新
  • 数据库监听器

前端的事件监听机制就是观察者模式的一个典型应用。

DOM事件系统:

  • DOM元素是被观察者(Subject)
  • 事件处理函数是观察者(Observer)
  • addEventListener是注册观察者的方法
  • removeEventListener是移除观察者的方法
  • 事件触发时,所有注册的处理函数都会被调用

示例:

// 传统DOM事件监听
const button = document.querySelector('#myButton');// 添加观察者(事件监听器)
button.addEventListener('click', function(event) {console.log('按钮被点击了!');
});// 可以添加多个观察者
button.addEventListener('click', function(event) {console.log('另一个观察者收到点击事件');
});

优点

  • 松耦合,观察者和主题之间没有直接依赖。
  • 适用于处理多个组件的状态同步。

四、发布订阅模式

定义:其中“发布者”发布消息,“订阅者”订阅消息并响应消息的变化。发布者和订阅者之间没有直接的联系,它们通过一个中介(通常是事件总线、消息队列等)进行通信。

发布订阅模式(Publish-Subscribe)和观察者模式(Observer)之间的主要区别在于事件通道(Event Channel)的引入。

观察者模式的特点

  • 直接关联:在观察者模式中,观察者直接依赖于主题。主题维护一个观察者列表,通知所有注册的观察者。
  • 一对多关系:通常,主题是单一的,而观察者可以有多个。因此,它的关系是“一个主题,多观察者”。

发布订阅模式的特点

  • 松耦合:发布者和订阅者之间没有直接联系。它们通过事件通道进行通信,这让它们更加独立。
  • 多对多关系:一个事件可以有多个订阅者,发布者也可以发布多个事件。订阅者可以选择订阅多个事件。

发布订阅模式的组成

  • 发布者(Publisher):负责发布事件或消息。
  • 订阅者(Subscriber):对感兴趣的事件进行订阅,并做出响应。
  • 事件总线/消息中介(Event Bus / Message Broker):负责管理发布的事件和订阅者的监听。它连接发布者和订阅者,确保事件能够正确传递给订阅者。

示例:

// 发布订阅模式示例(事件通道实现)
class EventBus {constructor() {this.events = {};}subscribe(event, listener) {if (!this.events[event]) {this.events[event] = [];}this.events[event].push(listener);}publish(event, data) {const listeners = this.events[event];if (listeners) {listeners.forEach(listener => listener(data));}}unsubscribe(event, listener) {const listeners = this.events[event];if (listeners) {this.events[event] = listeners.filter(l => l !== listener);}}
}// 使用事件通道
const eventBus = new EventBus();
const subscriber1 = (data) => console.log(`Subscriber 1 received: ${data}`);
const subscriber2 = (data) => console.log(`Subscriber 2 received: ${data}`);eventBus.subscribe("event1", subscriber1);
eventBus.subscribe("event1", subscriber2);// 发布事件
eventBus.publish("event1", "Hello, world!");// 取消订阅
eventBus.unsubscribe("event1", subscriber1);// 再次发布事件
eventBus.publish("event1", "Second message.");

优缺点

  • 松耦合:发布者和订阅者之间没有直接联系,它们只通过事件通道进行通信。这样它们是高度解耦的,适用于复杂的异步系统。
  • 灵活性高:订阅者可以选择订阅感兴趣的事件,发布者可以自由发布事件,而不需要关心订阅者的具体实现。
  • 事件管理:通过事件通道集中管理所有事件和订阅者,避免了多个主题间的耦合。

五、装饰器模式

定义:它允许动态地给一个对象添加一些额外的职责,而不需要修改其结构。换句话说,装饰器模式通过创建装饰类来“包装”原始对象,并在不改变原始对象的基础上扩展其功能。

应用场景

  • UI 组件的增强:比如为按钮、文本框等组件添加功能,例如添加边框、阴影、事件处理等功能,而无需修改原有的组件代码。
  • 流式API:装饰器模式常用于构建流式API。例如,JavaScript 中的 Array 对象方法链式调用(map()filter() 等)本质上使用了装饰器模式来增强对象的方法。
  • 权限控制:在对象上动态地添加权限验证功能,使用装饰器动态地给用户对象增加访问控制功能。
  • 日志记录、性能监控:通过装饰器为方法添加日志记录或性能计时功能,而无需修改原始业务逻辑。

示例:

// Component(组件)
class Coffee {cost() {return 5;  // 基础咖啡的价格
  }
}// ConcreteComponent(具体组件)
class BasicCoffee extends Coffee {cost() {return super.cost();  // 返回基础咖啡的价格
  }
}// Decorator(装饰器)
class CoffeeDecorator extends Coffee {constructor(coffee) {super();this.coffee = coffee;  // 持有一个具体的Coffee对象
  }cost() {return this.coffee.cost();  // 委托给被装饰的对象
  }
}// ConcreteDecorator(具体装饰器)
// 装饰器1:添加牛奶
class MilkDecorator extends CoffeeDecorator {cost() {return this.coffee.cost() + 2;  // 加2元牛奶
  }
}// 装饰器2:添加糖
class SugarDecorator extends CoffeeDecorator {cost() {return this.coffee.cost() + 1;  // 加1元糖
  }
}// 创建基础咖啡对象
let coffee = new BasicCoffee();// 装饰咖啡:添加牛奶
coffee = new MilkDecorator(coffee);// 装饰咖啡:添加糖
coffee = new SugarDecorator(coffee);console.log(`Total cost: ${coffee.cost()}$`);  // 输出:Total cost: 8$

优点

  • 灵活性:装饰器模式使得对象的功能扩展更加灵活,可以在运行时根据需要添加或删除功能,而不需要修改原始类。
  • 可维护性:可以在不修改原始类的基础上扩展功能,这使得原始代码保持简洁且不易破坏。
  • 符合开放封闭原则:装饰器模式遵循开放封闭原则,即“对扩展开放,对修改封闭”。你可以扩展对象的行为,而不需要修改已有的类。
  • 组合多种功能:你可以通过装饰器的组合,灵活地为对象组合多个功能,而不需要创建大量的子类。

缺点

  • 增加了类的数量:使用装饰器模式时,每添加一个新的功能就需要创建一个装饰器类,这可能会导致类的数量增加。
  • 管理复杂性:当有很多装饰器时,管理和维护它们的关系可能变得复杂,特别是在多个装饰器互相依赖时。
  • 性能开销:每次调用时都需要通过装饰器链传递方法,可能会导致一定的性能损耗。

六、代理模式

定义:通过代理对象来控制客户端对目标对象的访问,代理对象可以在访问目标对象之前或之后添加额外的操作。

应用场景

  • 想控制对某个对象的访问。
  • 想延迟对象的初始化,或控制访问过程中的权限。
  • 想实现访问的日志记录、缓存、性能监控等功能。

示例:

// Subject(主题)
class Database {query() {console.log("Executing database query...");}
}// RealSubject(真实主题)
class RealDatabase extends Database {query() {console.log("Querying real database...");}
}// Proxy(代理)
class DatabaseProxy extends Database {constructor(realDatabase, userRole) {super();this.realDatabase = realDatabase;this.userRole = userRole; // 用户角色,用于权限控制
  }query() {if (this.userRole === "admin") {console.log("Permission granted, proceeding with the query.");this.realDatabase.query();} else {console.log("Permission denied. Access is restricted.");}}
}// 客户端使用代理对象来进行访问
const realDatabase = new RealDatabase();// 使用代理进行访问,并控制权限
const proxyAdmin = new DatabaseProxy(realDatabase, "admin");
proxyAdmin.query();  // Output: Permission granted, proceeding with the query.//         Querying real database...

const proxyUser = new DatabaseProxy(realDatabase, "user");
proxyUser.query();  // Output: Permission denied. Access is restricted.
解释
  • Database(主题):是一个抽象类或接口,定义了 query() 方法,客户端通过它来访问目标对象。
  • RealDatabase(真实主题):继承自 Database,实现了 query() 方法,表示真实的数据库操作。
  • DatabaseProxy(代理):继承自 Database,持有一个 RealDatabase 对象的引用,并在 query() 方法中根据权限控制是否允许访问数据库。如果是 admin,则调用 realDatabase.query(),否则拒绝访问。

代理模式的核心在于通过 DatabaseProxy 来控制访问 RealDatabase 的权限,在不修改 RealDatabase 类的情况下增加了访问控制的功能。

优点

  • 控制访问:通过代理对象,可以控制对目标对象的访问,例如权限控制、访问计数等。
  • 透明性:客户端通过代理对象访问真实对象,通常客户端并不关心是通过代理还是直接访问目标对象,代理可以透明地增加功能。
  • 扩展性:通过代理可以方便地添加额外的功能,而不需要修改真实对象的代码。这有助于遵循开放封闭原则

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CentOS系统搭建K8s集群

前情概要 关于在虚拟机中centos系统搭建k8s集群,前前后后花了很多个白天黑夜才搞定,采用不同的搭建方式搭建集群次数至少10次以上,期间看了无数文章和视频,也踩过无数坑,很多视频、文章的安装教程都存在一些差别,有些时候可能因为k8s安装版本不同或者缺少某些必要的设置导…

中台建设为什么需要领域驱动设计

一、数字化转型 数字化转型是企业能力全面体系化,系统化,数据化提升的过程,这种提升包括了技术能力,业务能力,组织架构合理性等多方面的提升。而随着多年来海量高频业务的发展,技术也在推动着持续进步,并且越来越多的技术方案趋向成熟,类似于阿里巴巴,腾讯,美团等,…

某小程序sign关键字逆向分析

声明 本文章所有内容仅用于学习交流,严禁用于其他目的。文中不提供完整代码,抓包内容、敏感网址及数据接口等均已脱敏处理。严禁将相关内容用于商业用途和非法用途,否则由此产生的一切后果与作者无关。未经许可,禁止转载本文,禁止修改后二次传播。擅自使用本文讲解的技术导…

乌鸡国

1-队伍进入副本以后,走到下图分岔点离队,每人负责一条线路,寻找树妖。2-寻找仙人,可以按照1的策略继续反方向走回去找仙人。3-寻找完仙人以后,会要求帮助国王清理荆棘木,五个号散开清理完即可。 4-进入皇宫,击杀拘灵妖怪、缚仙妖怪、囚神妖怪,需要在12回合内击杀三个妖…

梦幻神器-起-泪痕碗之念-2星

1-该任务需要5个随机指定三级药、7个2级家具,其中2级家具可以提前准备,三级药为NPC随机指定,无法提前准备。 2-前面跟着流程跑,第一场战斗是"清风",需要先击杀小怪,主怪清风在第四回合以后会说"我们放水吧",说了以后才可以击杀主怪清风。3-击杀清风…

STM32F103 SPI配置(SSD1306)

有关SPI通信协议我们在《通信协议-SPI》已经进行了详细的介绍,因此这一节不再重复介绍。 一、软件/硬件SPI 想要控制STM32产生SPI方式的通讯,可以采用软件模拟或硬件SPI这两种方式。 1.1 软件模拟 所谓软件模拟,即直接使用CPU内核按照SPI协议的要求控制GPIO输出高低电平。 1…

梦幻神器-起-莫愁铃之恩-1星

1-该任务需要提前准备5个三级药,金创药、佛光舍利子除外。 2-第一场战斗是击败地府守卫弟子,1星难度不大,注意"诡蝠之刑"的反伤即可,中了"诡蝠之刑"的单位可以适当防御。3-击杀地府守卫弟子以后跟着流程走,接下来需要给三个水晶注入灵气,需要先注入中…

我家一次用电超负荷时20A保险丝断了,但16A的空气开关却没有跳闸.

回答一: 转载自:https://zhidao.baidu.com/question/1970736314255432140.html这说明两者的保护时限不同。无论是保险丝还是空气开关,并不电流达到就立即跳,而且有一定的时间延时来积累热量,热量到了才会动作。 16A的空气开关没跳说明电流虽然超过20A,但时间很快,保险丝…

Easysearch Java SDK 2.0.x 使用指南(二)

在 上一篇文章 中,我们介绍了 Easysearch Java SDK 2.0.x 的基本使用和批量操作。本文将深入探讨索引管理相关的功能,包括索引的创建、删除、开关、刷新、滚动等操作,以及新版 SDK 提供的同步和异步两种调用方式。 SDK 的对象构建有两种方式 1. 传统的 Builder 方式 最基础的…

《docker高级篇(大厂进阶):2.DockerFile解析》包括:是什么、DockerFile构建过程解析、DockerFile常用保留字指令、案例、小总结

《docker高级篇(大厂进阶):2.DockerFile解析》包括:是什么、DockerFile构建过程解析、DockerFile常用保留字指令、案例、小总结@目录二、高级篇(大厂进阶)2.DockerFile解析2.1 是什么2.2 DockerFile构建过程解析2.3 DockerFile常用保留字指令2.4案例2.4.1自定义镜像mycento…

按钮修改状态

首先创造函数void updateBaoxiaoSchedule( int id, String schedule, String schedulereason); 及其实现。 SQL语句: @Update("update submit set state=#{state},statereason=#{statereason} where id=#{id} ") void updateSubmit1( @Param("id") int id…