Skip to content

Instantly share code, notes, and snippets.

@giscafer
Last active January 21, 2019 09:31
Show Gist options
  • Save giscafer/30723edddae7970befcacd0847c84b48 to your computer and use it in GitHub Desktop.
Save giscafer/30723edddae7970befcacd0847c84b48 to your computer and use it in GitHub Desktop.
原生js接口设计模式
/**
* 类的封装和信息的隐藏
*
* 本例子使用闭包来实现静态变量熟悉和方法,通过创建一个受保护的变量空间,可以实现公用、私用和特权成员,以及
* 静态成员和常量。
*
* 概念:
* 1、特权方法(privileged method) 指有权访问私有变量和私有函数的公有方法
* 2、非特权方法(non-privileged method) 指无需(或没有权限)访问私有变量和私有函数的公有方法
*
* 这么做的好处:
*
* 1、保护内部数据完整性,数据(变量)只能通过取值器getter和赋值器setter来访问,可以完全控制,避免数据处于无效状态;
* 2、对象的重构因此变得更轻松,因为用户不知道对象的内部细节,所以可以随心所欲的修改对象内部数据结构和算法,外部使用不会受到影响,也不会知道;
* 3、公开一些接口规定的方法,可弱化模块之间的耦合性,这是面向对象编程重要的原则之一;
*
* 弊端:
*
* 1、封装会带来复杂性,和作用域闭包打交道,调式困难;
*/
var Book = (function () {
// 私有静态变量属性
var numOfBooks = 0;
// 私有静态方法
function checkIsbn() {
// ……
return true;
}
return function (isbn, title, author) {
// 私有属性
var _isbn, _title, _author;
// privileged method 特权方法
this.getIsbn = function () {
return _isbn;
};
this.setIsbn = function (isbn) {
if (!checkIsbn(isbn)) throw new Error('Book:无效的ISBN');
_isbn = isbn;
};
this.getTitle = function () {
return _title;
};
this.setTile = function (title) {
_title = title || 'No title';
};
this.setAuthor = function (author) {
_author = author || 'No title';
}
this.getAuthor = function () {
return _author;
};
// 静态变量自动技术,统计书的总数
numOfBooks++;
// 设置值
this.setIsbn(isbn);
this.setTile(title);
this.setAuthor(author);
}
})();
// 公共静态方法(好处是所有实例对象,共享一个方法,该方法只会创建一次,也就是内存只会存放一个,如果每个实例都创建一次,很浪费资源)
Book.convertToTitleCase = function (inputString) {
// ……
}
// 任何不需要直接访问私有变量(如_isbn,_title)的方法,都可以放到prototype里边定义,里边定义的方法都称为非特权方法(non-privileged method)
Book.prototype = {
// non-privileged method
dispaly: function () {
console.log(this.getIsbn(), this.getTitle(), this.getAuthor());
}
};
/**
* 定义一个Person类
* 其实JavaScript没有类之说(ES5之前无class关键词),JS的函数是一等对象,因为js太过于灵活(后端可能认为是奇葩),所以约定一些规范或者设计模式等。
* JS也可以有类(如下Person), 类可以实现继承。当然,不会像Java或者其他静态语言一样,一个关键字extends就搞定了类的继承。
* (了解ES6和ts的同学知道,类继承可以通过extends实现了,因为他们真正有类了,class)
* @param {*} name
*/
function Person(name) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
}
// 定义一个Author类,继承Person类
function Author(name, books) {
Person.call(this, name); // 调用父类构造函数
this.books = books;
}
Author.prototype = new Person(); // 将Author的原型链指向person的一个实例
Author.prototype.constructor = Author; // 因为上一句会造成Author原本的constructor丢失,变成了Person,所以需要手动修改为Author,类的构造器就是类本身
Author.prototype.getBooks = function () {
return this.books;
}
// test
var author1=new Author("东野奎君",["嫌疑人X的献身","白夜人"]);
var author2=new Author("村上春树",["挪威的深林"]);
console.log(author1.getName());
console.log(author1.getBooks());
console.log(author2.getBooks());
/**
* 派生子类过程包装到继承inherit函数中
* @param {*} C 子类
* @param {*} P 父类
*
* 很多著名的地图框架都采用类似的方式,如openlayers2.x和leaflet.js,Echarts2.x
* 如openlayers2.x的OpenLayers.Class的实现源码:
* https://github.com/openlayers/ol2/blob/master/lib/OpenLayers/BaseTypes/Class.js
*
*/
function inherit(C, P) {
// 使用空函数F,避免超类P创建的实例较庞大,以及规避超类构造器中的可能存在的副作用。
var F = function () { };
F.prototype = P.prototype;
C.prototype = new F;
C.prototype.constructor = C;
// 提供超类属性,弱化父子类之间的耦合性,使得子类也可以之间调用超类的方法
C.superclass = P.prototype;
// 确保父类的构造函数被正确设置
if (P.prototype.constructor == Object.prototype.constructor) {
P.prototype.constructor = P;
}
}
function Person(name) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
}
function Author(name, books) {
// 使用superclass来调用父类的构造器
Author.superclass.constructor.apply(this, arguments);
this.books = books;
}
// 原型链继承,Author继承Person
inherit(Author, Person);
Author.prototype.getBooks = function () {
return this.books;
}
// test
var author1 = new Author("东野奎君", ["嫌疑人X的献身", "白夜人"]);
var author2 = new Author("村上春树", ["挪威的深林"]);
console.log(author1.getName());
console.log(author1.getBooks());
console.log(author2.getBooks());
//===== 输出======//
// 东野奎君
// [ '嫌疑人X的献身', '白夜人' ]
// [ '挪威的深林' ]
/**
* Interface 类
* 检查某些类是否实现了指定的接口
* @param {*} name
* @param {*} methods
*/
var Interface = function (name, methods) {
if (arguments.length != 2) {
throw new Error('Interface contructor called width' + arguments.length + 'arguments,but expected exactly 2.');
}
this.name = name;
this.methods = [];
for (var i = 0; len = methods.length; 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]);
}
};
// Static class method
Interface.ensureImplements = function (object) {
if (arguments.length < 2) {
throw new Error('Function Interface.ensureImplements called width' + arguments.length + 'arguments,but expected exactly 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 implements the ' + interface.name + ' interface. Method ' + method + ' was not found');
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment