BOO大全/类别
类别
[编辑]类别是面向对象语言的基础。不像其他的 .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 让它传回我们想要的任何事。
举例来说,如果我们像这样定义了 Person 的 ToString:
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 的作法。