4- Private & public
Hầu hết các ngôn ngữ lập trình hướng đối tượng quen thuộc như Java hay C# đều hỗ trợ từ khóa public và private cho các thuộc tính cũng như phương thức trong class. Với JavaScript chúng ta cũng có thể tạo ra các pattern để giả lập hai từ khóa này trong các class (cũng giả lập luôn!!):
Hoặc chúng ta có thể xây đựng một cách đơn giản theo các Pattern dưới đây:
//Public:
function Constructor(...) {
this.membername = value;
}
Constructor.prototype.membername = value;
//Private:
function Constructor(...) {
var that = this;
var membername = value;
function membername(...) {...}
}
//chú ý
function membername(...) {...}
//có thể thay thế cho
var membername = function membername(...) {...};
//Privileged
function Constructor(...) {
this.membername = function (...) {...};
}
5- Kế thừa với prototype
Nếu như các bạn đã từng tìm hiểu về kế thừa với JavaScript thì hẳn các bạn nhận ra rằng có khá nhiều cách để cài đặt, tuy nhiên trong bài viết này tôi sẽ chỉ giới thiệu một cách mà theo cá nhân là khá đơn giản và tiện lợi khi triển khai, đó là sử dụng prototype và constructor.
Trước hết, giả sử chúng ta có hai class ParentClass và ChildClass. Để thực hiện cho ChildClass kế thừa ParentClass ta lần lượt các bước sau:
Như vậy với hai bước đơn giản ta đã thực hiện được kế thừa trong JavaScript. Tuy nhiên còn khá nhiều vấn đề nảy sinh trong khi bạn xây dựng các class phức tạp, ví dụ như làm thế nào để gọi phương thức của ParentClass trong khi ChildClass đã overridden nó, hay vấn đề về virtual class tức là class chỉ có thể kế thừa mà không cho phép tạo một thể hiện cho nó. Chúng ta sẽ lần lượt giải quyết vấn đề này tiếp sau đây.
Nhưng trước khi đi vào các vấn đề đã nêu ta hãy làm một ví dụ thú vị sau:
//Class Động vật có vú
function Mammal(name){
this.name=name;
this.offspring=[];//Mùa sinh sản!!!
}
//Phương thức sinh con (hoặc có con)
Mammal.prototype.haveABaby=function(){
var newBaby=new Mammal("Baby "+this.name); this.offspring.push(newBaby);
return newBaby;
}
//Hàm trả về tên con vật
Mammal.prototype.toString=function(){
return '[Mammal "'+this.name+'"]';
}
//Class Mèo kế thừa Class Động vật có vú
Cat.prototype = new Mammal();
Cat.prototype.constructor=Cat;
//Constructor của class Cat
function Cat(name){
this.name=name;
}
//Hàm trả về tên con vật
Cat.prototype.toString=function(){
return '[Cat "'+this.name+'"]';
}
//Tạo Mr.Bill !!!
var someAnimal = new Mammal('Mr. Bill');
//Tạo mèo Tom
var myPet = new Cat('Tom');
alert('someAnimal is '+someAnimal);
//Trả về 'someAnimal is [Mammal "Mr. Bill"]'
alert('myPet is '+myPet);
//Trả về 'myPet is [Cat "Tom"]'
//Cho mèo sinh con (kế thừa từ Mammal)
myPet.haveABaby();
//Thông báo về số con của mèo Tom
alert(myPet.offspring.length);
alert(myPet.offspring[0]);
//Trả về [Mammal "Baby Tom"]'
*Vấn đề Super & Sub Class
Hãy thử mở rộng ví dụ trên để ta có dịp minh họa cách mà một class con gọi đến một phương thức của class cha trong khi nó đã được overridden. Ta sẽ muốn rằng ngay sau khi mèo con được sinh ra nó sẽ kêu một tiếng "meeoo!" chẳng hạn. Để làm được điều này ta sẽ viết một hàm haveBaby của riêng class Cat trong đó sẽ gọi lại hàm haveBaby trong class Mammal:
Cat.prototype.haveABaby=function(){
Mammal.prototype.haveABaby.call(this);
alert("mew!");
}
Ở đây, các bạn hãy nhớ lại cách thức sử dụng phương thức call của đối tượng Function mà ta đã từng đề cập. Như vậy với việc sử dụng call() ta hoàn toàn có thể làm được giống như phương thức "super()" trong Java và các ngôn ngữ khác.
Do vậy, từ bây giờ để cho tiện thì tại sao chúng ta không cài đặt luôn một "super" cho class của chúng ta. Làm điều đó không mấy phiền hà như sau:
Cat.prototype=newMammal();
Cat.prototype.constructor=Cat;
Cat.prototype.parent=Mammal.prototype;//"super"
...
Cat.prototype.haveABaby=function(){
var theKitten = this.parent.haveABaby.call(this);
//"super(this)"
alert("mew!");
return theKitten;
}
//Các bạn sẽ có thắc mắc nhỏ là tại sao không dùng từ super thay cho parent? Lí do là vì hình như JavaScript có ý định sài từ này trong các phiên bản tương lai thì phải! Một điều nữa nếu bạn băn khoăn là từ parent đã được DOM sử dụng khi truy cập đến các node, điều này thì cứ vô tư đi vì đây là JavaScript mà!!!:D
*Vấn đề virtual Class
Một số ngôn ngữ lập trình hướng đối tượng có giải quyêt vấn đề về virtual class, tức là một class không thể có một thể hiện của chính nó, nhưng có thể kế thừa từ nó. Như trong ví dụ trên, ta muốn thêm vào một class LivingThing mà Mammal sẽ kế thừa từ nó, nhưng ta không muốn ai đó lại có thể tạo ra một LivingThing không mong muốn (chẳng hạn LivingStone!!:P). Với JavaScript ta có thể thực hiện điều này bằng cách thay thế function bằng một object cho virtual class.
//Khai báo class kiểu JSON
LivingThing={
beBorn : function(){
this.alive=true;
}
}
//...
Mammal.prototype = LivingThing;
Mammal.prototype.parent = LivingThing;
//Để ý rằng không phải là 'LivingThing.prototype'
Mammal.prototype.haveABaby=function(){
this.parent.beBorn.call(this);
var newBaby=new this.constructor("Baby "+this.name);
this.offspring.push(newBaby);
return newBaby;
}
Như vậy nếu một ai đó khai báo như sau:
var stone= new LivingThing(); // Sẽ gây lỗi
Bởi vì LivingThing bây giờ không phải là kiểu function mà có kiểu là object, do đó không thể coi nó như là một constructor với từ khóa new.
Như các bạn đã thấy, với cách cài đặt kế thừa trên ta luôn phải thực hiện hai dòng lệnh bắt buộc mỗi khi thực hiện kế thừa. Để cho tiện lợi, ta có thể mở rộng khả năng này cho bản thân object Function trong JavaScript, và coi đó như là một thuộc tính vốn có của BLOCKED SCRIPT
Function.prototype.inheritsFrom=function(parentClsOrObj){
if (parentClsOrObj.constructor == Function ){
//Normal Inheritance
this.prototype = new parentClsOrObj;
this.prototype.constructor = this;
this.prototype.parent=parentClsOrObj.prototype;
}
else
{
//Pure Virtual Inheritance
this.prototype = parentClsOrObj;
this.prototype.constructor = this;
this.prototype.parent = parentClsOrObj;
}
return this;
}
//
//
LivingThing = {
beBorn : function(){
this.alive = true;
}
}
//
//
function Mammal(name){
this.name=name;
this.offspring=[];
}
Mammal.inheritsFrom( LivingThing );
Mammal.prototype.haveABaby=function(){
this.parent.beBorn.call(this);
var newBaby=new this.constructor( "Baby "+this.name);
this.offspring.push(newBaby);
return newBaby;
}
//
//
function Cat( name ){
this.name=name;
}
Cat.inheritsFrom( Mammal );
Cat.prototype.haveABaby=function(){
var theKitten = this.parent.haveABaby.call(this);
alert("mew!");
return theKitten;
}
Cat.prototype.toString=function(){
return '[Cat "'+this.name+'"]';
}
//
//
var tom = new Cat( "Tom" );
var kitten = tom.haveABaby( ); // mew!
alert( kitten ); // [Cat "Baby Tom"]
////////////////////////////////////////////////////////