Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

javascript设计模式摘要 #20

Open
UNDERCOVERj opened this issue Jan 5, 2019 · 1 comment
Open

javascript设计模式摘要 #20

UNDERCOVERj opened this issue Jan 5, 2019 · 1 comment

Comments

@UNDERCOVERj
Copy link
Owner

[TOC]

单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。应用:显示登录浮窗

var Singleton = function(name) {
    this.name = name;
    this.instance = null;
}
Singleton.prototype.sayName = funciton() {

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

var a = Singleton.getInstance('a');
var b = Singleton.getInstance('b');

console.log(a === b) // true

透明的单例模式

实现一个透明的单例类,用户从这个类中创建对象的时候,可以像使用其他类一样

var CreateDiv = (function() {
    var instance;
    var CreateDiv = function() {
        if (instance) {
            return instance;
        }
        this.html = html;
        this.init();
        return instance = this;
    }
    CreateDiv.prototype.init = function() {
        var div = document.createElement('div');
        div.innerHTML = this.html;
        document.body.appendChild(div);
    }
    return CreateDiv
})();

用代理实现单例模式

var CreateDiv = function() {
    this.html = html;
    this.init();
}
CreateDiv.prototype.init = function() {
    var div = document.createElement('div');
    div.innerHTML = this.html;
    document.body.appendChild(div);
}
return CreateDiv

var ProxySingleton = (function(html) {
    var istance;
    if (!instance) {
        instance = new CreateDiv(html);
    }
    return instance
})()

创建方式:

  1. 命名空间
var MyApp = {

};

MyApp.namespace = function(name) {
    var parts = name.split('.');
    var current = MyApp;
    for (var i = 0, len = parts.length; i < len; i++) {
        if (!current[parts[i]]) {
            current[parts[i]] = {}
        }
        current = current[parts[i]];
    }
}

MyApp.namespace('event');
MyApp.namespace('dom.style');

// MyApp = {
//     event: {},
//     dom: {
//         style: {}
//     }
// }
  1. 闭包方式

惰性单例

采用事件机制,来创建,但是不符合单一职责原则,如果要创建其他标签,就得重写

var createDiv = (function() {
    var div = null;
    return function() {
        if (!div) {
            div = document.createElement('div');
            div.innerHTML = 'div';
            div.style.display = 'none';
            document.appendChild(div);
            return div;
        }
        return div;
    }
})();

document.querySelector('btn').onclick = function() {
    var player = createDiv();
    player.style.display = 'block'
}

通用的惰性单例

var getSingle = function(fn) {
    var result;
    return result || result = fn.apply(this, arguments)
}

策略模式

定义一系列算法,并将它们封装成策略类,算法被封装在策略类内部的方法里,使它们可以相互替换。在客户对Context发起请求时,Context总是将请求委托给这些策略对象中间的某一个计算

var performanceS = function() {

}
performanceS.prototype.calculate = function(salary) {
    return salary*4
}

var performanceA = function() {

}
performanceA.prototype.calculate = function(salary) {
    return salary*4
}

var Bonus = function() {
    this.salary = null;
    this.strategy = null;
}
Bonus.prototype.setSalary = function(salary) {
    this.salary = salary;
}
Bonus.prototype.setStrategy = function(strategy) {
    this.strategy = strategy;
}
Bonus.prototype.getBonus = function() {
    return this.strategy.calculate(this.salary);
}

优化方案:

体现了多态的特性

var strategies = {
    S: function(salary) {
        return salary*4
    },
    A: function(salary) {
        return salary*3
    }
}

var calculateBonus = function(level, salary) {
    return strategies[level](salary)
}

应用:

  1. 实现动画
  2. 表单检验

优点:

  1. 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
  2. 策略模式提供了对开发-封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换、理解、扩展
  3. 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
  4. 策略模式中利用组合和委托来让context拥有执行算法的能力,这也是继承的一种更轻便的方案。

缺点:

  1. 使用策略模式会在程序中增加许多策略类或者策略对象
  2. 必须了解所有的strategy

代理模式

当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。

保护代理

代理B帮助代理A过滤掉一些请求

虚拟代理

把一些开销很大的对象,延迟到真正需要它的时候才去创建。

var myImage = (function() {
    var img = new Image();
    document.appendChild(img)
    return {
        setSrc: function(src) {
            img.src = src;
        }
    }
})()

var proxyImage = (function() {
    var img = new Image();
    img.onload = function() {
        myImage.setSrc(this.src);
    }
    return {
        setSrc: function(src) {
            myImage.setSrc = 'loading.gif';
            img.src = src;
        }
    }
})()

proxyImage.setSrc('real-img.jpg')

好处

  1. 用户不清楚代理和本体的区别,用户可以放心的请求代理,他只关心是否能得到想要的效果
  2. 在任何地方本体都可以替换成代理

缓存代理

// 缓存代理,通过传入方法,也可以为各种计算进行代理

var mult = function() {
    var result = 1;
    for (var i = 0, len = arguments.length; i < len; i++) {
        result *= arguments[i];
    }
    return;
}

var proxyMult = (function() {
    var cache = {};
    return function() {
        var args = Array.prototype.join.call(arguments, ',');
        if (args in cache) {
            return cache[args];
        }
        return cache[args] = mult.apply(this, arguments);
    }
})();

proxyMult(2, 3, 4);

还能用于ajax异步请求数据,比如:分页。

其它代理

  1. 防火墙代理
  2. 远程代理
  3. 保护代理
  4. 智能引用
  5. 写时复制代理

迭代器模式

提供一个方法有序的访问一个聚合对象的每一个元素,又不用关心对象的内部构造

实现一个迭代器

var each = function(arr, callback) {
    for (var i = 0, len = arr.length; i < len; i++) {
        callback.call(arr[i], i, arr[i]);
    }
}

内部迭代器

上述的each函数,就是一个内部迭代器,each函数内部已经定义好了规则

外部跌代器

显示地跌代下一个元素

function Iterator(arr) {
    let index = 0;
    function next() {
        index++;
    }
    function isDone() {
        return index >= arr.length;
    }
    function getCurrent() {
        return arr[index];
    }
    return {
        next,
        isDone,
        getCurrent
    }
}

let iterator1 = Iterator([1, 2, 3]);
let iterator2 = Iterator([1, 2, 3]);

function compare(iterator1, iterator2) {
    while (!iterator1.isDone() && !iterator2.isDone()) {
        if (iterator1.getCurrent() !== iterator2.getCurrent()) {
            throw new Error('iterator1 !== iterator2');
        }
        iterator1.next();
        iterator2.next();
    }
}

跌代类数组对象和字面量对象

区分数组

倒序迭代器

迭代器提供有序访问,所以顺序是可以设定的

中止迭代器

用break来中止迭代器

发布-订阅模式

又叫观察者模式,定义了一对多的依赖关系

作用:

  1. 大量用于异步编程
  2. 取代对象硬编码的通知机制,不再显示调用接口

实现

  1. 自定义事件
    1. 首先指定发布者
    2. 发布者用一个列表缓存回调函数来通知订阅者
    3. 发布消息时,遍历缓存列表(此列表可为数组也可为对象,因为有时可能需要key)
  2. 通用发布-订阅模式的实现
class CustomEvent {
    constructor() {
        this.events = {};
    }
    on(evName, evFn) {
        let evArr = this.events[evName] = this.events[evName] || [];
        if (!isFunction(evFn) || evArr.indexOf(evFn) > -1) {
            return false;
        }
        evArr.push(evFn);
        return true;
    }
    un(evName, evFn) {
        let evArr = this.events[evName];
        if (!evArr) {
            return false;
        }

        if (!evFn || !isFunction(evFn)) {
            this.events[evName] = null;
            return true;
        }

        let index = evArr.indexOf(evFn);
        let result = index >= 0;
        result && evArr.splice(index, 1);
        return result;
    }
    emit(evName, ...args) {
        let evArr = this.events[evName];
        if (!Array.isArray(evArr)) {
            return false;
        }
        evArr.forEach((val, index) => {
            val.apply(this, args);
        });
        return true;
    }
}

优点

  1. 时间解耦
  2. 对象解耦

缺点

  1. 模块之间用了太多的全局发布-订阅模式来通信,那模块之间的联系就隐藏到了背后,难以追踪。
  2. 创建订阅者需要消耗一定的时间和内存

必须先订阅再发布吗?

不是的,如qq离线消息。建立一个离线存放事件的堆栈,当事件发布的时候,如果此时还没有订阅者来订阅这个事件,暂时把发布事件的动作包裹在一个函数里,这些包装函数将被存入堆栈,等到有对象来订阅时,遍历堆栈执行包装函数。

避免全局事件的命名冲突

给Event对象提供创建命名空间的功能

命令模式

命令模式中的命令指的是一个执行某些特定事情的指令

应用场景

有时候需要向某些对象发送请求,但是不知道请求的接受者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合来设计程序,是的请求发送者和请求接收者能够消除彼此之间的耦合关系。

代码实例

var MenuBar = {
    refresh: function() {
        console.log('refresh')
    }
}

var SubBar = {
    add: function() {
        console.log('add')
    }
}

function RefreshMenuBarCommand(receiver) {
    this.receiver = receiver;
}

RefreshMenuBarCommand.prototype.execute = function() {
    this.receiver.refresh()
}

function AddMenuBarCommand(receiver) {
    this.receiver = receiver;
}

AddMenuBarCommand.prototype.execute = function() {
    this.receiver.add()
}

var refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);
var addMenuBarCommand = new AddMenuBarCommand(SubBar);

function setCommand(button, command) {
    button.onclick = function() {
        command.execute();
    }
}

setCommand(document.querySelector('.btn-1'), refreshMenuBarCommand)

撤销功能

提供一个undo方法,如小球移动,每次移动记录上一次位置,这样在undo方法中就能回到上一次位置。

重做功能

提供一个队列,将每次命令入队列,重做时出队

命令队列

第一个完成再进行第二个

宏命令

一组命令的集合

var Macro = function() {
    execute: function() {
        for (var i = 0; i < this.list.length; i++) {
            this.list[i].execute();
        }
    }
}

组合模式

举例:宏命令对象和普通命令对象,采用深度优先遍历这些命令并execute

用途:

  1. 表示属性结构
  2. 利用对象多态性统一对待组合对象和单个对象

注意:

  1. 组合模式不是父子关系
  2. 对叶对象的操作一致性
  3. 双向映射关系
  4. 用职责连模式提高组合模式性能

好处:

  1. 透明,不用去估计树中组合对象和叶对象的区别
  2. 表示对象的部分-整体层次

模板方法模式

抽象类,需要继承实现的模式,通过封装变化提高系统的扩展性。

重写方法,通过封装变化提高系统扩展性的设计模式

享元模式

用于性能优化,节省内存开销,减少对象建立

目标:将对象的属性划分为内部状态与外部状态,尽量减少共享对象的数量

剥离外部状态,并把外部状态保存在其他地方,在合适的时刻
再把外部状态组装进共享对象

关键点

把握好内部状态和外部状态来避免多次建立对象。

共享是重点,用时间来换空间

职责链模式

定义:

使多个对象都能处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

Function.prototype.after = function(fn) {
    var self = this;
    return function() {
        console.log(self)
        console.log(fn)
        var ret = self.apply(this, arguments);
        if (ret === 'nextSuccessor') {
            return fn.apply(this, arguments);
        }
        return ret;
    }
}

function func1() {
    console.log('1');
    return 'nextSuccessor';
}

function func2() {
    console.log('2');
    return 'nextSuccessor';
}

function func3() {
    console.log('3');
    return 'nextSuccessor';
}

function func4() {
    console.log('3')
}


func1.after(func2).after(func3).after(func4)();

中介者模式

解除对象与对象之间的紧耦合关系

function Player(name, teamColor) {
    this.name = name;
    this.teamColor = teamColor;
    this.state = 'alive';
}
Player.prototype.die = function() {
    playerDirector.receiveMessage('playerDead', this);
}

var playerDirector = {
    receiveMessage: function(name, player) {
        console.log(player.name + ' ' + name);
    }
}

var player1 = new Player('bob', 'red');
player1.die();

装饰者模式

在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责

function Plane() {

}
Plane.prototype.fire = function() {
    console.log('发射子弹');
}

function MisslePlane(plane) {
    this.plane = plane;
}
MisslePlane.prototype.fire = function() {
    this.plane.fire();
    console.log('发射导弹')
}

function AtomPlane(plane) {
    this.plane = plane;
}
AtomPlane.prototype.fire = function() {
    this.plane.fire();
    console.log('发射原子弹');
}

var plane = new Plane();
plane = new MisslePlane(plane);
plane = new AtomPlane(plane);
plane.fire();

问题:

  1. this被劫持
Function.prototype.before = function(beforeFn) {
    var self = this;
    return function() {
        beforeFn.apply(self, arguments);
        return self.apply(self, arguments);
    }
}

Function.prototype.after = function(afterFn) {
    var self = this;
    return function() {
        var ret = self.apply(self, arguments);
        afterFn.apply(self, arguments);
        return ret;
    }
}

function one() {
    console.log('one');
}
function two() {
    console.log('two');
}
function three() {
    console.log('three');
}

two.before(one).after(three)();

状态模式

允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

function Light() {
    this.offLight = new OffLight(this);
    this.weakLight = new WeakLight(this);
    this.strongLight = new StrongLight(this);
}
Light.prototype.init = function() {
    var button = document.createElement('button');
    button.innerHTML = 'press';
    var self = this;
    this.curLight = this.offLight;
    button.onClick = function() {
        self.curLight.buttonWasPressed();
    }
    document.body.appendChild(button);
}
Light.prototype.setState = function(light) {
    this.curLight = light;
}

function OffLight(light) {
    this.light = light;
}
OffLight.prototype.buttonWasPressed = function() {
    console.log('弱光');
    this.light.setState(this.light.weakLight);
}

function WeakLight(light) {
    this.light = light;
}
WeakLight.prototype.buttonWasPressed = function() {
    console.log('强光');
    this.light.setState(this.light.strongLight);
}

function StrongLight(light) {
    this.light = light;
}
StrongLight.prototype.buttonWasPressed = function() {
    console.log('无光');
    this.light.setState(this.light.offLight);
}

优缺点

优点:

  1. 状态模式定义了状态与行为之间的关系,并将它们封装在一个类里。通过增加新的状态类,很容易增加新的状态和转换。
  2. 避免Context无限膨胀,状态切换的逻辑被分布在状态类中,也去掉了Context 中原本过多的条件分支。
  3. 用对象代替字符串来记录当前状态,使得状态的切换更加一目了然。
  4. Context中的请求动作和状态类中封装的行为可以非常容易地独立变化而互不影响。

缺点:

  1. 在系统中定义许多状态类,编写20 个状态类是一项枯燥乏味的工作,
    而且系统中会因此而增加不少对象。另外,由于逻辑分散在状态类中,虽然避开了不受欢迎的条件分支语句,但也造成了逻辑分散的问题,我们无法在一个地方就看出整个状态机的逻辑。

适配器模式

  1. 适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实现的,也不考虑它们将来可能会如何演化。适配器模式不需要改变已有的接口,就能够使它们协同作用。
  2. 装饰者模式和代理模式也不会改变原有对象的接口,但装饰者模式的作用是为了给对象增加功能。装饰者模式常常形成一条长的装饰链,而适配器模式通常只包装一次。代理模式是为了控制对对象的访问,通常也只包装一次。

外观模式

为一组子系统提供一个简单便利的访问入口。

隔离客户与复杂子系统之间的联系,客户不用去了解子系统的细节。

设计原则

单一职责原则、里氏替换原则、依赖倒置原则、接口隔离原则、合成复用原则和最少知识原则。

单一职责原则(SRP)

如果两个职责是同时变化的,那就不必分离

优点:降低单个类或者对象的复杂度,按照职责把对象分解成更小的粒度,这有助于代码的复用,也有利于进行单元测试。

缺点:增加了对象间联系的复杂度

体现:代理模式、迭代器模式、单例模式、装饰者模式

最小知识原则(LKP)

是一个软件实体应当尽可能少地与其他实体发生相互作用

减少对象之间的联系

体现:中介者模式、外观模式

开放封闭原则(OCP)

软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改。

  1. 放置挂钩
  2. 使用回调函数

体现:发布-订阅模式、模板方法模式、策略模式、代理模式

接口和面向接口编程

接口是对象能响应的请求的集合。

  1. 第一种接口:

通过主动暴露的接口来通信,可以隐
藏软件系统内部的工作细节

  1. 第二种接口:

语言提供的关键字

  1. 第三种接口:

面向接口编程

抽象类的作用:

  1. 向上转型(抽象类)
  2. 建立一些契约(覆写方法)

代码重构

  1. 提炼函数
  2. 合并重复的条件判断
  3. 把条件语句提炼成函数
  4. 合理使用循环
  5. 提前让函数退出代替嵌套条件分支
  6. 传递对象参数代替过长的参数列表
  7. 尽量减少参数数量
  8. 少用三目运算符
  9. 合理使用链式调用
  10. 分解大型类
  11. 用return 退出多重循环
@wython
Copy link

wython commented Aug 16, 2019

真正的博客,学习了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants