objeto de mesma classe ou subclasse chame o método (igual ao friend, do C++).
Vejamos o exemplo
a seguir:
class Person
attr_accessor :name
def befriend(people)
people.each { |friend| friend.add_friend(self) }
end
protected
def add_friend(friend)
puts "#{name} diz: Olá meu novo amigo #{friend.name}!"
end
end
joao = Person.new; joao.name = 'João'
pedro = Person.new; pedro.name = 'Pedro'
joaquim = Person.new; joaquim.name = 'Joaquim'
joao.befriend([pedro])
# Pedro diz: Olá meu novo amigo João!
joaquim.add_friend(joao)
# NoMethodError: protected method àdd_friend' ...
Exercício para você leitor! Altere o protected para private e veja o que acon-
tece. Depois, public.
Módulos
Módulos em Ruby são basicamente agrupadores de métodos, constantes, clas-
ses e variáveis. O uso é quase o mesmo de classes, porém não podemos criar uma
59
2.6. Classes e módulos
Casa do Código
instância de um módulo e usamos a palavra chave module, ao invés de class. Ou-
tra diferença é que não existe hierarquia de módulos, ou seja, não faz sentido um
módulo herdar de outro.
Módulo são usados de duas maneiras, usados como namespaces, ou seja, uma
boa maneira de agrupar classes, constantes e métodos quando pertinente (por exem-
plo, agrupar todas as classes que fazem parte do meio de pagamento de um site) ou
como mixins, uma forma de adicionar um comportamento comum a qualquer classe.
Veremos mais exemplos sobre essa importante funcionalidade.
Módulos como namespaces
Quando um sistema se torna grande, é importante agrupar diversas classes que
fazem parte de um componente em um namespace. Esta funcionalidade é de extrema
importância, por exemplo, quando temos diversas classes com o mesmo nome, seja
por um código que escrevemos ou por alguma biblioteca que usamos. Se não criar-
mos namespaces, comportamentos estranhos podem acontecer.
Para construir um módulo, basta usar uma sintaxe muito parecida com a de clas-
ses:
module Payment
class Purchase
end
end
Purchase.new # NameError: uninitialized constant Purchase
Payment::Purchase.new # => #<Payment::Purchase:0x007fac81d0b0f8>
No exemplo anterior podemos notar duas importantes características de módu-
los:
Módulos devem também ser nomeados como constantes, ou seja, CamelCase
com a primeira letra em maiúscula;
Quando declaramos uma constante ou classe dentro de um módulo, é neces-
sário explicitar o escopo via ::.
O uso do :: pode ser omitido no caso de estarmos dentro do módulo. Vejamos
um exemplo:
60
Casa do Código
Capítulo 2. Conhecendo Ruby
module Payment
MIN_COST_FOR_FREE_SHIPPING = 10.00
class Purchase
attr_accessor :total_cost
def calculate_shipping!
if total_cost >= MIN_COST_FOR_FREE_SHIPPING
puts "Parabéns, frete grátis para você!"
else
puts "Sem frete grátis :-("
end
end
end
end
purchase = Payment::Purchase.new
purchase.total_cost = 15.00
purchase.calculate_shipping! # Parabéns,
# frete grátis para você!
puts MIN_COST_FOR_FREE_SHIPPING # uninitialized constant
puts Payment::MIN_COST_FOR_FREE_SHIPPING # 10.0
Em algumas situações é possível que duas classes de mesmo nome sejam acessí-
veis em um escopo. Para isso, é possível explicitar integral ou parcialmente o escopo
usando o ::. Caso o escopo seja o escopo raiz, é possível declarar da seguinte forma:
::File. Isso irá fazer com que a resolução de nomes do Ruby vá buscar a classe ou
módulo File no nível raiz, sem nenhum módulo.
Para entender melhor como organizar projetos e escopos, veja o Capítulo 8 do
livro Ruby Best Practices [3].
Módulos como mixins
Mixins é uma das funcionalidades mais importantes do Ruby. Além de classes,
é possível declarar métodos de instância dentro de um módulo. Assim, é possível
misturar esses métodos em qualquer classe ou objeto. Dessa forma, é possível fazer
diversas classes obterem um conjunto de comportamento comum com apenas uma
linha de código.
61
2.6. Classes e módulos
Casa do Código
Dois exemplos básicos de mixins em Ruby são os módulos Comparable e
Enumerable.
O módulo Comparable, ao ser incluído em uma classe, basta que a
classe implemente o método <=> (também conhecido como spaceship method) e
ela ganha diversos outros comportamentos de graça, como <, <=, ==, >, >= e
between?. Com o Enumerable, implementar o método each faz com que a classe
ganhe várias funcionalidades, como all?, any?, map, count, detect e outros.
Veja a documentação do Ruby sobre Enumerable para mais detalhes (http://www.
ruby-doc.org/core-1.9.3/Enumerable.html).
Contudo, vamos a um exemplo simples para entender como mixins funcionam:
module Shipping
CUBED_WEIGHT_FACTOR = 167
def dimensional_weight
width * depth * height * CUBED_WEIGHT_FACTOR
end
end
class ShippingPrice
include Shipping
attr_accessor :width, :depth, :height
end
shipping = ShippingPrice.new
shipping.width = 0.5;
shipping.depth = 0.8;
shipping.height = 0.3;
shipping.dimensional_weight # 20.04
Para criar mixins, basta declarar um módulo e construir métodos diretamente