首页 web前端 js教程 面向对象的Javascript之三(封装和信息隐藏)_js面向对象

面向对象的Javascript之三(封装和信息隐藏)_js面向对象

May 16, 2016 pm 05:56 PM
封装 面向对象

同时,我们知道在面向对象的高级语言中,创建包含私有成员的对象是最基本的特性之一,提供属性和方法对私有成员进行访问来隐藏内部的细节。虽然JS也是面向对象的,但没有内部机制可以直接表明一个成员是公有还是私有的。还是那句话,依靠JS的语言灵活性,我们可以创建公共、私有和特权成员,信息隐藏是我们要实现的目标,而封装是我们实现这个目标的方法。我们还是从一个示例来说明:创建一个类来存储图书数据,并实现可以在网页中显示这些数据。

1. 最简单的是完全暴露对象。使用构造函数创建一个类,其中所有的属性和方法在外部都是可以访问的。

复制代码 代码如下:

var Book = function(isbn, title, author) {
if(isbn == undefined) {
throw new Error("Book constructor requires a isbn.");
}
this.isbn = isbn;
this.title = title || "";
this.author = author || "";
}
Book.prototype.display = function() {
return "Book: ISBN: " + this.isbn + ",Title: " + this.title + ",Author: " + this.author;
}

display方法依赖于isbn是否正确,如果不是你将无法获取图像以及链接。考虑到这点,每本图书isbn必须存在的,而图书的标题和作者是可选的。表面上看只要指定一个isbn参数似乎就能正常运行。但却不能保证isbn的完整性,基于此我们加入isbn的验证,使图书的检查更加健壮。
复制代码 代码如下:

var Book = function(isbn, title, author) {
if(!this.checkIsbn(isbn)) {
throw new Error("Book: invalid ISBN.");
}
this.isbn = isbn;
this.title = title || "";
this.author = author || "";
}
Book.prototype = {
checkIsbn: function(isbn) {
if(isbn == undefined || typeof isbn != "string") return false;
isbn = isbn.replace("-", "");
if(isbn.length != 10 && isbn.length != 13) return false;
var sum = 0;
if(isbn.length == 10) {
if(!isbn.match(\^\d{9}\)) return false;
for(var i = 0;i sum += isbn.charAt(i) * (10 - i);
}
var checksum = sum % 11;
if(checksum == 10) checksum = "X";
if(isbn.charAt(9) != checksum) return false;
} else {
if(!isbn.match(\^\d{12}\)) return false;
for(var i = 0;i sum += isbn.charAt(i) * (i % 2 == 0 ? 1 : 3);
}
var checksum = sum % 10;
if(isbn.charAt(12) != checksum) return false;
}
return true;
},
display: function() {
return "Book: ISBN: " + this.isbn + ",Title: " + this.title + ",Author: " + this.author;
}
};

我们添加了checkIsbn()来验证ISBN的有效性,确保display()可以正常运行。但是需求有变化了,每本书可能有多个版本,意味着同一本可能有多个ISBN号存在,需要维护单独的选择版本的算法来控制。同时尽管能检查数据的完整性,但却无法控制外部对内部成员的访问(如对isbn,title,author赋值),就谈不上保护内部数据了。我们继续改进这个方案,采用接口实现(提供get访问器/set存储器)。
复制代码 代码如下:

var Publication = new Interface("Publication", ["getIsbn", "setIsbn", "checkIsbn", "getTitle", "setTitle", "getAuthor", "setAuthor", "display"]);
var Book = function(isbn, title, author) {
// implements Publication interface
this.setIsbn(isbn);
this.setTitle(title);
this.setAuthor(author);
}
Book.prototype = {
getIsbn: function() {
return this.isbn;
},
setIsbn: function(isbn) {
if(!this.checkIsbn(isbn)) {
throw new Error("Book: Invalid ISBN.");
}
this.isbn = isbn;
},
checkIsbn: function(isbn) {
if(isbn == undefined || typeof isbn != "string") return false;
isbn = isbn.replace("-", "");
if(isbn.length != 10 && isbn.length != 13) return false;
var sum = 0;
if(isbn.length == 10) {
if(!isbn.match(\^\d{9}\)) return false;
for(var i = 0;i sum += isbn.charAt(i) * (10 - i);
}
var checksum = sum % 11;
if(checksum == 10) checksum = "X";
if(isbn.charAt(9) != checksum) return false;
} else {
if(!isbn.match(\^\d{12}\)) return false;
for(var i = 0;i sum += isbn.charAt(i) * (i % 2 == 0 ? 1 : 3);
}
var checksum = sum % 10;
if(isbn.charAt(12) != checksum) return false;
}
return true;
},
getTitle: function() {
return this.title;
},
setTitle: function(title) {
this.title = title || "";
},
getAuthor: function() {
return this.author;
},
setAuthor: function(author) {
this.author = author || "";
},
display: function() {
return "Book: ISBN: " + this.isbn + ",Title: " + this.title + ",Author: " + this.author;
}
};

现在就可以通过接口Publication来与外界进行通信。赋值方法也在构造器内部完成,不需要实现两次同样的验证,看似非常完美的完全暴露对象方案了。虽然能通过set存储器来设置属性,但这些属性仍然是公有的,可以直接赋值。但此方案到此已经无能为力了,我会在第二种信息隐藏解决方案中来优化。尽管如此,此方案对于那些没有深刻理解作用域的新手非常容易上手。唯一的不足是不能保护内部数据且存储器增加了多余的不必要代码。
2. 使用命名规则的私有方法。就是使用下划线来标识私有成员,避免无意中对私有成员进行赋值,本质上与完全暴露对象是一样的。但这却避免了第一种方案无意对私有成员进行赋值操作,却依然不能避免有意对私有成员进行设置。只是说定义了一种命名规范,需要团队成员来遵守,不算是一种真正的内部信息隐藏的完美方案。
复制代码 代码如下:

var Publication = new Interface("Publication", ["getIsbn", "setIsbn", "getTitle", "setTitle", "getAuthor", "setAuthor", "display"]);
var Book = function(isbn, title, author) {
// implements Publication interface
this.setIsbn(isbn);
this.setTitle(title);
this.setAuthor(author);
}
Book.prototype = {
getIsbn: function() {
return this._isbn;
},
setIsbn: function(isbn) {
if(!this._checkIsbn(isbn)) {
throw new Error("Book: Invalid ISBN.");
}
this._isbn = isbn;
},
_checkIsbn: function(isbn) {
if(isbn == undefined || typeof isbn != "string") return false;
isbn = isbn.replace("-", "");
if(isbn.length != 10 && isbn.length != 13) return false;
var sum = 0;
if(isbn.length == 10) {
if(!isbn.match(\^\d{9}\)) return false;
for(var i = 0;i sum += isbn.charAt(i) * (10 - i);
}
var checksum = sum % 11;
if(checksum == 10) checksum = "X";
if(isbn.charAt(9) != checksum) return false;
} else {
if(!isbn.match(\^\d{12}\)) return false;
for(var i = 0;i sum += isbn.charAt(i) * (i % 2 == 0 ? 1 : 3);
}
var checksum = sum % 10;
if(isbn.charAt(12) != checksum) return false;
}
return true;
},
getTitle: function() {
return this._title;
},
setTitle: function(title) {
this._title = title || "";
},
getAuthor: function() {
return this._author;
},
setAuthor: function(author) {
this._author = author || "";
},
display: function() {
return "Book: ISBN: " + this.getIsbn() + ",Title: " + this.getTitle() + ",Author: " + this.getAuthor();
}
};

注意:除了isbn,title,author属性被加上"_"标识为私有成员外,checkIsbn()也被标识为私有方法。

3. 通过闭包来真正私有化成员。如果对闭包概念中的作用域和嵌套函数不熟悉的朋友,可以参考"面向对象的Javascript之一(初识Javascript)"文章,这里不再详细论述。
复制代码 代码如下:

var Publication = new Interface("Publication", ["getIsbn", "setIsbn", "getTitle", "setTitle", "getAuthor", "setAuthor", "display"]);
var Book = function(newIsbn, newTitle, newAuthor) {
// private attribute
var isbn, title, author;
// private method
function checkIsbn(isbn) {
if(isbn == undefined || typeof isbn != "string") return false;
isbn = isbn.replace("-", "");
if(isbn.length != 10 && isbn.length != 13) return false;
var sum = 0;
if(isbn.length == 10) {
if(!isbn.match(\^\d{9}\)) return false;
for(var i = 0;i sum += isbn.charAt(i) * (10 - i);
}
var checksum = sum % 11;
if(checksum == 10) checksum = "X";
if(isbn.charAt(9) != checksum) return false;
} else {
if(!isbn.match(\^\d{12}\)) return false;
for(var i = 0;i sum += isbn.charAt(i) * (i % 2 == 0 ? 1 : 3);
}
var checksum = sum % 10;
if(isbn.charAt(12) != checksum) return false;
}
return true;
}
// previleged method
this.getIsbn = function() {
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(newTitle) {
title = newTitle || "";
},
this.getAuthor: function() {
return author;
},
this.setAuthor: function(newAuthor) {
author = newAuthor || "";
}
// implements Publication interface
this.setIsbn(newIsbn);
this.setTitle(newTitle);
this.setAuthor(newAuthor);
}
// public methods
Book.prototype = {
display: function() {
return "Book: ISBN: " + this.getIsbn() + ",Title: " + this.getTitle() + ",Author: " + this.getAuthor();
}
};

这种方案与上一种有哪些不同呢?首先,在构造器中使用var来声明三个私有成员,同样也声明了私有方法checkIsbn(),仅仅在构造器中有效。使用this关键字声明特权方法,即声明在构造器内部但却可以访问私有成员。任何不需要访问私有成员的方法都在Book.prototype中声明(如:display),也即是将需要访问私有成员的方法声明为特权方法是解决这个问题的关键。但此访问也有一定缺陷,如对每一个实例而言,都要创建一份特权方法的副本,势必需要更多内存。我们继续优化,采用静态成员来解决所面临的问题。顺便提一句:静态成员仅仅属于类,所有的对象仅共用一份副本(在"面向对象的Javascript之二(实现接口)中有说明,参见Interface.ensureImplements方法"),而实例方法是针对对象而言。
复制代码 代码如下:

var Publication = new Interface("Publication", ["getIsbn", "setIsbn", "getTitle", "setTitle", "getAuthor", "setAuthor", "display"]);
var Book = (function() {
// private static attribute
var numsOfBooks = 0;
// private static method
function checkIsbn(isbn) {
if(isbn == undefined || typeof isbn != "string") return false;
isbn = isbn.replace("-", "");
if(isbn.length != 10 && isbn.length != 13) return false;
var sum = 0;
if(isbn.length == 10) {
if(!isbn.match(\^\d{9}\)) return false;
for(var i = 0;i sum += isbn.charAt(i) * (10 - i);
}
var checksum = sum % 11;
if(checksum == 10) checksum = "X";
if(isbn.charAt(9) != checksum) return false;
} else {
if(!isbn.match(\^\d{12}\)) return false;
for(var i = 0;i sum += isbn.charAt(i) * (i % 2 == 0 ? 1 : 3);
}
var checksum = sum % 10;
if(isbn.charAt(12) != checksum) return false;
}
return true;
}
// return constructor
return function(newIsbn, newTitle, newAuthor) {
// private attribute
var isbn, title, author;
// previleged method
this.getIsbn = function() {
return isbn;
};
this.setIsbn = function(newIsbn) {
if(!Book.checkIsbn(newIsbn)) {
throw new Error("Book: Invalid ISBN.");
}
isbn = newIsbn;
}
this.getTitle = function() {
return title;
},
this.setTitle = function(newTitle) {
title = newTitle || "";
},
this.getAuthor = function() {
return author;
},
this.setAuthor = function(newAuthor) {
author = newAuthor || "";
}
Book.numsOfBooks++;
if(Book.numsOfBooks > 50) {
throw new Error("Book: at most 50 instances of Book can be created.");
}
// implements Publication interface
this.setIsbn(newIsbn);
this.setTitle(newTitle);
this.setAuthor(newAuthor);
};
})();
// public static methods
Book.convertToTitle = function(title) {
return title.toUpperCase();
}
// public methods
Book.prototype = {
display: function() {
return "Book: ISBN: " + this.getIsbn() + ",Title: " + this.getTitle() + ",Author: " + this.getAuthor();
}
};

这种方案与上种相似,使用var和this来创建私有成员和特权方法。不同之处在于使用闭包来返回构造器,并将checkIsbn声明为私有静态方法。可能有人会问,我为什么要创建私有静态方法,答案在于使所有对象公用一份函数副本而已。我们这里创建的50个实例都只有一个方法副本checkIsbn,且属于类Book。根据需要,你也可以创建公有的静态方法供外部调用(如:convertToTitle)。这里我们继续考虑一个问题,假设以后我们需要对不同的书做限制,比如>最大印发量为500,>最大印发量为1000,也即说需要一个最大印发量的常量。思考一下,利用已有的知识,我们如何声明一个常量呢?其实不难,我们想想,可以利用一个只有访问器的私有特权方法就可以实现。
复制代码 代码如下:

var Publication = new Interface("Publication", ["getIsbn", "setIsbn", "getTitle", "setTitle", "getAuthor", "setAuthor", "display"]);
var Book = (function() {
// private static attribute
var numsOfBooks = 0;
// private static contant
var Constants = {
"MAX_JAVASCRIPT_NUMS": 500,
"MAX_NET_NUMS": 1000
};
// private static previleged method
this.getMaxNums(name) {
return Constants[name.ToUpperCase()];
}
// private static method
function checkIsbn(isbn) {
if(isbn == undefined || typeof isbn != "string") return false;
isbn = isbn.replace("-", "");
if(isbn.length != 10 && isbn.length != 13) return false;
var sum = 0;
if(isbn.length == 10) {
if(!isbn.match(\^\d{9}\)) return false;
for(var i = 0;i sum += isbn.charAt(i) * (10 - i);
}
var checksum = sum % 11;
if(checksum == 10) checksum = "X";
if(isbn.charAt(9) != checksum) return false;
} else {
if(!isbn.match(\^\d{12}\)) return false;
for(var i = 0;i sum += isbn.charAt(i) * (i % 2 == 0 ? 1 : 3);
}
var checksum = sum % 10;
if(isbn.charAt(12) != checksum) return false;
}
return true;
}
// return constructor
return function(newIsbn, newTitle, newAuthor) {
// private attribute
var isbn, title, author;
// previleged method
this.getIsbn = function() {
return isbn;
};
this.setIsbn = function(newIsbn) {
if(!Book.checkIsbn(newIsbn)) {
throw new Error("Book: Invalid ISBN.");
}
isbn = newIsbn;
}
this.getTitle = function() {
return title;
},
this.setTitle = function(newTitle) {
title = newTitle || "";
},
this.getAuthor = function() {
return author;
},
this.setAuthor = function(newAuthor) {
author = newAuthor || "";
}
Book.numsOfBooks++;
if(Book.numsOfBooks > 50) {
throw new Error("Book: at most 50 instances of Book can be created.");
}
// implements Publication interface
this.setIsbn(newIsbn);
this.setTitle(newTitle);
this.setAuthor(newAuthor);
};
})();
// public static methods
Book.convertToTitle = function(title) {
return title.toUpperCase();
}
// public methods
Book.prototype = {
display: function() {
return "Book: ISBN: " + this.getIsbn() + ",Title: " + this.getTitle() +
",Author: " + this.getAuthor() + ", Maximum: ";
},
showMaxNums: function() {
return Book.getMaxNums("MAX_JAVASCRIPT_NUMS");
}
};

最完美的情况就是你所封装的程序对调用者而言,仅仅需要知道你的接口就可以,根本不关心你如何实现。但问题在于,随着工程量的扩大,你的封装内容必然会增大,在项目发生交接时,对于一个对作用域和闭包等概念不熟悉的成员来说,维护难度会变得如此之大。有些时候应需求响应必须改动源码(这里不一定指改接口),可能是新增一些细节,即使拿到你的源码却无从下手,那就不好做了。因此,我的建议:封装不要过度,接口一定要清晰,可扩展。
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

@JsonIdentityInfo注解在Java中使用Jackson的重要性是什么? @JsonIdentityInfo注解在Java中使用Jackson的重要性是什么? Sep 23, 2023 am 09:37 AM

当对象在Jackson库中具有父子关系时,将使用@JsonIdentityInfo注释。@JsonIdentityInfo 注解 用于在序列化和反序列化过程中指示对象身份。ObjectIdGenerators.PropertyGenerator是一个抽象占位符类,用于表示要使用的对象标识符来自POJO属性的情况。语法@Target(value={ANNOTATION_TYPE,TYPE,FIELD,METHOD,PARAMETER})@Retention(value=RUNTIME)public

集邦咨询:英伟达 Blackwell 平台产品带动台积电今年 CoWoS 产能提高 150% 集邦咨询:英伟达 Blackwell 平台产品带动台积电今年 CoWoS 产能提高 150% Apr 17, 2024 pm 08:00 PM

本站4月17日消息,集邦咨询(TrendForce)近日发布报告,认为英伟达Blackwell新平台产品需求看涨,预估带动台积电2024年CoWoS封装总产能提升逾150%。英伟达Blackwell新平台产品包括B系列的GPU,以及整合英伟达自家GraceArmCPU的GB200加速卡等。集邦咨询确认为供应链当前非常看好GB200,预估2025年出货量有望超过百万片,在英伟达高端GPU中的占比达到40-50%。在英伟达计划下半年交付GB200以及B100等产品,但上游晶圆封装方面须进一步采用更复

AMD 'Strix Halo” FP11 封装尺寸曝光:和英特尔 LGA1700 相当,比 Phoenix 大 60% AMD 'Strix Halo” FP11 封装尺寸曝光:和英特尔 LGA1700 相当,比 Phoenix 大 60% Jul 18, 2024 am 02:04 AM

本站7月9日消息,AMDZen5架构“Strix”系列处理器会有两种封装方案,其中较小的StrixPoint将采用FP8封装,而StrixHalo将会采用FP11封装。图源:videocardz消息源@Olrak29_最新曝料称StrixHalo的FP11封装尺寸为37.5mm*45mm(1687平方毫米),和英特尔AlderLake、RaptorLakeCPU的LGA-1700封装尺寸相同。AMD最新的PhoenixAPU采用FP8封装方案,尺寸为25*40mm,这意味着StrixHalo的F

探索Go语言中的面向对象编程 探索Go语言中的面向对象编程 Apr 04, 2024 am 10:39 AM

Go语言支持面向对象编程,通过类型定义和方法关联实现。它不支持传统继承,而是通过组合实现。接口提供了类型间的一致性,允许定义抽象方法。实战案例展示了如何使用OOP管理客户信息,包括创建、获取、更新和删除客户操作。

C++ 函数如何通过封装代码来提高 GUI 开发的效率? C++ 函数如何通过封装代码来提高 GUI 开发的效率? Apr 25, 2024 pm 12:27 PM

通过封装代码,C++函数可以提高GUI开发效率:代码封装:函数将代码分组到独立单元,使代码易于理解和维护。可重用性:函数可创建通用功能供应用程序中重复使用,减少重复编写和错误。简洁代码:封装代码使主逻辑简洁,便于阅读和调试。

PHP高级特性:面向对象编程的最佳实践 PHP高级特性:面向对象编程的最佳实践 Jun 05, 2024 pm 09:39 PM

PHP中OOP最佳实践包括命名约定、接口与抽象类、继承与多态、依赖注入。实战案例包括:使用仓库模式管理数据,使用策略模式实现排序。

PHP中的封装技术及应用 PHP中的封装技术及应用 Oct 12, 2023 pm 01:43 PM

PHP中的封装技术及应用封装是面向对象编程中的一个重要概念,它指的是将数据和对数据的操作封装在一起,以便提供对外部程序的统一访问接口。在PHP中,封装可以通过访问控制修饰符和类的定义来实现。本文将介绍PHP中的封装技术及其应用场景,并提供一些具体的代码示例。一、封装的访问控制修饰符在PHP中,封装主要通过访问控制修饰符来实现。PHP提供了三个访问控制修饰符,

Go语言的面向对象特性解析 Go语言的面向对象特性解析 Apr 04, 2024 am 11:18 AM

Go语言支持面向对象编程,通过struct定义对象,使用指针接收器定义方法,并通过接口实现多态。面向对象特性在Go语言中提供了代码重用、可维护性和封装,但也存在缺乏传统类和继承的概念以及方法签名强制类型转换的局限性。

See all articles