2008/08/08

Javascript 中的物件導向

有不少人看輕 javascript, 也有更多人像我一樣,只學到古早的 javascript,我早知道 Javascript 可以實現物件導向觀念的程式架構,也早知道 JSON,這邊就來跟大家分享一下怎麼在 Javascript 實現物件導向的程式架構。

Javascript 是 prototype 導向的寫法,與一般 C++, Java, C# 都不一樣的方式,不過它卻讓 Javascript 這種解譯式的語言有辦法使用封裝、繼承、與多形。也正因為 javascript 的解譯式天性,讓你在設計程式時可以即時動態改變物件,包含屬性與方法,甚至是整個物件。

Javascript 的物件表示法有不同的形式,從 new Object, new Function, 或是直接由 JSON 建構都行。本文主要來自 Introduction to Object-Oriented JavaScript ,因此就以該文為範例。


function MyClass()
{
.....
}

var c = new MyClass();


上面的例子裡面,雖然函數(物件)命名為 MyClass, 但是基本上,Javascript 裡面對 Class, Object, Instance 的區分並不是非常嚴謹,只能在使用時來判斷它。譬如就上例而言,似乎 var c = new MyClass() 是產生一個 instance, 例是這個 MyClass 確實也是個函數,可以直接呼叫!不過只要想成呼叫時,也等於偷偷 new MyClass() 也行,或是說,其實背後,全都是 Object,那可能就不會太奇怪了。舉例:


function MyClass()
{
this.MyData = "Some Text";
this.MoreData = "Some More Text";
alert ("in MyClass()");
} // end of MyClass()
var c = new MyClass();
alert (c.MyData + ", " + c.MoreData);

上例的結果會出現兩次 alert, 想來也就順理成章了。若將最後兩行直接改成:
MyClass(); 這樣呼叫函數,嘿嘿,也說得通不是?至於 this 的用法應該不必多說吧?


function MyClass()
{
this.MyData = "Some Text";
this.MoreData = "Some More Text";
} // end of MyClass()
MyClass.prototype.showData = function()
{
alert ("MyData: " + this.MyData + ", MoreData: " + this.MoreData);
} // end of showData()
var c = new MyClass();
// alert (c.MyData + ", " + c.MoreData);
c.showData();

這例子把 MyClass() 的「屬性」顯示出來,原來是在外面存取屬性,改成增加「方法」 showData() 的方式。這範例主要在講方法,即 showData() 的定義方式,及其引用本身物件(類別?)內屬性的方法。

不過,這例子其實是很不好的示範,自己顯示自己常常會牽涉到 GUI 的議題,最好是交由「外面」模組來實作,也就是提供 getData() 取代 showData()

接下來講講封裝。Javascript 並沒有真正良好的封裝,譬如什麼 public, private, 什麼的一堆有的沒的,當然有好有壞,反正嘛,彈性太大是會有問題,但是只要你會用,易用,易除錯,彈性帶來的好處也不小。


function MyClass()
{
this.MyData = "Some Text";
this.MoreData = "Some More Text";
} // end of MyClass()
MyClass.prototype.setData= function(theData)
{
this.MyData = theData;
} // end of setData()
MyClass.prototype.getData = function()
{
return this.MyData;
} // end of getData()

var c1 = new MyClass();
var c2 = new MyClass();
c1.setData("I am wade");
alert ("c1: " + c1.getData() + ", c2: " + c2.getData());

這邊真正示範了將 MyClass() 當成類別的作法,不止有屬性也有方法,自行研究研究。不同的物件(c1, c2)因為透過 setData() 而改變其屬性。事實上也可以寫類似 constructor 的方式。


function Animal(name)
{
this.name = name;
}
Animal.prototype.say = function(what)
{
if (what)
alert (this.name + " said: " + what);
else
alert (this.name + " said something.");
} // end of say()

//Inherited class constructor
function Dog(name)
{
Animal.call(this, name);
}
Dog.prototype = new Animal();

Dog.prototype.ChangeName = function(newname)
{
this.name = newname;
}

var dog = new Dog("My Poppy");
dog.say();
dog.ChangeName("Wader");
dog.say();

這個例子先是展示了繼承,Dog 繼承了 Animial 的 say() 方法及 name 屬性, 這些繼承能力是透過 Animal.call(this, name); 再加上 Dog.prototype = new Animal(); 來完成的。這也透露出一件訊息,就是前面講 Javascript 是 prototype based 這件事,但是若對 OO 敏感的人也可以看出來,Function 在此處就表現了它自身是 Object 的概念。
而後面第二次的 say() 展現了跟先前 setData() 同樣的功效,只是這次是用在繼承上。

接下來看看多型,不過減省版面下,請將下面的碼直接接在上例的後面,當然要把上例後面四行拿掉再加上去也是不錯的作法:

Dog.prototype.say = function(what)
{
alert (this.name + " woof: " + what);
} // end of say() of Dog

function Cat(name)
{
Animal.call(this, name);
} // end of Cat()

Cat.prototype.say = function(what)
{
Animal.prototype.say.call(this, what);
} // end of say() of Cat

dog.say("who are you?");
var cat = new Cat("Cat Girl");
cat.say("I love you");


Dog 的 say() 方法其實是用新的覆蓋掉 Animal 的,而 Cat 的 say() 方法,則是用繼承的作法。意思是,Cat, Dog 的 say() 其實已經與原來的 Animal 有不同意見了。

0 意見: