Javascript设计模式(1)

本文是学习了《JavaScript设计模式》(谢廷晟 译)做的学习笔记

一、JavaScript的灵活性

1. 普通 functon

    function startAnimation() {...}
    function stopAnimation() {...}

2. 类


    var Anim = function() {}   #构造函数

    # 方式一
        Anim.prototype.start = function() {...}
        Anim.prototype.stop = function() {...}

    # 方式二
        Anim.prototype = {
            start: function() {...},
            stop: function() {...}
        }

    # usage
        var myAnim = new Anim()
        myAnim.start()
        myAnim.stop()

3. 为实例创建方法


   # 方式一
    Function.prototype.method = function(name, fn) {
        this.prototype[name] = fn
    }
   # usage
    var Anim = function() {}
    Anim.method('start', function(){...})
    Anim.method('stop', function(){...})

   # 方式二 (链式)
    Function.prototype.method = function(name, fn) {
        this.prototype[name] = fn
        return this
    }
   # usage
    Anim.method('start', function(){...})
        .method('stop', function(){...})

4. 弱类型语言

  • 原始数据类型按值传递,其他数据类型按引用传递
  • javascript 类型系统可以分为标准类型对象类型,进一步标准类型又可以分为原始类型和引用类型,而对象类型又可以分为内置对象类型、普通对象类型、自定义对象类型。

  1. 原始类型(值类型):
    • Undefined undefined
    • Null null
    • Boolean true
    • String 'hello'
    • Number 123
  2. 引用类型(对象类型):
    • Object
    • Array
    • Data
    • RegExp
    • Function

二、接口

1. JavaScript 中模拟接口

注释描述接口

     # 只是使用注释说明接口

     /*

     interface Composite {
        function add (child)
        function remove (clild)
        function getChild(index)
     }
     interface FormItem {
        function save ()
     }

     */

使用属性检查模仿接口

假如我们要创建一个叫 CompositeForm 的类,并且它有两个规定好的接口 CompositeFormItem需要实现,我们可以在实现这个类的时候给它添加一个 implementsInterfaces 数组用于声明该类有哪些方法或属性,这样我们就能在需要的地方用统一的检测方法 implements 来检测是否该类作者已经实现了这些接口。

     var CompositeForm = function(id, method, action) {
        this.implementsInterfaces = ['Composite', 'FormItem'];   //它自己说它实现了
     }

    // 该方法需要传入一个具有 Composite、FormItem 接口的对象,所以我们在里面使用 implements 方法进行检查
    function addForm(formInstance) {
        if(!implements(formInstance, 'Composite', 'FormItem')) {    
            throw new Error("Object dost not implement a require interface.");
     	}
        // 检查通过后进行其他处理
    }

    function implements(object) {
     	for(var i=1; i < arguments.length; i++) {
     		var interfaceName = arguments[i];
     		var interfaceFound = false;
     		for(var j=0; j<object.implementsInterfaces.length; j++) {
     			if(object.implementsInterfaces[j] == interfaceName) {
     				interfaceFound = true;
     				break;
     			}
     		}
     		if(!interfaceFound) {
     			return false;
     		}
     	}
     	return true
    }

    // 使用
     addForm(new CompositeForm())   //只能知道它是否‘说’自己实现了接口,并未确保类真正实现了接口

用鸭式辩型模仿接口

假如我们要实现一个 Composite 类,它继承了两个类的方法 Composite:['add', 'remove', 'getchild'] FormItem:['save'],那我们可以实现专门的类:Interface ,他的每个实例用于指定一些接口方法(有点像 Java 的接口类),它有一个公共的方法 ensureImplements 可以用来检测某个对象是否有实现对应的 Interface 实例指定的接口。

   var Interface = function(name, methods) {
     	if(arguments.length !== 2) {
         	throw new Error("Interface constructor called with "+ arguments.length + "arguments, but expected exactly 2")
     	}
     	this.name = name;
     	this.methods = [];
     	for(var i=0, len=methods.length; i<len; i++) {
         	if(typeof methods[i] !== 'string') {
             	throw new Error("Interface constructor expects method names to be passed in as a string")
         	}
         	this.methods.push(methods[i]);
     	}
   }  

   Interface.ensureImplements = function(object) {
       	if(arguments.length < 2) {
           	throw new Error("Function Interface.ensureImplements call with " + arguments.length + "arguments, but expected at least 2")
       	}
       	for(var i=1,len=arguments.length; i<len; i++) {
           	var interface = arguments[i];
           	if(interface.constructor !== Interface) {
               	throw new Error("Function Interface.ensureImplements expects arguments two and above to be instances of Interface");
           	}
           	for(var j=0, methodsLen = interface.methods.length; j<methodsLen; j++) {
               	var method = interface.methods[j];
               	if(!object[method] || typeof object[method] !== 'function') {
                   throw new Error('Function Interface.ensureImplements: Object does not implement the '+ interface.name + " interface. Method " + method + " was not found")
               	}
           	}
       	}
     }

    // 创建两个 Interface 实例并指定各自需要实现的接口
     var Composite = new Interface('Composite', ['add', 'remove', 'getchild']);
     var FormItem = new Interface('FormItem', ['save'])


     // 使用
     
    var CompositeForm = function(id, method, action) {
     	this.add = function() {}   // 这里我们只实现了一个方法,所以会报错
     }
     function addForm(formInstance) {
        // 这里先检查传进来的对象是否实现对应的 Interface 实例所指定接口
     	Interface.ensureImplements(formInstance, Composite, FormItem)

         // 通过后……
     }

     addForm(new CompositeForm())  // 调用方法

    // 它只关心方法的名称,并不检查其参数的名称、数目或类别

其他方式

  • TypeScript :可以检查函数参数,对类成员可以显式声明访问级别:public、protected、private 等

三、封装和信息隐藏

1. 创建对象的基本模式

1.1 门户大开型对象

   var Book = function(isbn, title) {
     	this.setIsbn(isbn)
       this.setTitle(title)
   }

   Book.prototype = {
   	checkIsbn: function(isbn) {...},
       
       getIsbn: function(isbn) { return this.isbn },
       setIsbn: function(isbn) {
         	if(!this.checkIsbn(isbn)) throw new Error('Book: Invalid ISBN')
           this.isbn = isbn
       },
         
       getTitle: function() { return this.title },
       setTitle: function(title) {
         	this.title = title || 'No title specified'  //验证
       }
   }
     
   // 虽然设置了赋值器,但是属性依然是公开的,可以被直接赋值
   // 如 var b = new Book('aa', 'title1')  可直接通过 b.isbn 修改 isbn

1.2 用命名规范区别私有成员

   var Book = function(isbn, title) {
     	this.setIsbn(isbn)
       this.setTitle(title)
   }

   Book.prototype = {
   	_checkIsbn: function(isbn) {...},
       
       getIsbn: function(isbn) { 
         	return this._isbn 
       },
       setIsbn: function(isbn) {
         	if(!this._checkout(isbn)) throw new Error('Book: Invalid ISBN')
           this._isbn = isbn
       },
         
       getTitle: function() { 
         	return this._title 
       },
       setTitle: function(title) {
         	this._title = title || 'No title specified'  //验证
       }
   }
     
   // 下划线只能防止程序员无意间使用某个属性,却不能防止对它的有意使用

1.3 作用域、嵌套函数和闭包

   function foo() {
     	var a = 10;
   	return function bar() {
         	a *= 2;
     		return a;
       }
   }

   #usage
   var a = foo()
   var b = foo()
   a()		// 20
   a() 	// 40
   b() 	// 20

1.4 用闭包实现私有成员

   var Book = function(newIsbn, newTitel) {
       var isbn, title, author;          // Private attrbute 【私有属性】

       function checkIsbn(isbn) { return true}    // Private methods 【私有方法】

       this.getIsbn = function() {       // Privileged methods 【特权方法】
           return isbn 
       };
       this.setIsbn = function(newIsbn) {
           if(!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN');
           isbn = newIsbn
       }

       this.getTitle = function() { 
           return title 
       };
       this.setTitle = function(newTitel) {
           title = newTitel || 'No title specified'  //验证
       }

       this.setIsbn(newIsbn)
       this.setTitle(newTitel)
   }

   Book.prototype = {
       display: function() {}      //不需要访问私有属性的方法
   }
   // usage
   var b1 = new Book('11111', 'title1')

   // 每生成一个新的对象实例都将为每个私用方法和特权方法生成一个新的副本,这会耗费更多内存
   // 这种对象创建模式不利于派生子类,因为派生的子类不能访问超类的任何私用属性或方法,这种方式被称为“继承破坏封装”
   // 如果创建的类以后可能会需要派生出子类,最好使用【门户大开型】或【命名规范区别私有成员】两种方式

2. 更多高级对象创建方式

2.1 静态方法和属性

   var Book = (function() {
       var numOfBooks = 0;    // Private static attributes 【静态私有属性】所有实例共享

       function checkIsbn(isbn) {return true}   // Private static methods【静态私有方法】所有实例共享

       return function(newIsbn, newTitel) {
           // 单个实例独有
           var isbn, title, author;   // Private attribute 【私有属性】

           this.getIsbn = function() {    // Privileged methods 【私有方法】
               return isbn
           };
           this.setIsbn = function(newIsbn) {
               if(!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN');
               isbn = newIsbn
           }

           this.getTitle = function() { 
               return title 
           };
           this.setTitle = function(newTitel) {
               title = newTitel || 'No title specified'  //验证
           }

           numOfBooks++;
           if(numOfBooks > 50) {
               throw new Error('Book: only 50 instances of Book can be created')
           }

           this.setIsbn(newIsbn)
           this.setTitle(newTitel)
       }
   })()

   Book.convertToTitleCase = function(inputstring) {  //无需创建实例即可访问的方法
       return inputstring
   }

   Book.prototype = {     //不需要访问私有属性的方法
       display: function() {}
   }

   // usage
   var b1 = new Book('11111', 'title1')

2.2 常量

   var Class = (function(){
     
        // private static attributes【静态私有属性】所有实例共享
        var constants = {
            UPPER_BOUND: 100,
            LOWER_BOUND: -100
        }
        
        var ctor = {}
        ctor.getConstant = function(name) {
            return constants[name]
        }
        
        return ctor
   })()

   # usage

   Class.getContant('UPPER_BOUNDA')

   // 创建只有取值器而没有赋值器的私有变量来模仿常量

四、继承

1. 类式继承

1.1 原型链

    function Person(name) {
        this.name = name
    }
    Person.prototype.getName = function() {
        return this.name
    }

    function Author(name, books) {
        Person.call(this, name);  // 将 Person 中 this.xxx 复制到这里
        this.books = books
    }

    Author.prototype = new Person();
    Author.prototype.constructor = Author;
    Author.prototype.getBooks = function() {
        return this.books
    }

    # usage
    var a1 = new Author('aaa', ['a','b'])

1.2 extend 函数

    function extend(subClass, superClass) {
        var F = function() {}
        F.prototype = superClass.prototype;  // 使用 F 是为了避免创建超类的新实例,superClass实例做为原型时,其属性会在subClass的实例里面被共享(如果是引用类型如Array,里面的每一个项修改会导致所有实例都被改),这显然不是我们想要的,所以应该通过 superclass.call(this, ...) 把实例的属性直接引入到 subClass的构造函数中
        subClass.prototype = new F();
        subClass.prototype.constructor = subClass;
  
  	/* 增强版 */
        subClass.superclass = superClass.prototype    // 可直接通过 subClass.superclass.prototype.constructor 访问到超类的构造函数,弱化子类与超类的联系。subClass 的实例是访问不了 superClass 属性的。
  
  	// 检查超类的 prototype.contructor 是否指向自身构造函数,如果不是则改指导超类的构造函数
        if(superClass.prototype.contructor == Object.prototype.constructor) {
            superClass.prototype.constructor = superClass;
        }
    }

    # usage
    /* Class Person */
    function Person(name) {
    this.name = name
    }
    Person.prototype.getName = function() {
    return this.name
    }

    /* Class Author */
    function Author(name, books) {
    Person.call(this, name);  // 将 Person 中 this.xxx 复制到这里
    this.books = books
    }

    extend(Author, Person)    //需要在 prototype 上添加新方法前被调用

    Author.prototype.getBooks = function() {
    return this.books
    }

2. 原型式继承

得益于原型链查找的工作机制

    function clone(o) {     // 返回以给定对象为原型的【空对象】
        function F() {}
            F.prototype = o
            return new F()
    }

    // 注意这里是一个对象,不是构造函数
    var Person = {
        name: 'default name',
        getName: function() {
        return this.name
        }
    }

    var reader = clone(Person)
    alert(reader.getName())  // 'default name'

    reader.name = 'John Smith'
    alert(reader.getName())  // 'John Smith'

    // 原型对象 Person 为其他对象应有的模样提供了一个原型,这正是原型式继承这个名字的由来

对继承而来的成员的读和写的不对等性

第一次读 name 时是读到 Person 上的 name 属性,而第一次写是写 reader.name = 'xxx' 写在 reader 上(引用类型变量还是在原型对象上进行修改,这很糟糕,需要先 reader.books= [] , reader.books.push())


    # 工厂方法辅助

    function clone(o) {
        function F() {}
        F.prototype = o
        return new F()
    }

    var CompoundObject = {};
    CompoundObject.string1 = 'default value';
    CompoundObject.createChildObject = function() {      // 在父类中实现某个父类属性的【工厂方法】
        return {
            bool: true,
            num: 10
        }
    }
    CompoundObject.childObject = CompoundObject.createChildObject()   // 如果父类的属性是引用类型,那么可以通过工厂方法返回这个引用对象赋给对应属性,这样的话其子类在继承了这个对象后可以马上为其子类的相同属性附上对应工厂方法返回的默认引用对象,这样就能避免读写不对称而出现不符合我们想象的情况(如多个实例的属性彼此影响)。

    var c1 = clone(CompoundObject);    // 继承
    c1.childObject = CompoundObject.createChildObject()  // 使用父类的工厂方法来产生默认引用类型用于覆盖原型链上的父类对象
    c1.childObject.num = 5  // 添加新属性

3. 掺元类

将某个类中指定的方法复制到其他类中,而不是整体继承过去

    function argument(receivingClass, givingClass) {
        // 查看是否有其他参数用于指定复制特定方法
        if(argument[2]) {
            for(var i=2, len=arguments.length; i<len; i++) {
                receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]
            }
        }else {
        // 如果没有则全部方法都复制过去 receivingClass
            for(methodName in givingClass.prototype) {
                if(!receivingClass.prototype[methodName]) {
                    receivingClass.prototype[methodName] = givingClass.prototype[methodName]
                }
            }
        }
    }

    /* Mixin class */
    var Mixin = function () {}         // 掺元类
    Mixin.prototype = {
        serialize: function() {
            var output = [];
            for(key in this) {
                output.push(key + ': ' + this[key])
            }
            return output.join(', ')
        }
    }

    function Author(name, books) {
        this.name = name
        this.books = books
    }

    argument(Author, Mixin);          // 进行复制,后面可以继续加参数指定具体要从 Mixin 复制的方法

    var author = new Author('czs', ['js', 'nodejs'])
    var serializeStr = author.serialize()
    console.log(serializeStr)

    // 将某个类中指定的方法复制到其他类中,而不是整体继承过去


注意

转载、引用,但请标明作者和原文地址

posted @ 2018-03-01 20:08  另一个小菜头  阅读(139)  评论(0编辑  收藏  举报