Crystalのモジュールには、2つの目的があります。
1つ目の目的は、名前の衝突を避けるために名前空間を作ることです。 また、コードをまとめることで、そのコードが何のためのものかを理解しやすくもします。
2つ目の目的は、型の間でコードを共有するためのミックスインを定義することです。
モジュールはクラスに似ていますが、主な違いは、インスタンス化できないためインスタンス変数を持たないことです。
モジュールを宣言するには、moduleキーワードの後にモジュール名を書きます。
module Foo
end
名前空間は、コードをまとめるための仕組みです。
これにより名前の衝突を避けられ、そのコードが何のためのものかを理解しやすくなります。
名前空間の中に置かれた定数やクラスにアクセスしたいときは、::演算子を使います。
module Foo
class Bar
end
end
Foo::Bar.new
これは、たとえば複数のクラスが同じ「ベース」の機能を必要とするときや、無関係なクラス間でコードを共有したいときに役立ちます。 関係のないクラス同士でコードを共有したいときにも便利です。
モジュールをミックスインとして使う方法は2つあります。1つ目はincludeキーワードを使う方法で、2つ目はextendキーワードを使う方法です。
どちらの方法でも、モジュールをincludeまたはextendした型で定数が使えるようになります。
includeは、モジュール内のすべてのメソッドを、そのモジュールをincludeした型のインスタンスメソッドとして使えるようにします。
includeキーワードは型の先頭に書き、その後にモジュール名を続けます。
module Foo
def foo
"foo"
end
end
class Bar
include Foo
end
Bar.new.foo # => "foo"
extendはincludeと似たように動作しますが、メソッドをインスタンスメソッドとして使えるようにするのではなく、クラスメソッドとして使えるようにします。
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 Basketball Leagueは、それぞれが選手名簿を持つ複数のチームからなるリーグです。新しいシーズンがまもなく始まり、試合のチケットを販売するための新しいチケットシステムを作る必要があります。前シーズンはスタジアムの入口でチケットを販売していましたが、人気が爆発的に高まったため、増加する需要に対応するべく、今シーズンはオンラインでチケットを販売することにしました。
リーグから連絡があり、チケット販売を処理するシステムを作るよう依頼されました。
まず、チケット販売を処理するチケットシステムを作成する必要があります。チケットシステムは、ある試合で販売可能なチケットが何枚あるかを示す必要があります。
試合ごとに、TicketSystemというクラスのインスタンスを作成します。初期化された状態は、次の情報を保持している必要があります。
販売可能なチケットの枚数と、試合が行われるスタジアムを示す引数から、初期化された状態を構築します。初期化された状態は、インスタンス変数@tickets_availableと@stadiumで構成されている必要があります。これらには、それぞれ与えられた1番目と2番目の引数が入ります。
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,の中に、購入者の名前を持つ引数nameを取るorder_messageというメソッドを定義します。このメソッドは、そのメッセージを含む文字列を返す必要があります。
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."