Os módulos em Crystal servem dois propósitos:
O primeiro é criar um espaço de nomes para evitar colisões de nomes. No entanto, também agrupam o código, o que facilita perceber para que serve.
O segundo é definir mixins para partilhar código entre tipos.
Os módulos são semelhantes às classes, mas a principal diferença é que não podem ser instanciados e, por isso, não têm variáveis de instância.
Para declarar um módulo, usas a palavra-chave module seguida do nome do módulo.
module Foo
end
Um espaço de nomes é uma forma de agrupar código.
Isso evita colisões de nomes e facilita perceber para que serve o código.
Quando queres aceder a uma constante ou a uma classe que foi colocada dentro de um espaço de nomes, usas o operador ::.
module Foo
class Bar
end
end
Foo::Bar.new
Isto pode ser útil quando, por exemplo, várias classes precisam da mesma funcionalidade "base" ou quando é preciso partilhar código entre classes não relacionadas. Ou quando queres partilhar código entre classes que não estão relacionadas.
Há duas formas diferentes de usar um módulo como mixin: a primeira é usar a palavra-chave include e a segunda é usar a palavra-chave extend.
Ambos os métodos tornam as constantes disponíveis para o tipo que inclui ou estende o módulo.
O include disponibiliza todos os métodos do módulo como métodos de instância no tipo que inclui o módulo.
A palavra-chave include deve ficar no início do tipo, seguida do nome do módulo.
module Foo
def foo
"foo"
end
end
class Bar
include Foo
end
Bar.new.foo # => "foo"
O extend funciona de forma semelhante ao include, mas em vez de disponibilizar os métodos como métodos de instância, disponibiliza-os como métodos de classe.
A palavra-chave extend deve ser escrita no início do tipo, seguida do nome do módulo.
module Foo
def foo
"foo"
end
end
class Bar
extend Foo
end
Bar.foo # => "foo"
Um padrão típico em Crystal usa o padrão extend self num módulo.
Isto torna todos os métodos do módulo disponíveis como métodos de classe no próprio módulo.
Isto significa que não precisas de atribuir cada método ao módulo com a sintaxe def self.method_name.
module Foo
extend self
def foo
"foo"
end
end
Foo.foo # => "foo"