getter 和 setter 是方法,让你可以读取和写入对象属性的值。 Crystal 有宏,所以为属性定义 getter 和 setter 很容易。 宏是一种在编译期生成代码的方式,后面的宏概念里会讲到。
在 Ruby 中,这些方法用attr_reader、attr_writer和attr_accessor方法定义,表面上和 Crystal 的实现非常相似。
Crystal 则把它们定义成了getter、setter和property宏。
getter 是一个宏,它定义了一个返回实例变量值的方法。
这意味着,当你想访问实例变量时,不必再在它前面写@(在方法里,initialize除外),而是可以调用 getter 宏定义的方法。
getter 宏可以接受多个实例变量,用逗号分隔即可。
# This:
class Person
@name : String
@age : Int32
def name
@name
end
def age
@age
end
end
# Is the same as this:
class Person
@name : String
@age : Int32
getter name, :age
end
可以看到,使用 getter 减少了你要写的代码量,也让代码更好读。 另外,Ruby 和 Crystal 还有一个不同之处:Crystal 的 getter 宏既接受变量名,也接受符号作为参数。 符号会在后面的符号概念里讲到。
setter 是一个宏,它定义了一个设置实例变量值的方法。
这个宏并不常见,通常改用property宏。
创建出来的方法形如name_of_method=,正是这个=让属性可以被设置。
这种方法的定义有点特别,因为方法接收的实参写在=后面。
Crystal 中还有几个特殊方法也用这种语法,比如+方法。
和 getter 宏一样,定义了 setter 之后,当你想更新实例变量的值时,可以调用 setter 宏定义的方法。
# This:
class Person
@name : String
@age : Int32
def name=(name : String)
@name = name
end
def age=(age : Int32)
@age = age
end
end
# Is the same as this:
class Person
@name : String
@age : Int32
setter name, :age
end
property 是一个宏,它为实例变量同时定义 getter 和 setter。
class Person
@name : String
@age : Int32
property name, :age
end
在这个练习中,你将对一台称重机建模。
初始化时,称重机应参考其出厂设置,而出厂设置会因机器的销售地区而异。因此,机器在初始化时应能指定一个精度,以及它使用的是公制还是英制。
实现 WeighingMachine#initialize 方法,它接收两个实参:precision(一个 Int32)和 metric(一个 Bool)。metric 实参为 true 时表示机器应使用公制,否则应使用英制。这个初始化方法还应将实例变量 @weight 设为 0.0。
precision = 3
metric = true
vm = WeighingMachine.new(precision, metric)
为了满足不同的需求,我们允许每台称重机自定义精度(小数点后的位数)。
实现 WeighingMachine#precision getter 方法,返回称重机的精度。
precision = 3
metric = true
vm = WeighingMachine.new(precision, metric)
vm.precision
# => 3
实现 WeighingMachine#weight 属性,使重量既能被读取,_也_能被设置:
precision = 3
metric = true
wm = WeighingMachine.new(precision, metric)
wm.weight = 60.5
wm.weight
# => 60.5
wm.weigh
# => 60.5
实现 WeighingMachine#metric= 属性来设置单位。它应接受一个布尔值。
precision = 3
metric = true
wm = WeighingMachine.new(precision, metric)
vm.weight = 60.5
wm.metric = false
vm.weigh
# => 133.377