前端总结设计模式

设计模式存在的根本原因是为了代码的复用,增加可维护性。多久前看过张容铭的《JavaScript设计模式》然后感觉就是设计模式在实际项目中用到的不多,除非写功能框架类的东西。最近偶然看到一篇设计模式文章,对比了下现在在做的项目的某些功能模块都对应上了,感觉啪啪的打脸。故重新补了下知识点,以最简单的例子进行总结。要求自己对于常用的设计模式,强制模仿,掌握。而非常用的设计模式,视业务场景选择性使用。
常用设计模式
创建型模式:工厂模式,单例模式。
结构性模式:适配器模式,装饰器模式,代理模式,外观模式。
行为型模式:观察者模式,迭代器模式,状态模式。
不常用设计模式
创建型模式:原型模式。
结构性模式:桥接模式,组合模式,享元模式。
行为型模式:职责链模式,命令模式,备忘录模式,中介者模式,访问者模式,解释器模式。
设计模式遵循7大原则:
1,开闭原则:对扩展开放,对修改关闭。
2,里氏转换原则: 子类继承父类,单独掉完全可以运行。
3,依赖倒转原则: 引用一个对象,如果这个对象有底层类型,直接引用底层。
4,接口隔离原则:每个接口应该是一个角色。
5,合成聚合复用原则:新的对象应该使用一些已有的对象,使之成为新对象的一部分。
6,迪米特原则:一个对象应对其他对象有尽可能少得了解。
7,单一职责原则:一个类负责一件职责 其逻辑肯定比负责多项职责简单的多。

我的画图项目中的部分模式使用

工厂模式

定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型。
ps:商店要封装好画矩形和画圆的工作,工厂对他们进行生产,我们只管对工厂要矩形还是圆。

// 产品矩形框
class Rect {
    constructor(props){
        this.props = props
        this.render()
    }
    render() {
        console.log('绘画矩形')
    }
}
// 产品圆框
class Arc{
    constructor(){
        this.render()
    }
    render() {
        console.log('绘画矩形')
    }
}

// 定义一个生产框的工厂
function frame(name) {
    switch(name){
        case 'rect':
            return new Rect()
        case 'arc':
            return new Arc()
    }
}
frame('rect')

jQuery例子:

class Jquery {
    constructor(selector) {
        //  ...逻辑
    }
    //  ...逻辑
}
window.$ = function(selector) {
    return new Jquery(selector)
}

React.createElement例子:

class Vnode {
    constructor(tag, attrs, children){
        // ...
    }
    // ...
}
React.createElement = function(tag, attrs, children) {
    return new Vnode(tag, attrs, children)
}

let profile = React.createElement("div", null, React.createElement("img",{src:'图片.png'},))

单例模式

单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。
ps:登录框,无论点击多少次,弹窗只应该被创建一次。

class LoginForm {
    constructor(){
        this.state = 'hide'
    }
    show() {
        if (this.state === 'show') {
            console.log('已经显示')
            return
        }
        this.state = 'show'
        console.log('已经显示')
    }
    hide() {
        if (this.state === 'hide') {
            console.log('已经隐藏')
            return
        }
        this.state = 'hide'
        console.log('已经隐藏')
    }
}
//  确保只实例一次
let getInstance = (function(){
    let instance
    return function() {
        if (!instance) {
            instance = new LoginForm()
        }
        return instance
    }
})()
let obj1 = getInstance()
let obj2 = getInstance()
console.log(obj1 === obj2)

jQuery里面只有一个$

if (window.jQuery != null) {
    return window.jQuery
} else {
    // 初始化...
}

适配器模式

将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一些工作。
ps: 想使用功能,但是原有的功能不能用了。封装旧接口处理。

ajax({
    url:'/getData',
    type: 'Get',
    dataType: 'json',
    data: {
        id: "123"
    }
}).then((res)=>{})

// 由于历史原因,以前代码中全都是用$.ajax({...})方法

// 做一层适配器
var $ = {
    ajax: (option) => {
        return ajax(option)
    }
}

又如vue computer


vue computer

装饰器模式

提供比继承更有弹性的替代方案。装饰者用于包装同接口的对象,不仅允许你向方法添加行为,而且还可以将方法设置成原始对象调用。
ps:想使用功能,但是不满足现有的功能,给扩展。加手机壳,外壳不影响手机原有的功能(如拍照)的使用。

class Circle {
    draw() {
        console.log('画一个圆形')
    }
}

class Decorator {
    constructor(circle) {
        this.circle = circle
    }
    draw() {
        this.circle.draw()
        this.setBorder(this.circle)
    }
    setBorder(circle) {
        //...处理circle
        console.log('把圆的边设成红色')
    }
}

let circle = new Circle()

let dec = new Decorator(circle)
dec.draw()

又如ES7 装饰器

function decorator(isDec) {
    return function(target){
        target.isDec = isDec
    }
}
@decorator(true)
class Demo {
    // ...
}
console.log(Demo.isDec)

代理模式

为其他对象提供一种代理以控制对这个对象的访问
ps:想使用功能,但是无权使用,让别人来帮做。A想送花给B,但是又不敢去,委托C去送。

class B{
    constructor(name) {
        this.name = name
    }
}

class A {
    constructor(b) {
        this.b = b
    }
    sendGift(gift) {
        console.log(`给${this.b.name}送${gift}`)
    }
}
class C {
    constructor(b) {
        this.b = b
    }
    sendGift(gift) {
        let a = new A(this.b)
        a.sendGift('花')
    }
}
let girl = new B('小姐姐')
let proxy = new C(girl)
proxy.sendGift()

又如,ES6 Proxy,网页的事件代理。

外观模式

为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层接口,这个接口值得这一子系统更加容易使用。

ps:
image.png
function bindEvent(elem, type, selector, fn) {
    if (!fn) {
        fn = selector
        selector = null
    }
}
bindEvent(elem, 'click', '#div', fn)
bindEvent(elem, 'click', fn)

观察者模式

又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。
ps:班长要去通知班里的某些人一些事情,与其一个一个的手动调用触发的方法(私下里一个一个通知),不如维护一个列表(建一个群),这个列表存有你想要调用的对象方法(想要通知的人);之后每次通知事件的时候只要循环执行这个列表就好了(群发),而不用关心这个列表里有谁。

// 被观察者(发布者)
class Subject {
    constructor(){
        this.state = 0
        this.observers = []  //  需要更新的列表
    }
    getState(){
        return this.state
    }
    setState(state){
        this.state = state
        this.notifyAllObservers()
    }
    notifyAllObservers(){
        this.observers.forEach(observer => {
            observer.update()
        })
    }
    attach(observer){
        this.observers.push(observer)
    }
}

// 观察者(订阅者)
class Observer {
    constructor(name, subject) {
        this.name = name
        this.subject = subject
        this.subject.attach(this)
    }
    update() {
        console.log(`${this.name} updatae`)
    }
}

let s = new Subject()
let o1 = new Observer('o1', s)

s.setState(1)

其他场景如:vue和react组件的生命周期触发,vue watch,网页事件绑定,node自定义事件。

迭代器模式(Iterator)

提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象内部表示。
ps: ES6 Iterator

/*
<div>
    <p>1</p>
    <p>2</p>
    <p>3</p>
 </div>
*/
// ES6 Iterator 示例
let arr = [1, 2, 3, 4]
let nodeList = document.getElementsByTagName('p')
let m = new Map()
m.set('a', 100)
m.set('b', 200)
// 显然三个遍历不能以一种方式变遍历,我们使用迭代器Iterator

function each(data){
    // 生成遍历器
    let iterator = data[Symbol.iterator]
    // iterator.next() 返回有数据时{value: 1, done: false},无数据时{value: undefine, done: true}

    let item = {done: false}
    while (!item.done){
        item = iterator.next()
        if (!item.done) {
            console.log(item.value)
        }
    }
}
each(arr)
eact(nodeList)
eact(m)

// Symbol.iterator并不是大家都知道的,并且还要去封装each方法。
// 因此有了iterator的语法糖 for...of,对上面each方法修改。
function each(data) {
    for(let item of data) {
        console.log(item)
    }
}

状态模式

允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类。它主要有两个角色组成:
1.环境类:拥有一个状态成员,可以修改其状态并作出相应反应。
2.状态类:表示一种状态,包含其相应的处理方法。
ps:一个对象有状态变化,状态多的时候,每次状态变化都会触发一个逻辑,不能总是if...else来控制。每个状态都只可以做当前状态才可以做的事情。如交通信号灯不同颜色的变化,红绿黄之间的变化 。

// 状态类 传入红灯,绿,黄
class State {
    constructor(color) {
        this.color = color
    }
    handle(context) {
        context.setState(this)
    }
}

// 环境类
class Context {
    constructor() {
        this.state = null
    }
    getState() {
        return this.state
    }
    setState(state) {
        this.state = state
          //....
    }
}

let context = new Context()
// 亮绿灯
let lightGreen = new State('green')
lightGreen.handle(context) // 修改其状态并作出相应反应
console.log(content.getState())
// ...

其他场景如:javascript-state-machine状态机的使用,promise的pending,fullfilled,rejected。

原型模式

原型模式(prototype)是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象。
ps:clone自己,生成一个新对象。
Object.create的应用

let prototype = {
    getName: function() {
        console.log(this.first, this.last)
    }
}

let x = Object.create(prototype, {
    id: {
        value: '111',
        enumerable: true
    }
})
x.first = 'A'
x.last = 'B'
x.getName()

let y = Object.create(prototype)
y.first = 'C'
y.last = 'D'
y.getName()

桥接模式

桥接模式(Bridge)将抽象部分与它的实现部分分离,使它们都可以独立地变化。
ps:用于把抽象化和实现化解耦


image.png
class Color{
    constructor(name) {
        this.name = name
    }
}
class Shape{
    constructor(name, color) {
        this.name = name
        this.color = color
    }
    draw() {
        console.log(this.color.name, this.name)
    }
}
let red = new Color('red')
let yellow = new Color('yellow')
let circle = new Shape('circle', red)
circle.draw()
let triangle = new Shape('triangle', yellow)
triangle.draw()

组合模式

组合模式(Composite)将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。
ps:生成树形结构,表示整体-部分关系,让整体和部分都具有一致的操作。


image.png
// 文件夹-整体
class Folder {
    constructor(folder) {
        this.folder = folder
        this.lists = []
    }
    add(file) {
        this.lists.push(file)
    }
    scan() {
        console.log('文件夹:', this.folder)
        for(let i = 0, l = this.lists.length; i < l; i++) {
            this.lists[i].scan()
        }
    }
}
// 文件-部分
class File {
    constructor(file) {
        this.file = file
    }
    add(){
        console.log('文件不能添加子文件')
    }
    scan(){
        console.log('件夹:', this.file)
    }
}
let folder = new Folder('根文件夹')
let folder1 = new Folder('JS')
let folder2 = new Folder('story')

let file = new File('深入React技术栈.pdf')
let file2 = new File('前端架构从入门到微前端')
let file3 = new File('安徒生童话故事')

folder1.add(file)
folder1.add(file2)

folder2.add(file3)

folder.add(folder1)
folder.add(folder2)

folder.scan()
console.log(folder)

策略模式

策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户。
ps:不同策略分开处理,避免出现大量if...else或switch...case。

class User {
    constructor(type){
        this.type = type
    }
    buy(){
        switch (this.type) {
            case 'ordinary':
                console.log('普通用户购买')
                break;
            case 'member':
                console.log('会员用户购买')
                break;
            case 'vip':
                console.log('vip用户购买')
                break;
            default:
                break;
        }
    }
}
let m1 = new User('ordinary')
m1.buy()
let m2 = new User('member')
m2.buy()
let m3 = new User('vip')
m3.buy()

// 使用策略模式修改
class Orfinary{
    buy(){
        console.log('普通用户购买')
    }
}
class Member{
    buy(){
        console.log('会员用户购买')
    }
}
class Vipuser{
    buy(){
        console.log('vip用户购买')
    }
}
let m1 = new Orfinary()
m1.buy()
let m2 = new Member()
m2.buy()
let m3 = new Vipuser()
m3.buy()

职责链模式

职责链模式是使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。
ps:链中收到请求的对象要么亲自处理它,要么转发给链中的下一个候选者。

class Actoin{
    constructor(name) {
        this.name = name
        this.nextAction = null
    }
    setNextAction(action) {
        this.nextAction = action
    }
    handle() {
        console.log(`${this.name} 审批`)
        if (this.nextAction != null) {
            this.nextAction.handle()
        }
    }
}
let a1 = new Actoin('组长')
let a2 = new Actoin('经理')
let a3 = new Actoin('总监')

a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()

又如, js中的链式操作,promise.then的链式操作。

命令模式

命令模式的定义是:用于将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及执行可撤销的操作。也就是说改模式旨在将函数的调用、请求和操作封装成一个单一的对象,然后对这个对象进行一系列的处理。此外,可以通过调用实现具体函数的对象来解耦命令对象与接收对象。
ps:执行命令时,发布者和接受者分开。


image.png
// 执行者
class Receiver {
    exec(){
        console.log('执行')
    }
}
// 命令对象
class Command {
    constructor(receiver) {
        this.receiver = receiver
    }
    cmd() {
        console.log('触发命令')
        this.receiver.exec()
    }
}
// 发布者
class Invoker {
    constructor(command) {
        this.command = command
    }
    invoke() {
        console.log('开始')
        this.command.cmd()
    }
}
// 士兵
let soldier = new Receiver()
// 小号手
let trumpeter = new Command(soldier)
// 将军
let general = new Invoker(trumpeter)
general.invoke()

备忘录模式

撤销功能我们不由得想起备忘录模式。在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。

// 状态备忘录
class Memento {
    constructor(content) {
        this.content = content
    }
    getContent() {
        return this.content
    }
}

// 备忘录表
class CareTaker {
    constructor() {
        this.list = []
    }
    add(memento){
        this.list.push(memento)
    }
    get(index) {
        return this.list[index]
    }
}
// 编辑器
class Editor{
    constructor() {
        this.content = null
    }
    setContent(content){
        this.content = content
    }
    getContent(){
        return this.content
    }
    saveContentToMemento(){  
        return new Memento(this.content)
    }
    getContentFromMemento(memento){
        this.content = memento.getContent(memento)
    }
}

let editor = new Editor()
let careTaker = new CareTaker()

// 编辑器设置一条数据
editor.setContent('111')
// 存储备忘录
careTaker.add(editor.saveContentToMemento()) // 存进去的是Memento的实例

// 编辑器设置第二条数据
editor.setContent('222')
// 存储备忘录
careTaker.add(editor.saveContentToMemento())

// 撤销操作
editor.getContentFromMemento(careTaker.get(0))

中介者模式

用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
ps:买房租房中的中介


image.png
class Mediator {
    constructor(a, b) {
        this.a = a
        this.b = b
    }
    setA() {
        let number = this.b.number
        this.a.setNumber(number*100)
    }
    setB() {
        let number = this.a.number
        this.b.setNumber(number/100)
    }
}
class A {
    constructor(){
        this.number = 0
    }
    setNumber(num, m) {
        this.number = num
        if (m) {
            m.setB()
        }
    }
}
class B {
    constructor(){
        this.number = 0
    }
    setNumber(num, m) {
        this.number = num
        if (m) {
            m.setA()
        }
    }
}
let a = new A()
let b = new B()
let m = new Mediator(a, b)
a.setNumber(100, m)
console.log(a.number, b.number
b.setNumber(100, m)
console.log(a.number, b.number)

又如,网页富文本编辑器操作,浏览器封装了一个命令对象。

参考链接:
[汤姆大叔的博客]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 创建型模式 工厂模式 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设...
    liuyang7519阅读 320评论 0 2
  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,727评论 2 9
  • 创建型模式 工厂模式 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设...
    隔墙送来秋千影阅读 2,644评论 0 11
  • Iterator模式 (迭代器) 一个一个遍历 一个集合类可以遵守 Iterator 协议,并实现一个 Itera...
    SSBun阅读 1,824评论 0 15
  • 一、设计模式的分类 总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者...
    RamboLI阅读 742评论 0 1