Object-oriented Javascript 3 (encapsulation and Information Hiding)

Source: Internet
Author: User

At the same time, we know that in the object-oriented advanced language, creating an object containing private members is one of the most basic features. It provides attributes and methods to access private members to hide internal details. Although JS is also object-oriented, there is no internal mechanism to directly indicate whether a member is public or private. In other words, relying on the language flexibility of JS, we can create public, private, and privileged members. Information Hiding is our goal, and encapsulation is our method to achieve this goal. The following example shows how to create a class to store book data and display the data on the webpage.

1. The simplest is to completely expose the object. Create a class using the constructor. All the attributes and methods are accessible externally.Copy codeThe Code is as follows: 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;
}

The display method depends on whether isbn is correct. If not, you will not be able to obtain images and links. With this in mind, each book isbn must exist, and the title and author of the book are optional. On the surface, you only need to specify an isbn parameter. However, the integrity of isbn cannot be guaranteed. Based on this, we add the isbn verification to make the library check more robust.Copy codeThe Code is as follows: 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 <9; 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 <12; 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;
}
};

We added checkIsbn () to verify the validity of ISBN and ensure that display () runs properly. However, the demand has changed. Each book may have multiple versions, which means that the same book may have multiple ISBN numbers. You need to maintain an algorithm for controlling the selected version. At the same time, although data integrity can be checked, external access to internal members (such as assigning values to isbn, title, and author) cannot be controlled, and internal data cannot be protected. We continue to improve this solution by using interfaces (providing get accessors/set storage ).Copy codeThe Code is as follows: 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 <9; 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 <12; 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;
}
};

Now you can use the Publication interface to communicate with the outside world. The assignment method is also completed in the constructor. It does not need to perform the same verification twice. It seems like a perfect completely exposed object solution. Although you can set attributes through set storage, these attributes are still public and can be assigned directly. However, this solution is no longer feasible. I will optimize it in the second Information Hiding solution. However, this solution is very easy for beginners who do not have a deep understanding of the scope. The only disadvantage is that internal data cannot be protected and unnecessary code is added to the memory.
2. Use the private method of the naming rule. It is to use underscores to identify private members, so as to avoid assigning values to Private Members accidentally. Essentially, it is the same as completely exposing objects. However, this avoids the first scheme from having no intention to assign values to Private Members, but still cannot avoid setting private members intentionally. It just defines a naming convention that requires team members to comply with. It is not a perfect solution for hiding internal information.Copy codeThe Code is as follows: 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 <9; 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 <12; 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 ();
}
};

Note: In addition to the isbn, title, and author attributes, the "_" attribute is identified as a private member, and the checkIsbn () attribute is also identified as a private method.

3. Actually private members through closures. If you are not familiar with the scope and nested functions of the closure concept, you can refer to the article "one of object-oriented Javascript (originally known as Javascript)", which will not be discussed in detail here.Copy codeThe Code is as follows: 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 <9; 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 <12; 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 ();
}
};

What is the difference between this solution and the previous one? First, use var in the constructor to declare three private members, and also declare the private method checkIsbn (), which is only valid in the constructor. Use the this keyword to declare the privileged method, that is, it is declared inside the constructor but can access private members. Any method that does not need to access private members is in Book. declared in prototype (such as: display), that is, declaring the methods that need to access private members as privileged methods is the key to solving this problem. However, this access also has some drawbacks. For example, if you want to create a copy of the privileged method for each instance, more memory is required. We continue to optimize and use static members to solve the problems we face. By the way, static members only belong to the class, and all objects share only one copy (which is described in "Object-Oriented Javascript II (Implementation Interface). For more information, see Interface. ensureImplements method "), and the instance method is for objects.Copy codeThe Code is as follows: 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 <9; 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 <12; 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 ();
}
};

This solution is similar to the above, using var and this to create private members and privileged methods. The difference is that the closure is used to return the constructor and the checkIsbn is declared as a private static method. Some may ask why I want to create a private static method. The answer is to make all objects share a copy of the function. The 50 instances we created here have only one method copy, checkIsbn, and belong to the class Book. You can also create public static methods for external calls (for example, convertToTitle) as needed ). Here we will continue to consider a problem. Suppose we need to limit different books in the future, for example, <Javascript advanced programming> the Maximum printing volume is 500, <. NET> the Maximum printing volume is 1000, that is, a constant with the maximum printing volume is required. Think about how to declare a constant using the existing knowledge? In fact, it is not difficult to think about it. We can use a private privileged method with only accessors.Copy codeThe Code is as follows: 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 <9; 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 <12; 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 ");
}
};

The perfect situation is that your encapsulated program only needs to know your interface for callers and does not care how you implement it. But the problem is that as the amount of work expands, your package content will inevitably increase. when a project is handed over, for a Member who is not familiar with the concepts of scope and closure, the maintenance difficulty will become so great. In some cases, the source code must be changed in response to requests (not necessarily the interface to be changed here). Some details may be added. Even if you get your source code, it will not work. Therefore, my suggestion: Do not over-encapsulate the interface. The interface must be clear and scalable.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.