Javascript常用设计模式

JavaScript常用设计模式详解

JavaScript作为前端开发的核心语言,其灵活性和动态性使得在实际项目中可以使用多种设计模式来解决复杂问题。本文将详细介绍创建型、结构型和行为型三种主要类型的设计模式,并通过具体示例说明每种模式的实现方式及应用场景。

创建型模式

创建型模式主要用于处理对象的创建过程,常见的有工厂模式、单例模式和原型链模式。接下来我们将逐一介绍这几种模式的特点及其在实际开发中的应用。

单例模式(Singleton Pattern)

定义: 单例模式的核心思想是确保一个类只有一个实例,并提供一个全局访问点来获取该唯一实例。

使用场景: 适用于需要维护单一对象的状态或配置的场景,例如全局模态框、全局状态管理等。通过实现单例模式可以避免重复创建同一类型的对象,从而提高性能和资源利用率。

class User {
    constructor(name) {
        if (User.instance) {
            return User.instance;
        }
        this.name = name;
        User.instance = this;
    }   
}

const user1 = new User('张三');
const user2 = new User('李四');
console.log(user1 === user2); // true

工厂模式(Factory Pattern)

定义: 工厂模式是一种创建对象的设计模式,它封装了实例化的逻辑并将这些细节从调用者那里抽象出来。通过工厂方法可以轻松地在运行时决定使用哪种具体类。

使用场景: 适用于需要根据不同的条件或参数动态创建不同类型的对象的情况,例如组件库、第三方SDK适配等复杂业务需求。

class Phone {}
class Computer {}

function createProduct(type, name) {
    switch (type) {
        case 'phone':
            return new Phone(name);
        case 'computer':
            return new Computer(name);
        default:
            throw new Error('未知类型');
    }   
}

const phone = createProduct('phone', 'iPhone');
console.log(phone); // Phone { name: 'iPhone' }

原型链模式(Prototype Pattern)

定义: 原型链模式允许通过克隆已有对象来创建新对象,这种机制可以避免重复编写代码或继承结构的复杂性。每个实例都包含自己特定的功能和状态,但共享相同的函数实现。

使用场景: 适用于需要在多个相关类之间共享方法时,例如对象之间的继承关系、需要封装公共属性等。

function Person(name) { 
    this.name = name;
}

Person.prototype.sayName = function() {
    console.log(this.name);
};

const person1 = new Person('张三');
person1.sayName(); // 张三

结构型模式

结构型模式关注于类和对象的组合,通过这些组合可以提供更高层次的抽象和强大的功能。在这一部分中,我们将介绍适配器模式、代理模式以及装饰器模式。

适配器模式(Adapter Pattern)

定义: 适配器模式允许将一个接口转换成客户希望的另一个接口,即它使原本由于接口不兼容而不能一起工作的类可以协作工作。通过使用适配器,我们可以在不需要修改现有业务代码的情况下引入新的功能或改变现有的交互方式。

const oldInterface = {
    getName: () => console.log('这是旧接口的方法1')
};

function adapter(oldInterface) {
    return {
        ...oldInterface,
        getAge: () => console.log('这是适配器新增的方法2')
    }   
}

const newInterface = adapter(oldInterface);
newInterface.getName(); // 这是旧接口的方法1

代理模式(Proxy Pattern)

定义: 代理模式提供一种控制访问对象的方式,可以用来进行拦截、增强对象功能等操作。通过在实际对象前添加一个代理对象来实现这些功能,使得原始对象的行为发生改变而不影响其他部分的代码。

const user = { name: '张三', age: 18 };
const proxyUser = new Proxy(user, {
    get(target, prop) {
        console.log(`访问了属性${prop}`);
        return target[prop];
    },
    set(target, prop, value) {
        console.log(`修改了属性${prop},新值为${value}`);       
        target[prop] = value;
        return true;
    }
});

console.log(proxyUser.name); // 访问了属性name  张三
proxyUser.age = 20; // 修改了属性age,新值为20

装饰器模式(Decorator Pattern)

定义: 装饰器模式动态地给对象添加新的功能而无需修改其结构。通过使用装饰器可以实现对已有类的增强,例如在不改变原有代码的情况下增加日志记录、权限控制等功能。

function log(target, name, descriptor) {
    const original = descriptor.value;
    descriptor.value = function(...args) {
        console.log(`调用了方法${name},参数为${JSON.stringify(args)}`);
        return original.apply(this, args);
    }
    return descriptor;
}

@log
function testFunction() {console.log('这是一个装饰器函数');}

testFunction(); // 调用了方法testFunction,参数为[]  这是一个装饰器函数

通过上述示例和详细解释,可以清晰地理解各种设计模式的特点及其在实际项目中的应用价值。这些模式不仅能够帮助开发者解决复杂问题,还能提高代码的可维护性和扩展性。

3、行为型模式(发布-订阅模式、观察者模式、策略模式)

行为型设计模式主要关注对象之间的交互方式,如如何实现解耦合和动态改变功能。以下是几种常见的行为型模式:

1. 发布-订阅模式

发布-订阅模式允许组件通过事件中心进行通信而不直接引用彼此,从而提高代码的可维护性和扩展性。

const eventBus = {
    events: {},
    subscribe(event, callback) {
        if (!this.events[event]) {
            this.events[event] = []
        }
        this.events[event].push(callback)
    },
    publish(event, data) {
        if (this.events[event]) {
            this.events[event].forEach((callback) => callback(data))
        }
    }
}

// 订阅事件
evntBus.subscribe('testEvent', (data) => {
    console.log(`接收到事件testEvent,数据为${data}`)
})

// 发布事件
evntBus.publish('testEvent', 'Hello World') // 输出:接收到事件testEvent,数据为Hello World

evntBus.subscribe('testEvent1', (data) => {
    console.log(`又接收到事件testEvent,数据为${data}`)
})
evntBus.publish('testEvent', 'Hello Again')
// 输出:
// 接收到事件testEvent,数据为Hello Again
// 又接收到事件testEvent,数据为Hello Again finally

发布-订阅模式常见于构建系统中的事件管理系统、消息队列和组件通信场景。

2. 观察者模式

观察者模式允许对象通过直接引用关系进行通信,适用于一对多的关系。这种模式在实现数据绑定、状态管理等方面非常有用。

class Subject {
    constructor() {
        this.observers = [] // 存储所有观察者的数组
    }
    add(observer) {
        this.observers.push(observer) // 添加新的观察者
    }
    notify(data) {
        this.observers.forEach((observer) => observer.update(data)) // 通知所有观察者更新状态
    }
}

// 创建主题实例并添加观察者
const subject = new Subject();
subject.add({
    update(data) {
        console.log(`观察者1接收到数据${data}`)
    }
})
subject.notify('Hello Observer') // 输出:观察者1接收到数据Hello Observer

subject.add({
    update(data) {
        console.log(`观察者2接收到数据${data}`)
    }
})
subject.notify('Hello Again')
// 输出:
// 观察者1接收到数据Hello Again
// 观察者2接收到数据Hello Again finally

与发布-订阅模式相比,观察者模式更加直接简单,但耦合性稍高。

3. 策略模式

策略模式通过封装算法并使它们可以互换来实现代码的灵活性。它适用于需要动态改变功能或行为的情况。

const strategies = {
    isNonEmpty(value, errorMsg) {
        if (value === '') {
            return errorMsg
        }
    },
    isNumber(value, errorMsg) {
        if (typeof value !== 'number') {
            return errorMsg
        }
    }
}

function validate(value, rules) {
    for (let i = 0; i < rules.length; i++) {
        const rule = rules[i]
        const strategy = strategies[rule.strategy]
        if (strategy) {
            const errorMsg = strategy(value, rule.errorMsg)
            if (errorMsg) {
                return errorMsg
            }
        }
    }
    return null
}

const rules = [
    { strategy: 'isNonEmpty', errorMsg: '不能为空' },
    { strategy: 'isNumber', errorMsg: '必须是数字' }

const errorMsg = validate('', rules)     // 输出:不能为空    
console.log(errorMsg); // 不能为空

const errorMsg2 = validate('123', rules) // 输出:必须是数字
console.log(errorMsg2); // 必须是数字

通过策略模式,可以轻松增加或移除验证逻辑而无需修改核心代码。此外,在处理表单验证、数据加密和排序算法时也非常有用。

const strategies2 = {
    A:(score)=>score >=90,
    B:(score)=>score >=80 && score <90,
    C:(score)=>score >=70 && score <80,
    D:(score)=>score >=60 && score <70,
    E:(score)=>score <60
}
function getGrade(score) {
    for (let key in strategies2) {
        if (strategies2[key](score)) {
            console.log(`成绩为${key}`);
            return key
        }
    }
    return '未知'
}

getGrade(95); // 输出:A
getGrade(85); // 输出:B
getGrade(75); // 输出:C
getGrade(65); // 输出:D
getGrade(55); // 输出:E

以上示例展示了如何使用策略模式根据不同的评分规则来获取成绩。