Crystal 中的模块有两个用途:
第一个用途是创建命名空间,以避免名称冲突。 不过,它也会把代码组织起来,让人更容易看懂代码的用途。
第二个用途是定义混入,把代码共享给各个类型。
模块和类很相似,但主要区别在于:模块不能被实例化,因此也没有实例变量。
要声明一个模块,使用module关键字,后面跟上模块的名字。
module Foo
end
命名空间是一种组织代码的方式。
这样可以避免名称冲突,也更容易看清代码的用途。
如果想访问放在某个命名空间里的常量或类,就要使用::运算符。
module Foo
class Bar
end
end
Foo::Bar.new
例如,当多个类需要相同的“基础”功能,或者需要在互不相关的类之间共享代码时,这就会很有用。 或者,当你想在不相关的类之间共享代码时,也可以这么做。
把模块用作混入有两种方式:第一种是使用include关键字,第二种是使用extend关键字。
两种方式都会让模块中的常量对包含或扩展该模块的类型可用。
包含会让模块中的所有方法都变成包含该模块的类型的实例方法。
include关键字应写在类型的顶部,后面跟上模块的名字。
module Foo
def foo
"foo"
end
end
class Bar
include Foo
end
Bar.new.foo # => "foo"
扩展的用法和包含类似,但它不会把方法变成实例方法,而是把它们变成类方法。
extend关键字应写在类型的顶部,后面跟上模块的名字。
module Foo
def foo
"foo"
end
end
class Bar
extend Foo
end
Bar.foo # => "foo"
Crystal 中一种常见的模式,是在模块里使用 extend self 模式。
这样,模块中的所有方法都会变成模块自身的类方法。
这意味着你不需要用def self.method_name语法把每个方法都挂到模块上。
module Foo
extend self
def foo
"foo"
end
end
Foo.foo # => "foo"
Bellebrook 篮球联盟是一个由多支球队组成的联盟,每支球队都有一份球员名单。新赛季即将开始,他们需要创建一套新的票务系统来售卖比赛门票。上个赛季,他们在球馆入口处售票,但人气暴涨,于是他们决定本赛季改为在线售票,以应对增长的需求。
联盟联系了你,请你创建一个处理门票销售的系统。
首先,你需要创建一套票务系统来处理门票销售。票务系统需要说明一场比赛有多少张票可售。
每场比赛都应该创建一个名为 TicketSystem 的类的实例。初始化后的状态应包含以下信息:
通过实参构造初始化状态,这些实参分别表示可售门票的数量和比赛所在的球馆。初始化状态应包含实例变量 @tickets_available 和 @stadium。它们分别保存传入的第一个和第二个实参。
TicketSystem.new(100, "Bellebrook")
# => #<TicketSystem:0x10f0b8 @tickets_available=100, @stadium="Bellebrook">
首先,你需要创建一个方法,返回某场比赛的可售门票数量。
在已经定义好的类上方定义一个名为 TicketingReservation 的模块。这个模块应该被 TicketSystem 类包含。
在模块内部,定义一个名为 tickets_available 的方法。这个方法应该返回可售门票的数量。
ticket_system = TicketSystem.new(100)
ticket_system.tickets_available
#=> 100
你需要创建一个方法,检查某场比赛是否还有门票可售。每场比赛需要预留 100 张票。这是为了确保在球馆入口处买票的人始终能买到票。
在 TicketingReservation 模块内部,定义一个名为 order_ticket? 的方法。如果有至少 100 张票可售,该方法应返回 true,否则返回 false。如果有至少 100 张票可售,该方法还应把可售门票的数量减少 1。
ticket_system = TicketSystem.new(100)
ticket_system.order_ticket?
#=> true
票务系统的提示信息必须让用户容易理解。而且这些信息应该显得个性化,所以要包含用户的名字。
如果购票成功,用户应该收到一条消息,告诉他们购票成功、他们的票号,以及比赛在哪个球馆举行。票号应该是下单前可售门票的数量。
如果购票成功,消息应该像这样:{name}, your purchase was successful, your ticket number is #{ticket_number}, and the game is played at the {stadium_name} stadium.。
如果购票失败,消息应该像这样:{name}, your purchase was unsuccessful, there are not enough tickets available.。
在 TicketingReservation, 模块内部,定义一个名为 order_message 的方法,该方法接收一个实参 name,其中包含购票者的名字。该方法应返回一个包含这条信息的字符串。
ticket_system = TicketSystem.new(100, "Bellebrook")
ticket_system.order_message("John")
#=> "John, your purchase was successful, your ticket number is #100, and the game is played at the Bellebrook stadium."