前端常用设计模式

前端常用设计模式

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

一. 单例模式

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

应用场景:

全局共享状态:当多个部分需要访问相同的数据或资源时,使用单例模式可以避免数据的冗余拷贝和不一致性。例如,配置管理器、数据库连接池、日志记录器等。

控制访问:例如,线程池管理器、缓存管理器等,需要对资源进行有效控制和分配,避免创建多个实例带来的不必要开销。

懒加载:只有在需要时才创建实例,避免了不必要的资源消耗。

示例:

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 === instance2 为 true,因为它们实际上指向同一个实例。

优点:

控制实例数量,节省内存。

全局访问点,方便管理全局状态。

二、工厂模式

定义:隐藏 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() 等)本质上使用了装饰器模式来增强对象的方法。

权限控制:在对象上动态地添加权限验证功能,使用装饰器动态地给用户对象增加访问控制功能。

日志记录、性能监控:通过装饰器为方法添加日志记录或性能计时功能,而无需修改原始业务逻辑。

示例:

// 基础奶茶类

class MilkTea {

cost() {

return 10;

}

getDesc() {

return "奶茶";

}

}

// 简单的装饰器函数

const addPearl = (milkTea) => {

const cost = milkTea.cost();

const desc = milkTea.getDesc();

return {

cost: () => cost + 2,

getDesc: () => desc + " + 珍珠"

};

}

const addPudding = (milkTea) => {

const cost = milkTea.cost();

const desc = milkTea.getDesc();

return {

cost: () => cost + 3,

getDesc: () => desc + " + 布丁"

};

}

// 使用

let tea = new MilkTea();

console.log(tea.getDesc()); // 奶茶

console.log(tea.cost()); // 10

tea = addPearl(tea);

console.log(tea.getDesc()); // 奶茶 + 珍珠

console.log(tea.cost()); // 12

tea = addPudding(tea);

console.log(tea.getDesc()); // 奶茶 + 珍珠 + 布丁

console.log(tea.cost()); // 15

优点

灵活性:装饰器模式使得对象的功能扩展更加灵活,可以在运行时根据需要添加或删除功能,而不需要修改原始类。

可维护性:可以在不修改原始类的基础上扩展功能,这使得原始代码保持简洁且不易破坏。

符合开放封闭原则:装饰器模式遵循开放封闭原则,即“对扩展开放,对修改封闭”。你可以扩展对象的行为,而不需要修改已有的类。

组合多种功能:你可以通过装饰器的组合,灵活地为对象组合多个功能,而不需要创建大量的子类。

缺点

增加了类的数量:使用装饰器模式时,每添加一个新的功能就需要创建一个装饰器类,这可能会导致类的数量增加。

管理复杂性:当有很多装饰器时,管理和维护它们的关系可能变得复杂,特别是在多个装饰器互相依赖时。

性能开销:每次调用时都需要通过装饰器链传递方法,可能会导致一定的性能损耗。

六、代理模式

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

应用场景:

想控制对某个对象的访问。

想延迟对象的初始化,或控制访问过程中的权限。

想实现访问的日志记录、缓存、性能监控等功能。

示例:

// 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 类的情况下增加了访问控制的功能。

优点

控制访问:通过代理对象,可以控制对目标对象的访问,例如权限控制、访问计数等。

透明性:客户端通过代理对象访问真实对象,通常客户端并不关心是通过代理还是直接访问目标对象,代理可以透明地增加功能。

扩展性:通过代理可以方便地添加额外的功能,而不需要修改真实对象的代码。这有助于遵循开放封闭原则。

相关灵感

365bet吧 科普宝来车雾灯怎么开📖

科普宝来车雾灯怎么开📖

📅 09-16 👁️ 113
365bet吧 日本邀请中韩联合申办世界杯?足协:暂没听说
365bet吧 钉钉如何退出钉钉运动-钉钉如何关闭钉钉运动[多图]
mobile365 绝地求生PUBG账号误封 解封流程
beat365体育亚洲入口 鹅掌柴怎么修剪,什么时候修剪
beat365体育亚洲入口 分类:中国影视公司

分类:中国影视公司

📅 07-03 👁️ 6413
beat365体育亚洲入口 陈引驰:陶渊明为何归隐田园

陈引驰:陶渊明为何归隐田园

📅 06-29 👁️ 9937
beat365体育亚洲入口 鸡蛋海盗船的做法与步骤

鸡蛋海盗船的做法与步骤

📅 10-20 👁️ 7405
beat365体育亚洲入口 27尺等于多少厘米

27尺等于多少厘米

📅 10-06 👁️ 5363