`
eric_weitm
  • 浏览: 235941 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

js 设计模式

阅读更多
js 设计模式

参考资料:http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/

一 单例
var SingletonTester = (function(){

  // args: an object containing arguments for the singleton // 构造函数在匿名闭包内,所以外部不可见
  function Singleton( args ) {

   // set args variable to args passed or empty object if none provided.
    var args = args || {};
    //set the name parameter
    this.name = 'SingletonTester';
    //set the value of pointX
    this.pointX = args.pointX || 6; //get parameter from arguments or set default
    //set the value of pointY
    this.pointY = args.pointY || 10;

  }

// this is our instance holder
  var instance;

// this is an emulation of static variables and methods
  var _static = {
    name: 'SingletonTester',
   // This is a method for getting an instance

   // It returns a singleton instance of a singleton object
    getInstance: function ( args ){
      if (instance === undefined) {
        instance = new Singleton( args );
      }
      return instance;
    }
  };
  return _static; // 外部可见的只有这个_static , _static在闭包内定义,所以可以调用构造函数
})();

var singletonTest = SingletonTester.getInstance({pointX: 5});
console.log(singletonTest.pointX); // 输出 5

var anothersSingletonTest = SingletonTester.getInstance({pointX: 66666});
console.log(anothersSingletonTest.pointX); // 输出 5

if (singletonTest === anothersSingletonTest)
  console.log('OK');


  小结:主要技巧是使用匿名闭包

二 模块化
即js中实现c++中的namespace或者java中的package,
1、基本形式
var basketModule = (function() {
    var basket = []; // 私有变量
    function doSomethingPrivate(){
      //... // 私有函数
    }

    function doSomethingElsePrivate(){
      //...
    }
    return { // 提供给外部的接口
        addItem: function(values) {
            basket.push(values);
        },
        getItemCount: function() {
            return basket.length;
        },
        doSomething: doSomethingPrivate(),
        getTotal: function(){
           var q = this.getItemCount(),p=0;
            while(q--){
                p+= basket[q].price;
            }
            return p;
        }
    }
}());

//basketModule is an object with properties which can also be methods
basketModule.addItem({item:'bread',price:0.5});
basketModule.addItem({item:'butter',price:0.3});

console.log(basketModule.getItemCount());
console.log(basketModule.getTotal());

//however, the following will not work:
console.log(basketModule.basket);// 无法访问,其在闭包中
console.log(basket); // 也无法访问

2、jquery中组织库的方法
function library(module) { // 此函数用于定义一个包
  $(function() {
    if (module.init) {
      module.init();  // init是初始化的接口
    }
  });
  return module;
}

var myLibrary = library(function() {
   return {
     init: function() {
       /*implementation*/  // 这里可以对此模块进行初始化
     }
   };
}());

3、改进
var myRevealingModule = (function(){

    var name = 'John Smith';
    var age = 40;

    function updatePerson(){
      name = 'John Smith Updated';
    }
    function setPerson () {
       name = 'John Smith Set';
    }
    function getPerson () {
       return name;
    }
    return {
        set: setPerson, // 此处不再是直接写函数体,而是设个指针
        get: getPerson
    };
}());

// Sample usage:
myRevealingModule.get();

好处是:return部分的代码更简洁

三 观察者模式
好处:灵活,低耦合、好内聚
坏处:不容易看清楚调用关系(调用栈是从回调处开始的);一个listener挂了,发出消息的对象不知道。

例子:
var pubsub = {};

(function(q) {

    var topics = {},
        subUid = -1;

    // Publish or broadcast events of interest
    // with a specific topic name and arguments
    // such as the data to pass along
    q.publish = function( topic, args ) {  // 发布消息

        if ( !topics[topic] ) {
            return false;
        }

        setTimeout(function() {           // 放到下一个tick里面会安全一点
            var subscribers = topics[topic],
                len = subscribers ? subscribers.length : 0;

            while (len--) {
                subscribers[len].func(topic, args); // 调用回调函数
            }
        }, 0);

        return true;

    };

    // Subscribe to events of interest
    // with a specific topic name and a
    // callback function, to be executed
    // when the topic/event is observed
    q.subscribe = function( topic, func ) { // 注册回调

        if (!topics[topic]) {
            topics[topic] = [];
        }

        var token = (++subUid).toString();  // 这个主要是标记各个回调函数
        topics[topic].push({
            token: token,
            func: func
        });
        return token;
    };

    // Unsubscribe from a specific
    // topic, based on a tokenized reference
    // to the subscription
    q.unsubscribe = function( token ) {
        for ( var m in topics ) {
            if ( topics[m] ) {
                for (var i = 0, j = topics[m].length; i < j; i++) {
                    if (topics[m][i].token === token) {
                        topics[m].splice(i, 1);
                        return token;
                    }
                }
            }
        }
        return false;
    };
}( pubsub ));

var testSubscriber = function( topics , data ){
    console.log( topics + ": " + data );
};

// 1, 基本使用方法:
// Publishers are in charge of "publishing" notifications about events

pubsub.publish( 'example1', 'hello world!' );
pubsub.publish( 'example1', ['test','a','b','c'] );
pubsub.publish( 'example1', [{'color':'blue'},{'text':'hello'}] );

// Subscribers basically "subscribe" (or listen)
// And once they've been "notified" their callback functions are invoked
var testSubscription = pubsub.subscribe( 'example1', testSubscriber );

// Unsubscribe if you no longer wish to be notified

setTimeout(function(){
    pubsub.unsubscribe( testSubscription );
}, 0);

pubsub.publish( 'example1', 'hello again! (this will fail)' );


// 2, 股票交易信息模拟
var grid = {

   addEntry: function(data){

       if (data !== 'undefined') {

          console.log('Entry:'

                      + data.title

                      + ' Changenet / %'

                      + data.changenet

                      + '/' + data.percentage + ' % added');

       }

   },

   updateCounter: function(timestamp){
       console.log('grid last updated at: ' + timestamp);
   }
};



var gridUpdate = function(topics, data){
       grid.addEntry(data);
       grid.updateCounter(data.timestamp);
}



var gridSubscription = pubsub.subscribe( 'dataUpdated', gridUpdate );

pubsub.publish('dataUpdated',   { title: "Microsoft shares", changenet: 4, percentage: 33, timestamp: '17:34:12'  });

pubsub.publish('dataUpdated',   { title: "Dell shares", changenet: 10, percentage: 20, timestamp: '17:35:16'  });

四、中介者模式 The Mediator Pattern
  此模式基本概念:把模块之间的网状引用关系,变成一对多的关系,即Mediator负责维护模块间的调用关系。
优点:解耦合、重用
缺点:性能低(不是直接通信)、不容易看清调用流程

与门面模式区别:维护网状依赖, 门面仅仅是简化一个模块的调用。

例子:
var mediator = (function(){
    // Subscribe to an event, supply a callback to be executed
    // when that event is broadcast
    var subscribe = function(channel, fn){
        if (!mediator.channels[channel]) mediator.channels[channel] = [];
        mediator.channels[channel].push({ context: this, callback: fn });
        return this;
    },

    // Publish/broadcast an event to the rest of the application
    publish = function(channel){
        if (!mediator.channels[channel]) return false;
        var args = Array.prototype.slice.call(arguments, 1);
        for (var i = 0, l = mediator.channels[channel].length; i < l; i++) {
            var subscription = mediator.channels[channel][i];
            subscription.callback.apply(subscription.context, args);
        }
        return this;
    };

    return {
        channels: {},
        publish: publish,
        subscribe: subscribe,
        installTo: function(obj){
            obj.subscribe = subscribe;
            obj.publish = publish;
        }
    };

}());

(function( m ){

    function initialize(){

      // Set a default value for 'person'
      var person = "tim";

      // Subscribe to an event called 'nameChange' with
      // a callback function which will log the original
      // person's name and (if everything works) the new
      // name

      m.subscribe('nameChange', function( arg ){
          console.log( person ); // tim
          person = arg;
          console.log( person ); // david
     });
    }

    function updateName(){
      // Publish/Broadcast the 'nameChange' event with the new data 将原来的直接回调,变成了这样中转一次
      m.publish( 'nameChange', 'david' );
    }

})( mediator );

五 The Prototype Pattern 原型模式 JS本身就支持

// 所有vehicle都该有的样子
var vehiclePrototype = {
    init: function( carModel ) {
        this.model = carModel;
    },
    getModel: function() {
        console.log( 'The model of this vehicle is..' + this.model );
    }
};


function vehicle( model ) {
    function F() {};
    F.prototype = vehiclePrototype;

    var f = new F();// 这个新对象数据是空的,但是函数却是和vehiclePrototype一样(prototype链机制)

    f.init( model );// 将数据进行初始化
    return f;
}

var car = vehicle( 'Ford Escort' );
car.getModel();// 调用原型上的函数

六 命令模式
以统一的接口来调用各个函数,JS中函数可以直接当变量传,所以很容易实现
$(function(){

  var CarManager = {

      // request information
      requestInfo: function( model, id ){
        return 'The information for ' + model +
        ' with ID ' + id + ' is foobar';
      },

      // purchase the car
      buyVehicle: function( model, id ){
        return 'You have successfully purchased Item '
        + id + ', a ' + model;
      },

      // arrange a viewing
      arrangeViewing: function( model, id ){
        return 'You have successfully booked a viewing of '
        + model + ' ( ' + id + ' ) ';
      }

    };

})();

// 把一个请求映射为具体的函数
CarManager.execute = function( command ){
  return CarManager[command.request](command.model,command.carID);
};

// 使用方法
CarManager.execute({request: "arrangeViewing", model: 'Ferrari', carID: '145523'});
CarManager.execute({request: "requestInfo", model: 'Ford Mondeo', carID: '543434'});
CarManager.execute({request: "requestInfo", model: 'Ford Escort', carID: '543434'});
CarManager.execute({request: "buyVehicle", model: 'Ford Escort', carID: '543434'});

七 The DRY Pattern  --------相似代码只写一次
1、一般实现
// Let's store some defaults about a car for reference
var defaultSettings = {};
defaultSettings['carModel']   = 'Mercedes';
defaultSettings['carYear']    = 2010;
defaultSettings['carMiles']   = 5000;
defaultSettings['carTint']    = 'Metallic Blue';

// Let's do something with this data if a checkbox is clicked
$('.someCheckbox').click(function(){

if ( this.checked ){

    $('#input_carModel').val(activeSettings.carModel);
    $('#input_carYear').val(activeSettings.carYear);
    $('#input_carMiles').val(activeSettings.carMiles);
    $('#input_carTint').val(activeSettings.carTint);

} else {

    $('#input_carModel').val('');
    $('#input_carYear').val('');
    $('#input_carMiles').val('');
    $('#input_carTint').val('');
}
});


2、DRY Pattern

$('.someCheckbox').click(function(){
    var checked = this.checked,
    fields = ['carModel', 'carYear', 'carMiles', 'carTint'];
    /*
        What are we repeating?
        1. input_ precedes each field name
        2. accessing the same array for settings
        3. repeating value resets

        What can we do?
        1. programmatically generate the field names
        2. access array by key
        3. merge this call using terse coding (ie. if checked,
            set a value, otherwise don't)
  */
  $.each(fields, function(i,key){
    $('#input_' + key).val(checked ? defaultSettings[key] : ''); // 这里不是写很多的$('#input_XX').val(activeSettings.YY); 而是统一写一句通用的代码
  });
});

3、自己项目中的一段代码实例:

CraftData.inheritsFrom(BasicData);

CraftData.prototype.CRAFT_TYPE_INFO = {
  // order is:speed, range, acceleration, maxFuel, weaponNum, maxDamage, maxCapacity, HWPs, costPerMonth
  'SKYRANGER'   : [760, 13500, 2, 1500, 0, 150, 14, 3, 500000], // 这里的数据没有写成罗嗦的json格式
  'INTERCEPTOR' : [2100, 8050, 3, 1000, 2, 100, 0,  0, 600000],

  'LIGHTNING'   : [3100, 7750, 8, 30,   1, 800, 12, 0, 600000],
  'FIRESTORM'   : [4200, 7000, 9, 20, 2, 500, 0,  0, 400000],
  'AVENGER'     : [5400, 27000, 10, 60, 2, 1200, 26,  4, 900000],
};

CraftData.prototype.initSpecialArg = function(){
  var that = this;

  var arg = that.CRAFT_TYPE_INFO[that.type];

  if (!arg){
    alert("facility type error! in CraftData.prototype.initSpecialArg ");
    throw new error("facility type error! in CraftData.prototype.initSpecialArg ");
  }

  var order = ['speed', 'range', 'acceleration', 'maxFuel', 'weaponNum', 'maxDamage', 'maxCapacity', 'HWPs', 'costPerMonth'];

  for (var index = 0;index < arg.length; index++){
    that[order[index]] = arg[index];  // 这里将对应的属性设置上去
  }
};

八 门面 The Facade Pattern -----降低使用代码的难度
var module = (function() {
    var _private = {
        i:5,
        get : function() {
            console.log('current value:' + this.i);
        },
        set : function( val ) {
            this.i = val;
        },
        run : function() {
            console.log( 'running' );
        },
        jump: function(){
            console.log( 'jumping' );
        }
    };
    return {
        facade : function( args ) { // 一次性提供一个facade就好。不使用门面时,需要暴露给client程序员set 和get方法,并且要求client程序员要知道怎么组织这部分的逻辑(先set 后run)
            _private.set(args.val);
            _private.get();
            if ( args.run ) {
                _private.run();
            }
        }
    }
}());


module.facade({run: true, val:10});
//outputs current value: 10, running


九 工厂模式

var Car = (function() {
   var Car = function ( model, year, miles ){
       this.model = model;
       this.year   = year;
       this.miles = miles;
   };

   return function ( model, year, miles ) {
       return new Car( model, year, miles ); // 工厂内部进行new
   };

})();

var civic = Car( "Honda Civic", 2009, 20000 ); // 不是直接new 而是调用工厂的方法
var mondeo = Car("Ford Mondeo", 2010, 5000 );

十 The Mixin Pattern  类似于c++的多继承
// Car
var Car = function( settings ){
  this.model = settings.model || 'no model provided';
  this.colour = settings.colour || 'no colour provided';
};

// Mixin
var Mixin = function(){};
Mixin.prototype = {
  driveForward: function(){
    console.log('drive forward');
  },
  driveBackward: function(){
    console.log('drive backward');
  }
};


// 第一个参数要使用别人功能的对象, 第二个参数 提供功能的对象, 后面的参数是提供的函数的名字
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 ( !receivingClass.prototype[methodName] ) {
        receivingClass.prototype[methodName] = givingClass.prototype[methodName];
      }
    }
  }
}


// Augment the Car have the methods 'driveForward' and 'driveBackward'*/
augment( Car, Mixin,'driveForward','driveBackward' );

// Create a new Car
var vehicle = new Car({model:'Ford Escort', colour:'blue'});

// Test to make sure we now have access to the methods
vehicle.driveForward(); // 尽管vehicle是car,但是通过augment,使得Car的对象都具有了Mixin的函数
vehicle.driveBackward();


十一 The Decorator Pattern 装饰模式
1、OOP的经典重用技术 -------继承
var subclassExample = subclassExample || {};
subclassExample = {
    Person: function( firstName , lastName ){
        this.firstName = firstName;
        this.lastName =  lastName;
        this.gender = 'male'
    }
}

//a new instance of Person can then easily be created as follows:
var clark = new subclassExample.Person( "Clark" , "Kent" );

//Define a subclass constructor for for 'Superhero':
subclassExample.Superhero = function( firstName, lastName , powers ){
    /*
        Invoke the superclass constructor on the new object
        then use .call() to invoke the constructor as a method of
        the object to be initialized.
    */
    subclassExample.Person.call(this, firstName, lastName); // 利用js的动态性,实现java中super类似的功能
    //Finally, store their powers, a new array of traits not found in a normal 'Person'
    this.powers = powers;
}
subclassExample.Superhero.prototype = new subclassExample.Person; // 通过prototype将方法共享过来
var superman = new subclassExample.Superhero( "Clark" ,"Kent" , ['flight','heat-vision'] );
console.log(superman); /* includes superhero props as well as gender*/

2、不用继承 而用装饰者模式 ,java的io流中大量使用了此模式。不使用继承,所以类的层次结构不会被打乱。
//What we're going to decorate
function MacBook() {
    this.cost = function () { return 997; };
    this.screenSize = function () { return 13.3; };
}

/*Decorator 1*/
function Memory( macbook ) {
    var v = macbook.cost();
    macbook.cost = function() {
        return v + 75; // 这时cost返回的是 997+75
    }
}
/*Decorator 2*/
function Engraving( macbook ){
   var v = macbook.cost();
   macbook.cost = function(){
     return  v + 200;// 这时cost返回的是 997+75+200
  };
}

/*Decorator 3*/
function Insurance( macbook ){
   var v = macbook.cost();
   macbook.cost = function(){
     return  v + 250;// 这时cost返回的是 997+75+200+250
  };
}
var mb = new MacBook();
Memory(mb);
Engraving(mb);
Insurance(mb);
console.log(mb.cost()); //1522,所以这个值是1522
console.log(mb.screenSize()); //13.3


架构模式
一 MVC
例子:
backbone.js
二 MVP

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics