由于JavaScript不是典型的面向?qū)ο笳Z言,因而在實(shí)現(xiàn)一些經(jīng)典的設(shè)計(jì)模式上也與一般語言存在差異,本文主要介紹在JavaScript中如何實(shí)現(xiàn)常用的設(shè)計(jì)模式。
1. 單例模式
單例模式是最常見的設(shè)計(jì)模式,在一般的OOP語言中痘拆,我們可以通過私有化構(gòu)造函數(shù)實(shí)現(xiàn)單例模式。但由于單例模式實(shí)際上可以看做返回的是一個(gè)結(jié)構(gòu)氮墨,該結(jié)構(gòu)在內(nèi)存中有且僅有唯一的一份错负,所以可以類比JavaScript中的閉包,所以可以記住閉包完成單例模式的實(shí)現(xiàn):
// 單例模式
var mySingleton = (function(){
var instance;
init = function() {
var privateVar = "privateVar";
privateFunc = function() {
console.log("This is private func");
};
return {
publicVar: 'public var', // 公共變量
publicFunc: function() { // 公共方法
console.log('This is public func');
},
getPrivateVar: function() {
return privateVar;
}
}
};
return {
getInstance: function() {
if (!instance) {
instance = init();
}
return instance;
}
}
})();
var singleton1 = mySingleton.getInstance();
var singleton2 = mySingleton.getInstance();
singleton1.publicFunc();
console.log(singleton1 === singleton2);
2. 觀察者模式
觀察者模式下存在兩個(gè)成員:觀察者和被觀察者勇边。觀察者在被被觀察者處進(jìn)行注冊(cè),當(dāng)被觀察者相關(guān)狀態(tài)發(fā)生變化時(shí)折联,被觀察者告知觀察者粒褒,同時(shí)觀察者執(zhí)行相應(yīng)更新邏輯。通常來說诚镰,存在多個(gè)觀察者觀察同一個(gè)被觀察者的情況奕坟。在觀察者模式下,存在以下幾個(gè)組件:
- 被觀察者:維護(hù)一組被觀察接口清笨,用于添加月杉、刪除觀察者,通知觀察者
- 觀察者:維護(hù)一組觀察者接口抠艾,用于在被觀察者狀態(tài)發(fā)生變化時(shí)苛萎,通知到觀察者
- 具體的被觀察者:實(shí)現(xiàn)被觀察者接口
- 具體的觀察者:實(shí)現(xiàn)觀察者接口
// 觀察者模式:建立觀察者/被觀察者關(guān)系,觀察者可以注冊(cè)其觀察對(duì)象(被觀察者)检号,當(dāng)被觀察者的狀態(tài)發(fā)生改變時(shí)腌歉,可以及時(shí)通知到觀察者
// 被觀察者管理觀察者能力建模
function ObserverList() {
this.observerList = [];
}
// 添加觀察者
ObserverList.prototype.Add = function(observer) {
this.observerList.push(observer);
}
// 清空觀察者
ObserverList.prototype.Empty = function() {
this.observerList = [];
}
// 觀察者數(shù)量
ObserverList.prototype.Count = function() {
return this.observerList.length;
}
// 獲取某個(gè)觀察者
ObserverList.prototype.Get = function(index) {
if (index >= 0 && index < this.observerList.length) {
return this.observerList[index];
}
return undefined;
}
// 刪除某個(gè)觀察者
ObserverList.prototype.RemoveAt = function( index ){
if( index === 0 ){
this.observerList.shift();
}else if( index === this.observerList.length -1 ){
this.observerList.pop();
}
};
// var testObserverList = new ObserverList();
// for(var key in testObserverList) {
// console.log('key:' + key + '->' + testObserverList[key]);
// }
// 給某個(gè)對(duì)象擴(kuò)展被觀察者能力
function extend(extension, target) {
for(var key in extension) {
target[key] = extension[key];
}
}
// 創(chuàng)建被觀察者對(duì)象Subject,同時(shí)集成觀察者對(duì)象的能力
function Subject() {
this.observerList = new ObserverList();
};
Subject.prototype.AddObserver = function(observer) {
this.observerList.Add(observer)
};
Subject.prototype.RemoveObserver = function( observer ){
this.observers.RemoveAt( this.observers.IndexOf( observer, 0 ) );
};
// 通知所有觀察者
Subject.prototype.Notify = function(context) {
var count = this.observerList.Count();
for(var i = 0; i < count; i++) {
this.observerList.Get(i).Update(context);
}
};
// 構(gòu)建觀察者對(duì)象,主要是定義觀察后的處理函數(shù)
function Observer() {
this.Update = function() {
//do something
}
}
接下來我們基于觀察者模式實(shí)現(xiàn)一個(gè)例子:
- 一個(gè)按鈕齐苛,這個(gè)按鈕用于增加新的充當(dāng)觀察者的選擇框到頁面上
- 一個(gè)控制器的選擇框翘盖,充當(dāng)一個(gè)被觀察者,通知其他選擇框是否應(yīng)該被選中
- 一個(gè)容器凹蜂,用于放置新的選擇框
<body>
<button id="addNewObserver">Add New Observer checkbox</button>
<input id="mainCheckbox" type="checkbox"/>
<div id="observersContainer"></div>
</body>
<script src="./observer.js"></script> <!-- 引入上文中的js代碼 -->
<script type="text/javascript">
var controlCheckbox = document.getElementById('mainCheckbox');
var addBtn = document.getElementById('addNewObserver');
var container = document.getElementById('observersContainer');
// 給controlCheckbox擴(kuò)展被觀察者能力
extend(new Subject(), controlCheckbox);
controlCheckbox.addEventListener('click', function() {
this.Notify(this.checked);
});
// 添加觀察者
addBtn.addEventListener('click', AddNewObserver);
function AddNewObserver() {
// 創(chuàng)建一個(gè)checkbox
var check = document.createElement('input');
check.type = 'checkbox';
check.checked = controlCheckbox.checked;
// 擴(kuò)展觀察者能力
extend(new Observer(), check);
check.Update = function(checked) {
this.checked = checked;
}
//添加到controlCheckbox的觀察者列表中
controlCheckbox.AddObserver(check);
// 添加到容器區(qū)域
container.appendChild(check);
}
}
3 訂閱模式
訂閱模式和觀察者模式很類似馍驯,都是建立觀察者與被觀察者之間的消息通道阁危。觀察者模式需要觀察者顯示的調(diào)用被觀察者的觀察接口來聲明觀察關(guān)系,從而在代碼層面存在依賴關(guān)系汰瘫。而訂閱模式通過使用主題/事件頻道將訂閱者和發(fā)布者進(jìn)行解耦狂打。
// 訂閱者對(duì)象
function Subscriber() {
this.subscriberEventList = [];
}
Subscriber.prototype.addSubscribe = function(subscribe) {
this.subscriberEventList.push(subscribe);
}
// 訂閱事件對(duì)象
function Subscribe(name, callback) {
this.name = name;
this.callback = callback;
}
// 發(fā)布事件對(duì)象
function Publish(name, context) {
this.name = name;
this.context = context;
}
//訂閱中心對(duì)象
function SubscribeCenter() {
this.subscriberList = [];
}
SubscribeCenter.prototype.addSubscriber = function(subscriber) {
this.subscriberList.push(subscriber);
}
SubscribeCenter.prototype.publish = function(publisher) {
var name = publisher.name;
var context = publisher.context;
for(var i = 0; i < this.subscriberList.length; i++) {
for(var j = 0; j < this.subscriberList[i].subscriberEventList.length; j++) {
var subscribeevent = this.subscriberList[i].subscriberEventList[j];
if(subscribeevent.name === name) {
subscribeevent.callback.call(this.subscriberList[i], name, context);
}
}
}
}
function extend(extend, obj) {
for(var key in extend) {
obj[key] = extend[key];
}
}
4. 工廠模式
工廠模式的實(shí)質(zhì)由一個(gè)工廠類來代理對(duì)象(工廠模式下稱為組件)的構(gòu)造,組件遵循同一套組件接口吟吝,使用方只需按照工廠定制的標(biāo)準(zhǔn)將參數(shù)傳遞給工廠類的組件構(gòu)造函數(shù)即可菱父。工廠模式實(shí)現(xiàn)了組件使用方與組件之間的解耦,使得兩者之間不存在顯示的依賴關(guān)系剑逃,特別適合于組件眾多的情況浙宜。
// 工廠模式
// A constructor for defining new cars
function Car( options ) {
// some defaults
this.doors = options.doors || 4;
this.state = options.state || "brand new";
this.color = options.color || "silver";
}
// A constructor for defining new trucks
function Truck( options){
this.state = options.state || "used";
this.wheelSize = options.wheelSize || "large";
this.color = options.color || "blue";
}
// FactoryExample.js
// Define a skeleton vehicle factory
function VehicleFactory() {}
// Define the prototypes and utilities for this factory
// Our default vehicleClass is Car
VehicleFactory.prototype.vehicleClass = Car;
// Our Factory method for creating new Vehicle instances
VehicleFactory.prototype.createVehicle = function ( options ) {
if( options.vehicleType === "car" ){
this.vehicleClass = Car;
}else{
this.vehicleClass = Truck;
}
return new this.vehicleClass( options );
};
// Create an instance of our factory that makes cars
var carFactory = new VehicleFactory();
var car = carFactory.createVehicle( {
vehicleType: "car",
color: "yellow",
doors: 6 } );
// Test to confirm our car was created using the vehicleClass/prototype Car
// Outputs: true
console.log( car instanceof Car );
// Outputs: Car object of color "yellow", doors: 6 in a "brand new" state
console.log( car );
// 抽象工廠
var AbstractVehicleFactory = (function () {
// Storage for our vehicle types
var types = {};
return {
getVehicle: function ( type, customizations ) {
var Vehicle = types[type];
return (Vehicle ? new Vehicle(customizations) : null);
},
registerVehicle: function ( type, Vehicle ) {
var proto = Vehicle.prototype;
// only register classes that fulfill the vehicle contract
if ( proto.drive && proto.breakDown ) {
types[type] = Vehicle;
}
return AbstractVehicleFactory;
}
};
})();
// Usage:
AbstractVehicleFactory.registerVehicle( "car", Car );
AbstractVehicleFactory.registerVehicle( "truck", Truck );
// Instantiate a new car based on the abstract vehicle type
var car = AbstractVehicleFactory.getVehicle( "car" , {
color: "lime green",
state: "like new" } );
// Instantiate a new truck in a similar manner
var truck = AbstractVehicleFactory.getVehicle( "truck" , {
wheelSize: "medium",
color: "neon yellow" } );
5. Mixin模式
mixin是javascript中最為常用的一種模式,幾乎所有javascript框架都用到了mixin蛹磺。既可以將任意一個(gè)對(duì)象的全部和部分屬性拷貝到另一個(gè)對(duì)象或類上粟瞬。Mix允許對(duì)象以最小量的復(fù)雜性從外部借用(或者說繼承)功能.作為一種利用Javascript對(duì)象原型工作得很好的模式,它為我們提供了從不止一個(gè)Mix處分享功能的相當(dāng)靈活,但比多繼承有效得多得多的方式。
// Define a simple Car constructor
var Car = function ( settings ) {
this.model = settings.model || "no model provided";
this.color = settings.color || "no colour provided";
};
// Mixin
var Mixin = function () {};
Mixin.prototype = {
driveForward: function () {
console.log( "drive forward" );
},
driveBackward: function () {
console.log( "drive backward" );
},
driveSideways: function () {
console.log( "drive sideways" );
}
};
// Extend an existing object with a method from another
function augment( receivingClass, givingClass ) {
// only provide certain methods
if ( arguments[2] ) {
for ( var i = 2, len = arguments.length; i < len; i++ ) {
receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]];
}
}
// provide all methods
else {
for ( var methodName in givingClass.prototype ) {
// check to make sure the receiving class doesn't
// have a method of the same name as the one currently
// being processed
if ( !Object.hasOwnProperty(receivingClass.prototype, methodName) ) {
receivingClass.prototype[methodName] = givingClass.prototype[methodName];
}
// Alternatively:
// if ( !receivingClass.prototype[methodName] ) {
// receivingClass.prototype[methodName] = givingClass.prototype[methodName];
// }
}
}
}
// Augment the Car constructor to include "driveForward" and "driveBackward"
augment( Car, Mixin, "driveForward", "driveBackward" );
// Create a new Car
var myCar = new Car({
model: "Ford Escort",
color: "blue"
});
// Test to make sure we now have access to the methods
myCar.driveForward();
myCar.driveBackward();
// Outputs:
// drive forward
// drive backward
// We can also augment Car to include all functions from our mixin
// by not explicitly listing a selection of them
augment( Car, Mixin );
var mySportsCar = new Car({
model: "Porsche",
color: "red"
});
mySportsCar.driveSideways();
// Outputs:
// drive sideways
6. 裝飾模式
裝飾模式動(dòng)態(tài)地給一個(gè)對(duì)象增加一些額外的職責(zé)萤捆。就功能來說裙品,Decorator模式相比生成子類更靈活,在不改變接口的前提下可以增強(qiáng)類的功能俗或,在如下場(chǎng)景可以考慮使用裝飾模式:
- 需要擴(kuò)展一個(gè)類的功能市怎,或給一個(gè)類增加附加責(zé)任
- 動(dòng)態(tài)地給一個(gè)對(duì)象增加功能,這些功能可以再動(dòng)態(tài)撤銷
- 需要增加一些基本功能的排列組合而產(chǎn)生的非常大量的功能辛慰,從而使繼承變得 不現(xiàn)實(shí)
裝飾模式下存在以下幾個(gè)角色:
- 抽象構(gòu)件:給出一個(gè)抽象接口区匠,以規(guī)范準(zhǔn)備接收附加責(zé)任的對(duì)象
- 具體構(gòu)件:定義一個(gè)將要接收附加責(zé)任的類
- 裝飾角色:持有一個(gè)構(gòu)件對(duì)象的實(shí)例,并定一個(gè)與抽象構(gòu)件一致的接口
- 具體裝飾角色:負(fù)責(zé)給構(gòu)件對(duì)象添加附加責(zé)任
[圖片上傳失敗...(image-7703bf-1512819295760)]
相關(guān)概念可參考:設(shè)計(jì)模式——裝飾模式(Decorator)
// The constructor to decorate
function MacBook() {
this.cost = function () { return 997; };
this.screenSize = function () { return 11.6; };
}
// Decorator 1
function Memory( macbook ) {
var v = macbook.cost();
macbook.cost = function() {
return v + 75;
};
}
// Decorator 2
function Engraving( macbook ){
var v = macbook.cost();
macbook.cost = function(){
return v + 200;
};
}
// Decorator 3
function Insurance( macbook ){
var v = macbook.cost();
macbook.cost = function(){
return v + 250;
};
}
var mb = new MacBook();
Memory( mb );
Engraving( mb );
Insurance( mb );
// Outputs: 1522
console.log( mb.cost() );
// Outputs: 11.6
console.log( mb.screenSize() );