JavaScript设计模式与开发实践(三)--设计模式篇

模板方法模式

定义:模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。

  1. 钩子方法
var Beverage = function() {};
Beverage.prototype.boilWater = function() {
    console.log('把水煮沸');
};
Beverage.prototype.brew = function() {
    throw new Error('子类必须重写 brew 方法');
};
Beverage.prototype.pourInCup = function() {
    throw new Error('子类必须重写 pourInCup 方法');
};
Beverage.prototype.addCondiments = function() {
    throw new Error('子类必须重写 addCondiments 方法');
};
Beverage.prototype.customerWantsCondiments = function() {
    return true; // 默认需要调料
};
Beverage.prototype.init = function() {
    this.boilWater();
    this.brew();
    this.pourInCup();
    if (this.customerWantsCondiments()) { // 如果挂钩返回 true,则需要调料
        this.addCondiments();
    }
};
var CoffeeWithHook = function() {};
CoffeeWithHook.prototype = new Beverage();
CoffeeWithHook.prototype.brew = function() {
    console.log('用沸水冲泡咖啡');
};
CoffeeWithHook.prototype.pourInCup = function() {
    console.log('把咖啡倒进杯子');
};
CoffeeWithHook.prototype.addCondiments = function() {
    console.log('加糖和牛奶');
};
CoffeeWithHook.prototype.customerWantsCondiments = function() {
    return window.confirm('请问需要调料吗?');
};
var coffeeWithHook = new CoffeeWithHook();
coffeeWithHook.init();
  1. 好莱坞原则
    好莱坞无疑是演员的天堂,但好莱坞也有很多找不到工作的新人演员,许多新人演员在好莱坞把简历递给演艺公司之后就只有回家等待电话。有时候该演员等得不耐烦了,给演艺公司打电话询问情况,演艺公司往往这样回答:“不要来找我,我会给你打电话。”在设计中,这样的规则就称为好莱坞原则。在这一原则的指导下,我们允许底层组件将自己挂钩到高层组件中,而高层组件会决定什么时候、以何种方式去使用这些底层组件,高层组件对待底层组件的方式,跟演艺公司对待新人演员一样,都是“别调用我们,我们会调用你”。

享元模式

定义:享元(flyweight)模式是一种用于性能优化的模式,“fly”在这里是苍蝇的意思,意为蝇量级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象
使用:使用享元模式的关键是如何区别内部状态和外部状态。可以被对象共享的属性通常被划分为内部状态,如同不管什么样式的衣服,都可以按照性别不同,穿在同一个男模特或者女模特身上,模特的性别就可以作为内部状态储存在共享对象的内部。而外部状态取决于具体的场景,并根据场景而变化,就像例子中每件衣服都是不同的,它们不能被一些对象共享,因此只能被划分为外部状态。
划分内部状态和外部状态的关键主要有以下几点:
a. 内部状态储存于对象内部。
b. 内部状态可以被一些对象共享。
c. 内部状态独立于具体的场景,通常不会改变。
d. 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。
使用场景:
a. 一个程序中使用了大量的相似对象。
b. 由于使用了大量对象,造成很大的内存开销。
c. 对象的大多数状态都可以变为外部状态。
d. 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。

 // Upload
var Upload = function(uploadType) {
    this.uploadType = uploadType;
};
// Delete
Upload.prototype.delFile = function(id) {
    uploadManager.setExternalState(id, this); // (1)
    if (this.fileSize < 3000) {
        return this.dom.parentNode.removeChild(this.dom);
    }
    if (window.confirm('确定要删除该文件吗? ' + this.fileName)) {
        return this.dom.parentNode.removeChild(this.dom);
    }
};
// UploadFactory
var UploadFactory = (function() {
    var createdFlyWeightObjs = {}; // 享元对象
    return {
        create: function(uploadType) {
            if (createdFlyWeightObjs[uploadType]) {
                return createdFlyWeightObjs[uploadType];
            }
            return (createdFlyWeightObjs[uploadType] = new Upload(uploadType));
        },
    };
})();
// uploadManager
var uploadManager = (function() {
    // 保存所有 upload 对象的外部状态,以便在程序运行过程中给upload 共享对象设置外部状态
    var uploadDatabase = {};
    return {
        add: function(id, uploadType, fileName, fileSize) {
            var flyWeightObj = UploadFactory.create(uploadType);
            var dom = document.createElement('div');
            dom.innerHTML =
                '<span>文件名称:' +
                fileName +
                ', 文件大小: ' +
                fileSize +
                '</span>' +
                '<button class="delFile">删除</button>';
            dom.querySelector('.delFile').onclick = function() {
                flyWeightObj.delFile(id);
            };
            document.body.appendChild(dom);
            uploadDatabase[id] = {
                fileName: fileName,
                fileSize: fileSize,
                dom: dom,
            };
            return flyWeightObj;
        },
        setExternalState: function(id, flyWeightObj) {
            var uploadData = uploadDatabase[id];
            for (var i in uploadData) {
                flyWeightObj[i] = uploadData[i];
            }
        },
    };
})();
var id = 0;
window.startUpload = function(uploadType, files) {
    for (var i = 0, file;
        (file = files[i++]);) {
        var uploadObj = uploadManager.add(++id, uploadType, file.fileName, file.fileSize);
    }
};
startUpload('plugin', [{
        fileName: '1.txt',
        fileSize: 1000,
    },
    {
        fileName: '2.html',
        fileSize: 3000,
    },
    {
        fileName: '3.txt',
        fileSize: 5000,
    },
]);
startUpload('flash', [{
        fileName: '4.txt',
        fileSize: 1000,
    },
    {
        fileName: '5.html',
        fileSize: 3000,
    },
    {
        fileName: '6.txt',
        fileSize: 5000,
    },
]);
  • 对象池
var toolTipFactory = (function() {
    var toolTipPool = []; // toolTip 对象池
    return {
        create: function() {
            if (toolTipPool.length === 0) { // 如果对象池为空
                var div = document.createElement('div'); // 创建一个 dom
                document.body.appendChild(div);
                return div;
            } else { // 如果对象池里不为空
                return toolTipPool.shift(); // 则从对象池中取出一个 dom
            }
        },
        recover: function(tooltipDom) {
            return toolTipPool.push(tooltipDom); // 对象池回收 dom
        }
    }
})();
var ary = [];
for (var i = 0, str; str = ['A', 'B'][i++];) {
    var toolTip = toolTipFactory.create();
    toolTip.innerHTML = str;
    ary.push(toolTip);
};
for (var i = 0, toolTip; toolTip = ary[i++];) {
    toolTipFactory.recover(toolTip);
};
for (var i = 0, str; str = ['A', 'B', 'C', 'D', 'E', 'F'][i++];) {  // 复用了'A','B'两个组件
    var toolTip = toolTipFactory.create();
    toolTip.innerHTML = str;
};

职责链模式

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

  1. 异步的职责链
var Chain = function(fn) {
    this.fn = fn;
    this.successor = null;
};
Chain.prototype.setNextSuccessor = function(successor) {
    return this.successor = successor;
};
Chain.prototype.passRequest = function() {
    var ret = this.fn.apply(this, arguments);
    if (ret === 'nextSuccessor') {
        return this.successor && this.successor.passRequest.apply(this.successor, arguments);
    }
    return ret;
};
Chain.prototype.next = function() {  // 在异步请求之后可以执行
    return this.successor && this.successor.passRequest.apply(this.successor, arguments);
};
var fn1 = new Chain(function() {
    console.log(1);
    return 'nextSuccessor';
});
var fn2 = new Chain(function() {
    console.log(2);
    var self = this;
    setTimeout(function() {
        self.next();
    }, 1000);
});
var fn3 = new Chain(function() {
    console.log(3);
});
fn1.setNextSuccessor(fn2).setNextSuccessor(fn3);
fn1.passRequest();
  1. AOP实现职责链
// 500 元订单
var order500 = function(orderType, pay, stock) {
    if (orderType === 1 && pay === true) {
        console.log('500 元定金预购, 得到 100 优惠券');
    } else {
        order200(orderType, pay, stock); // 将请求传递给 200 元订单
    }
};
// 200 元订单
var order200 = function(orderType, pay, stock) {
    if (orderType === 2 && pay === true) {
        console.log('200 元定金预购, 得到 50 优惠券');
    } else {
        orderNormal(orderType, pay, stock); // 将请求传递给普通订单
    }
};
// 普通购买订单
var orderNormal = function(orderType, pay, stock) {
    if (stock > 0) {
        console.log('普通购买, 无优惠券');
    } else {
        console.log('手机库存不足');
    }
};
// 注意这里是Function.prototype
Function.prototype.after = function(fn) {
    var self = this;
    return function() {
        var ret = self.apply(this, arguments);
        if (ret === 'nextSuccessor') {
            return fn.apply(this, arguments);
        }
        return ret;
    }
};

var order = order500yuan.after( order200yuan ).after( orderNormal );
order( 1, true, 500 ); // 输出:500 元定金预购,得到 100 优惠券
order( 2, true, 500 ); // 输出:200 元定金预购,得到 50 优惠券
order( 1, false, 500 ); // 输出:普通购买,无优惠券

// 等同于
order500yuan( 1, true, 500 ).after( order200yuan ).after( orderNormal );

中介者模式

定义:中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系

var goods = { // 手机库存
    "red|32G": 3,
    "red|16G": 0,
    "blue|32G": 1,
    "blue|16G": 6
};
// 中介者
var mediator = (function() {
    var colorSelect = document.getElementById('colorSelect'),
        memorySelect = document.getElementById('memorySelect'),
        numberInput = document.getElementById('numberInput'),
        colorInfo = document.getElementById('colorInfo'),
        memoryInfo = document.getElementById('memoryInfo'),
        numberInfo = document.getElementById('numberInfo'),
        nextBtn = document.getElementById('nextBtn');
    return {
        changed: function(obj) {
            var color = colorSelect.value, // 颜色
                memory = memorySelect.value, // 内存
                number = numberInput.value, // 数量
                stock = goods[color + '|' + memory]; // 颜色和内存对应的手机库存数量
            if (obj === colorSelect) { // 如果改变的是选择颜色下拉框
                colorInfo.innerHTML = color;
            } else if (obj === memorySelect) {
                memoryInfo.innerHTML = memory;
            } else if (obj === numberInput) {
                numberInfo.innerHTML = number;
            }
            if (!color) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '请选择手机颜色';
                return;
            }
            if (!memory) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '请选择内存大小';
                return;
            }
            if (((number - 0) | 0) !== number - 0) { // 输入购买数量是否为正整数
                nextBtn.disabled = true;
                nextBtn.innerHTML = '请输入正确的购买数量';
                return;
            }
            nextBtn.disabled = false;
            nextBtn.innerHTML = '放入购物车';
        }
    }
})();
// 事件函数:
colorSelect.onchange = function() {
    mediator.changed(this);
};
memorySelect.onchange = function() {
    mediator.changed(this);
};
numberInput.oninput = function() {
    mediator.changed(this);
};

装饰者模式

定义:能够动态地添加职责,装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。

  1. 普通的js装饰者
var plane = {
    fire: function () {
        console.log('发射普通子弹');
    },
};
var missileDecorator = function () {
    console.log('发射导弹');
};
var atomDecorator = function () {
    console.log('发射原子弹');
};
var fire1 = plane.fire;
plane.fire = function () {
    fire1();
    missileDecorator();
};
var fire2 = plane.fire;
plane.fire = function () {
    fire2();
    atomDecorator();
};
plane.fire();
// 分别输出: 发射普通子弹、发射导弹、发射原子弹
  1. 用AOP装饰函数
Function.prototype.before = function(beforefn) {
    var __self = this; // 保存原函数的引用
    return function() { // 返回包含了原函数和新函数的"代理"函数
        beforefn.apply(this, arguments); // 执行新函数,且保证 this 不被劫持,新函数接受的参数
        // 也会被原封不动地传入原函数,新函数在原函数之前执行
        return __self.apply(this, arguments); // 执行原函数并返回原函数的执行结果,
        // 并且保证 this 不被劫持
    }
}
Function.prototype.after = function(afterfn) {
    var __self = this;
    return function() {
        var ret = __self.apply(this, arguments);
        afterfn.apply(this, arguments);
        return ret;
    }
};

window.onload = function() {
    alert(1);
}
window.onload = (window.onload || function() {}).after(function() {
    alert(2);
}).after(function() {
    alert(3);
}).after(function() {
    alert(4);
});
  1. 用AOP动态改变函数的参数
Function.prototype.before = function(beforefn) {
    var __self = this;
    return function() {
        beforefn.apply(this, arguments); // (1)
        return __self.apply(this, arguments); // (2)
    }
}

var func = function(param) {
    console.log(param); // 输出: {a: "a", b: "b"}
}
func = func.before(function(param) {
    param.b = 'b';
});
func({
    a: 'a'
});
  1. 用AOP进行表单验证
Function.prototype.before = function(beforefn) {
    var __self = this;
    return function() {
        if (beforefn.apply(this, arguments) === false) {
            // beforefn 返回 false 的情况直接 return,不再执行后面的原函数
            return;
        }
        return __self.apply(this, arguments);
    }
}
var validata = function() {
    if (username.value === '') {
        alert('用户名不能为空');
        return false;
    }
    if (password.value === '') {
        alert('密码不能为空');
        return false;
    }
}
var formSubmit = function() {
    var param = {
        username: username.value,
        password: password.value
    }
    ajax('http:// xxx.com/login', param);
}
formSubmit = formSubmit.before(validata);
submitBtn.onclick = function() {
    formSubmit();
}

状态模式

定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。(第一部分的意思是将状态封装成独立的类,并将请求委托给当前的状态对象,当对象的内部状态改变时,会带来不同的行为变化。第二部分是从客户的角度来看,我们使用的对象,在不同的状态下具有截然不同的行为,这个对象看起来是从不同的类中实例化而来的,实际上这是使用了委托的效果。)
要点:状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。

  1. 电灯案例
// OffLightState:
var OffLightState = function (light) {
    this.light = light;
};
OffLightState.prototype.buttonWasPressed = function () {
    console.log('弱光'); // offLightState 对应的行为
    this.light.setState(this.light.weakLightState); // 切换状态到 weakLightState
};
// WeakLightState:
var WeakLightState = function (light) {
    this.light = light;
};
WeakLightState.prototype.buttonWasPressed = function () {
    console.log('强光'); // weakLightState 对应的行为
    this.light.setState(this.light.strongLightState); // 切换状态到 strongLightState
};
// StrongLightState:
var StrongLightState = function (light) {
    this.light = light;
};
StrongLightState.prototype.buttonWasPressed = function () {
    console.log('关灯'); // strongLightState 对应的行为
    this.light.setState(this.light.offLightState); // 切换状态到 offLightState
};

var Light = function () {
    // this指向light实例,可以在状态类中控制灯
    this.offLightState = new OffLightState(this); // 持有状态对象的引用
    this.weakLightState = new WeakLightState(this);
    this.strongLightState = new StrongLightState(this);
    this.button = null;
};

Light.prototype.init = function () {
    var button = document.createElement('button'),
        self = this;
    this.button = document.body.appendChild(button);
    this.button.innerHTML = '开关';
    this.currState = this.offLightState; // 设置当前状态
    this.button.onclick = function () {
        self.currState.buttonWasPressed();
    };
};

Light.prototype.setState = function (newState) {
    this.currState = newState;
};

var light = new Light();
light.init();

适配器模式(也叫包装器)

作用:解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。

上一篇:golang命令模式


下一篇:Lua 问题 持久更新