浅谈设计模式

本文介绍了设计模式在JavaScript中的应用,包括工厂模式创建对象、单例模式保证全局唯一、适配器模式解决接口不兼容、代理模式控制访问权限,以及发布-订阅模式的一对多依赖关系。展示了如何在实际项目中使用这些模式提高代码的可读性、复用性和维护性。
摘要由CSDN通过智能技术生成

设计模式

设计模式总的来说是⼀个抽象的概念, 通过无数次的实践总结出的⼀套写代码的方式, 通过这种方式写的代码可以让别⼈更加容易阅读 、维护以及复用。

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。

更多详细内容,请微信搜索“前端爱好者戳我 查看

工厂模式

在 JavaScript 中,工厂模式是一种创建对象的常见方式。

工厂模式是对象创建型模式,它提供了创建对象的接口,但具体的对象创建逻辑由子类决定。

class Man {
    constructor(name) {
        this.name = name
    }
    alertName() {
        alert(this.name)
    }
}

class Factory {
    static create(name) {
        return new Man(name)
    }
}

Factory.create( 'yck').alertName()

当然工厂模式并不仅仅是用来 new 出实例。

可以想象⼀个场景。

假设有⼀份很复杂的代码需要用户去调用,但是用户并不关⼼这些复杂的代码, 只需要你提供给我⼀个接口去调用,用户只负责传递需要的参数, 至于这些参数怎么使用, 内部有什么逻辑是不关⼼的, 只需要你最后返回我⼀个实例 。这个构造过程就是工厂。

工厂起到的作用就是隐藏了创建实例的复杂度, 只需要提供⼀个接口, 简单清晰。

在 Vue 源码中,你也可以看到工厂模式的使用, 比如创建异步组件

export function createComponent (
        Ctor: Class<Component> | Function | Object | void,
        data: ?VNodeData,
        context: Component,
        children: ?Array<VNode>,
        tag?: string
    ): VNode | Array<VNode> | void {
        
    // 逻辑处理 ...
    const vnode = new VNode(
        `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
        data, undefined, undefined, undefined, context,
        { Ctor, propsData, listeners, tag, children },
        asyncFactory
    )
    return vnode
} 

在上述代码中, 我们可以看到我们只需要调用createComponent 传⼊参数就能创建⼀个组件实例

但是创建这个实例是很复杂的⼀个过程, 工厂帮助我们隐藏了这个复杂的过程, 只需要⼀句代码调用就能实现功能

function CarMaker(model) {  
    var car;  
  
    this.produce = function () {  
        if (typeof car === 'undefined') {  
            car = new model();  
            console.log("A " + model.name + " is created.");  
        } else {  
            console.log("This " + model.name + " has already been created.");  
        }  
        return car;  
    };  
}  
  
var audi = {  
    name: 'Audi',  
    color: 'blue',  
    speed: 120  
};  
  
var bmw = {  
    name: 'BMW',  
    color: 'black',  
    speed: 180  
};  
  
var makerAudi = new CarMaker(audi);  
makerAudi.produce(); // 输出:"A Audi is created."  
makerAudi.produce(); // 输出:"This Audi has already been created."  
  
var makerBmw = new CarMaker(bmw);  
makerBmw.produce(); // 输出:"A BMW is created."  
makerBmw.produce(); // 输出:"This BMW has already been created."

在这个例子中,CarMaker 是一个工厂类,它接收一个模型参数(model),并有一个 produce 方法用于创建模型对象。

当 produce 方法被调用时,如果还没有创建对象,就会创建一个新的模型对象,并且打印出一条消息说明创建了什么对象。

如果对象已经被创建,就会打印出一条消息说明该对象已经被创建。

我们可以创建 CarMaker 的子类来根据需要创建不同类型的对象。

这就是工厂模式的基本思想。

单例模式

单例模式很常用, 比如 全局缓存全局状态管理 等等这些只需要⼀个对象,就可以使用单例模式。

单例模式的核心就是保证全局只有⼀个对象可以访问

因为 JS 是门无类的语言,所以别的语言实现单例的方式并不能套⼊ JS 中, 我们只需要用⼀个变量确保实例只创建⼀次就行

以下是如何实现单例模式的例⼦

class Singleton {
    constructor() {}
}

Singleton.getInstance = (function() {
    let instance
    return function() {
        if (!instance) {
            instance = new Singleton()
        }
        return instance
    }
})()

let s1 = Singleton.getInstance()
let s2 = Singleton.getInstance()
console.log(s1 === s2) // true

在 Vuex 源码中,你也可以看到单例模式的使用, 虽然它的实现方式不大⼀样, 通过⼀个外部变量来控制只安装⼀次 Vuex

let Vue / / bind on install

export function install (_Vue) {
    if (Vue && _Vue === Vue) {
        // 如果发现 Vue 有值,就不重新创建实例了
        return
    }

    Vue = _Vue
    applyMixin(Vue)
}

适配器模式

适配器用来解决两个接口不兼容的情况,不需要改变已有的接口, 通过包装⼀层的方式实现两个接口的正常协作。

以下是如何实现适配器模式的例子

class Plug {
    getName() {
        return '港版插头 '
    }
}

class Target {
    constructor() {
        this.plug = new Plug()
    }
    getName() {
        return this.plug.getName() + ' 适配器转⼆脚插头 '
    }
}

let target = new Target()
target.getName() // 港版插头 适配器转⼆脚插头

在 Vue 中, 我们其实经常使用到适配器模式 。

比如父组件传递给子组件⼀个时间戳属性,组件内部需要将时间戳转为正常的日期显示,⼀般会使用computed 来做转换这件事情, 这个过程就使用到了适配器模式

一个基本的适配器模式的JavaScript实现:

class Adaptee {  
    specificRequest() {  
        console.log('Adaptee specific request');  
    }  
}  
  
class Target {  
    request() {  
        console.log('Target request');  
    }  
}  
  
class Adapter extends Adaptee {  
    constructor(adaptee) {  
        this.adaptee = adaptee;  
    }  
  
    request() {  
        console.log('Adapter request');  
        this.adaptee.specificRequest();  
    }  
}  
  
let adaptee = new Adaptee();  
let adapter = new Adapter(adaptee);  
adapter.request();  // Outputs: "Adapter request" and "Adaptee specific request"

在这个例子中,Adaptee类有一个特定的specificRequest方法,而Target类需要一个与其request方法兼容的adaptee对象。\

Adapter类继承自Adaptee,并且有一个request方法,该方法调用Adaptee的specificRequest方法以满足Target的request方法的需求。

这样,我们就创建了一个适配器,将Adaptee的接口转换为Target所期望的接口。

在JavaScript中,我们也可以使用对象组合的方式来实现适配器模式:

class Adaptee {  
    specificRequest() {  
        console.log('Adaptee specific request');  
    }  
}  
  
class Target {  
    request(adaptee) {  
        console.log('Target request');  
        adaptee.specificRequest();  
    }  
}  
  
let adaptee = new Adaptee();  
let target = new Target();  
target.request(adaptee);  // Outputs: "Target request" and "Adaptee specific request"

在这个例子中,我们直接将Adaptee对象传递给Target的request方法。

这种实现方式更灵活,因为我们可以动态地改变Adaptee对象。

装饰模式

装饰模式是一种设计模式,它允许你在不改变对象自身的基础上,动态地给对象添加新的功能。

这主要是通过创建一个装饰类,该类继承了原始对象,并包含原始对象的一个实例。

然后,这个装饰类可以添加额外的功能,最后调用原始对象的方法。

在JavaScript中,装饰模式可以通过以下方式实现:

function Car(make, model, year) {  
    this.make = make;  
    this.model = model;  
    this.year = year;  
}  
  
Car.prototype.honk = function() {  
    console.log('Beep Beep!');  
};  
  
function CarDecorator(car) {  
    this.car = car;  
}  
  
CarDecorator.prototype.honk = function() {  
    console.log('Decorated Honk: ');  
    this.car.honk();  
};  
  
let myCar = new Car('Toyota', 'Corolla', 2020);  
let decoratedCar = new CarDecorator(myCar);  
  
decoratedCar.honk();  // Outputs: Decorated Honk: Beep Beep!

在这个例子中,Car类有一个honk方法,这是一个简单的对象创建和功能调用。

然后我们创建了一个CarDecorator类,它接受一个Car对象并在原有功能上添加额外的功能。

最后我们创建了一个新的CarDecorator实例,并调用了它的honk方法。

这个方法首先打印出"Decorated Honk: ",然后调用原始Car对象的honk方法。

这样我们就成功地在不改变Car类的基础上添加了新的功能。

因此,在JavaScript中,装饰器函数需要接受一个函数作为参数,并返回一个新的函数。

这使得在JavaScript中使用装饰器模式相比其他一些语言更为复杂。

代理模式

代理是为了控制对对象的访问,不让外部直接访问到对象 。在现实生活中,也有很多代理的场景

比如你需要买⼀件国外的产品, 这时候你可以通过代购来购买产品。

在实际代码中其实代理的场景很多,也就不举框架中的例子了, 比如事件代理就用到了代理模式

<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>

<script>
let ul = document.querySelector( '#ul')
ul.addEventListener( 'click', (event) => {
    onsole.log(event.target);
})
</script>

因为存在太多的 li,不可能每个都去绑定事件。

这时候可以通过给父节点绑定⼀个事件,让父节点作为代理去拿到真实点击的节点

发布-订阅模式

发布-订阅模式(Publish-Subscribe Pattern)是一种常见的设计模式,广泛应用于前端开发中。

这种模式定义了一种一对多的依赖关系,让多个订阅者(Subscribers)能够接收到发布者(Publishers)发布的信息。

在发布-订阅模式中,发布者负责发布消息,而订阅者则订阅感兴趣的主题。当发布者发布消息时,所有订阅了该主题的订阅者都会收到相应的消息。

下面是一个简单的发布-订阅模式实现示例:

class Publisher {
  constructor() {
    this.subscribers = [];
  }

  subscribe(subscriber) {
    this.subscribers.push(subscriber);
  }

  publish(message) {
    this.subscribers.forEach((subscriber) => {
      subscriber.receive(message);
    });
  }
}

class Subscriber {
  constructor(name) {
    this.name = name;
  }

  receive(message) {
    console.log(`${this.name} received message: ${message}`);
  }
}

const publisher = new Publisher();

const subscriber1 = new Subscriber("Subscriber 1");
const subscriber2 = new Subscriber("Subscriber 2");
const subscriber3 = new Subscriber("Subscriber 3");

publisher.subscribe(subscriber1);
publisher.subscribe(subscriber2);
publisher.subscribe(subscriber3);

publisher.publish("Hello, subscribers!");

在这个例子中,我们创建了一个Publisher类和Subscriber类。Publisher类负责管理订阅者列表,并提供subscribepublish方法。Subscriber类表示订阅者,每个订阅者都有自己的名字,并且可以接收并处理消息。

首先,我们创建了一个Publisher实例和三个Subscriber实例。然后,我们让这三个订阅者订阅发布者,当发布者发布消息时,所有订阅了该消息的订阅者都会接收到消息,并打印出自己的名字和收到的消息。

在现代前端框架(如React、Vue等)中,发布-订阅模式通常被用于实现组件间通信、状态管理和响应式编程等功能。

这些框架都提供了相应的工具和机制来简化发布-订阅模式的实现,例如事件总线、观察者模式、Redux等。

外观模式

外观模式提供了⼀个接⼝,隐藏了内部的逻辑,更加方便外部调用。

个例⼦来说, 我们现在需要实现⼀个兼容多种浏览器的添加事件方法

function addEvent(elm, evType, fn, useCapture) {
    if (elm.addEventListener) {
        elm.addEventListener(evType, fn, useCapture)
        return true
    } else if (elm.attachEvent) {
        var r = elm.attachEvent("on" + evType, fn)
        return r
    } else {
        elm["on" + evType] = fn
    }
}

对于不同的浏览器,添加事件的方式可能会存在兼容问题 。

如果每次都需要去这样写⼀遍的话肯定是不能接受的,所以我们将这些判断逻辑统⼀封装在⼀个接口中,外部需要添加事件只需要调用 addEvent 即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端布道人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值