上一章:函式 目錄 下一章:介面


類別 编辑

類別是物件導向語言的基礎。不像其他的 .NET 語言,Boo 在程式裡並不一定要有類別。

下面的類別 Person 表示了一個人,而裡面有兩個欄位:_name_age與兩個方法:建構子與 ToString。類別可以說是建立新物件的樣板,誠如星星形狀的模子是製作星星形狀餅乾的樣板一樣。回到主題,以 Person 產生出來的物件包含兩個資訊,人的名字與年齡。建構子在物件被建構出來時進行初始化欄位資料的動作,建構之後,就可以呼叫該物件的方法。

# class1.boo
class Person:
	_name as string
	_age as int
	
	def constructor(name as string, age as int):
		_name = name
		_age = age

	def ToString():
		return _name

p = Person("john",36)
print p.ToString()

輸出結果

john

上面的代碼就跟下面的 C# 程式一樣:

using System;

class Person {
	protected string _name;
	protected int _age;
	
	public Person(string name, int age) {
		_name = name;
		_age = age;
	}

	public string ToString() {
		return _name;
	}
}
	
class TestPerson {
	public static void Main(string[] args) {
		Person p = new Person("john",36);
		Console.WriteLine(p.ToString());		
	}
}

Boo 的版本明顯地比較短,而且也不需要大括號 { },比較易於了解。Boo 的欄位可視性預設為 protected,方法可視性則預設為 public﹔C# 對可視性則完全預設為 private。最後面的代碼都會被放在隱藏模組類別裡的 Main 方法,在這裡,編譯器會將此隱藏模組類別命名為 class1Module。你可以明確地將模組類別寫出,讓 Boo 看起來更像 C# 的代碼:

import System
[module]
class TestMain:
	public static def Main():
		Console.WriteLine("hello, world!")

物件屬性 编辑

你可以把物件的屬性當作是物件的公開欄位。在 Windows form 程式裡,我們會常看到大部分控制項都有 Text 屬性。如果你改變了 form 的 Text 屬性,form 的標題就會被改變。怎麼做的呢?這是因為屬性只是一對函式:getter 用來回傳值,而 setter則用來設置內部狀態。所以 form.Text="Hello!"實際上是呼叫了 form.set_Text("Hello!")(很明顯地跟 Java 相似)。在前面提過的 Person 類別裡,我們可以增加一個 Name 的屬性,實際上是存取 _name 欄位:

	Name:
		get:
			return _name
		set:
			_name = value

Boo 提供了一個快捷的方法,讓代碼看起來更簡潔,新版本的 'Person 如下:

class Person:	
	[property(Name)]
	_name as string

	[property(Age)]
	_age as int

	override def ToString():
		return _name
		
p = Person(Name:"john",Age:36)
print p

注意,我們不再需要建構子來初始這些欄位了。有了這些公開的屬性,我們可以在呼叫建構子的時候,表明要初始化這些屬性,這讓初始化物件的代碼更清楚(這個方法只能在呼叫建構子時使用)。

Getters與預處理條件 编辑

唯讀屬性是很常見的情況,這個時候只需要定義 getter 函式:

  Name:
    get
      return _name

Boo 提供 getter attribute,讓你可以用更簡潔的方式來寫這段代碼:

  [getter(Name)]
  _name as string

一般來說,越早處理不好的輸入越好。這會簡化後續的處理﹔之後就可以假設資料是正確的,因為有堅固的防火牆在系統與真實世界之間。舉例來說,類別:Person 的 Name 應該避免為空白或是 null 字串。 property attribute 提供了一個可選擇的引數,你可以指定預處理條件,當符合此條件時,才可以進行 setter 動作,否則就失敗。

  [property(Name,value is not null and len(value) > 0)]
  _name = "que?"

這樣的代碼比下面的代碼短多了:

  _name = "que?"
  Name:
    get:
      return _name
    set:
      assert value is not null and len(value) > 0
      _name = value

這樣的寫法的確節省了不少行數,但實際上編譯出來的結果並沒有。否則,我們將結束繼續作像是在同一行放置數個指派等的笨事( Otherwise, we would end up doing silly things like placing multiple assigments etc on the same line.)。這樣寫的好處,是讓程式設計師的意圖更為清楚。在 自訂Attribute 一章裡,我將示範一個新的屬性:NonEmptyString,這會讓代碼的意圖更為容易被了解,也更容易輸入。

為甚麼需要屬性 编辑

存取屬性值,其實很像是呼叫函式,所以屬性的存取明顯地比存取公開欄位要慢 (雖然有最佳化)。但是這仍有幾個好處,第一,屬性對框架來說是公開且可用的。最好的例子是 Windows Forms 裡的控制項 PropertyGrid

// 譯註原程式無法執行因為少了 Application.Run這裡稍作改寫
import System.Windows.Forms from "System.Windows.Forms"

Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(false)

f=Form()
f.Text="Test"
pg=PropertyGrid( Dock: DockStyle.Fill, CommandsVisibleIfAvailable: true, Text: "Property Grid Demo" )
f.Controls.Add( pg )
pg.SelectedObject=f
f.Show()

Application.Run( f )

這很酷吧,你可以檢閱剛剛建立的 form 的所有屬性。

第二,在需要改變屬性實作時,比較不會影響到函式庫裡其他用到此屬性的部份。看看這兩種方法:

class Bill:
	public DateDue as DateTime
	public Amount as double
	public Debtor as Customer
	...
		
class Bill:
	[property(DateDue)]
	_dateDue as DateTime
	[property(Amount)]
	_amount as double
	[property(Debtor)]
	_debtor as Customer
	...

前者的確可用,但想想,如果你突然想檢查這些數值(參考上一節),你就得改變為第二種方式來作。如果 Bill 類別是你系統組件公開介面的一部分,那麼你的程式將可能出錯,並需要重新編譯。如果組件是在使用者的電腦上,那麼情況會更險惡,而且也不易遞增升級你的系統。

多載與繼承 编辑

CLI 架構下,所有類別最終都繼承自 Object。所以可以說 Object 定義了所有物件的通用行為。最常被用到的行為,就是將自己轉為字串,這通常會被多載﹔他的名字就是 ToString 方法。當框架需要顯示物件的文字時,它就會呼叫這個方法 (這就是 print 知道如何顯示的原因)。預設是簡單地傳回類別名稱,這很安全,而且很有用(尤其是在互動環境下),但是我們可以多載 ToString 讓它傳回我們想要的任何事。

舉例來說,如果我們像這樣定義了 PersonToString

override def ToString():
	return _name

那麼物件將知道怎麼印出它自己:

p = Person("jane",25)
print p

繼承的基本策略就在於利用既有的類別,並且延伸它。舉例來說,'Employee 是個 Person (通常是)。它有些行為與 Person 一樣,但卻有獨特的 ID。

class Employee(Person):
	_employeeId as int
	
	def constructor(name as string, age as int, id as int):
		super(name,age)
		_employeeId = id
		
	override def ToString():
		return super.ToString() + " id = " + _employeeId
		
e = Employee("James",47,2333)
print e

輸出結果

James id = 2333

請注意關鍵字 super﹔它用來參照父類別。所以 Employee 建構子得以初始化父類別Person,也可以在方法裡使用父類別的某些方法。

事實上,如果建構子沒有引數 (這被稱為預設建構子)的話,並不一定要呼叫父類別的建構子。在沒有建構子的情況下,任何欄位將被初始化為 0 或 null,或是任何有意義的初始值。

class Base:
	public X as double
	
	def constructor():
		X = 1.0

class Inherits(Base):
	public Y as double
			
b = Inherits()
b.Y = 2.0
print b.X,b.Y

我們可以說,Inherits物件是一種特別的 Base 物件。

Java 程式設計師要注意一點,方法預設並非 virtual (虛擬),所以你必須明確指定要多載繼承下來的方法。這與 C# 一樣﹔了解 C# 的作法,會讓你比較容易清楚 Boo 的作法。

命名慣例 编辑

Boo 與大部分的程式語言一樣,並沒有堅決主張一個命名慣例。在一般用法上,會將欄位名稱以 _ 開頭,而方法的名稱以 camel case 命名。此外,你也可以參考其他 .NET 語言或 Python 的作法。


上一章:函式 目錄 下一章:介面