Structs em Ruby
O que são Structs?
Você provavelmente já deve ter visto Structs em algumas linguagens e pode estar se perguntando o que são, de onde vêm e para que servem. Structs têm origem em C e são estruturas de dados usadas para agrupar atributos. O Ruby, inspirado nessa ideia, criou sua própria implementação de Struct, que, na minha opinião, é melhor.
No Ruby, um Struct é uma classe que cria novas subclasses nas quais podemos inserir atributos e até métodos. Portanto, o resultado é um objeto. Eu gosto de descrever Structs em Ruby como classes simplificadas.
Como criar um Struct?
É muito simples criar um Struct em Ruby. Vamos criar a estrutura inicial agora:
Struct.new("Fruits", :name, :price)
=> Struct::Fruits
Note que o irb retornou a classe Fruit
que foi criada.
Uma forma simplificada de criar Struct é atribuindo ela diretamente a uma constante que será o nome da classe:
Fruits = Struct.new(:name, :price)
Dessa forma a classe Fruit
é criada sem o namespace Struct::
, o que, na minha opinião, é mais elegante.
Agora podemos instanciar a classe para atribuir valores a name
e price
:
banana = Fruits.new("Banana", "5.00")
Vamos verificar o que a variável “banana” retorna:
=> #<struct Fruits name="Banana", price="5.00">
Trata-se de um objeto, então podemos acessar os atributos:
banana.price
=> "5.00"
Também podemos atribuir novos valores:
banana.price = 8.00
banana.price
=> 8.0
Criando métodos no Struct
Vamos tornar as coisas mais interessantes criando Structs com métodos:
Fruit = Struct.new(:name, :price) do
def announce
"Buy #{name} for just $#{price}!"
end
end
banana = Fruit.new("banana", "5.00")
banana.announce
=> "Buy banana for just $5.00!"
Passando um struct em vez de uma hash
É comum precisarmos passar muitos dados como argumentos ao instanciar uma classe. No entanto, não é uma boa prática passar mais do que 3 argumentos. Veja o exemplo:
BuyFruit.new("Banana", "8.0", "Marcos", :credit_card).call
Isso pode se tornar confuso e difícil de entender. Normalmente, a primeira solução que vem à mente é passar uma hash para resolver:
BuyFruit.new(fruit_name: "Banana", fruit_price: "8.0", customer: "Marcos", payment_method: :credit_card).call
Podemos melhorar:
detail = {
fruit_name: "Banana",
fruit_price: "8.0",
customer: "Marcos",
payment_method: :credit_card
}
BuyFruit.new(detail).call
Ficou bem aceitável, mas eu gostaria de propor algo ainda melhor. Vamos encapsular esses dados em um Struct em vez de uma Hash:
Details = Struct.new(:fruit_name, :fruit_price, :customer, :payment_method)
detail = Details.new("Banana", "8.0", "Marcos", :credit_card)
BuyFruit.new(detail).call
Indo mais além
Podemos ainda separar os dados para tornar tudo mais semântico:
class BuyFruit
def initialize(fruit, customer)
@fruit = fruit
@customer = customer
end
def call
StockRemove.new(@fruit).call
PaymentGateway.new(@fruit, @customer).call
end
end
Fruits = Struct.new(:name, :price, keyword_init: true)
Customers = Struct.new(:name, :payment_method, keyword_init: true)
fruit = Fruits.new(name: "Banana", price: "8.0")
customer = Customers.new(name: "Marcos", payment_method: :credit_card)
BuyFruit.new(fruit, customer).call
Observe que escrevemos mais código do que simplesmente passar os parâmetros, mas, além de tornar o código mais semântico, você ganha a vantagem de não precisar passar dados avulsos por várias camadas da aplicação. O que você está passando são apenas os dois objetos. A classe BuyFruit não precisa saber o que está contido nesses objetos; ela apenas os repassa para classes mais concretas.
Conclusão
Em resumo, Structs em Ruby oferecem uma maneira poderosa e elegante de criar estruturas de dados e objetos com atributos, métodos e semântica clara. Ao encapsular dados em Structs e utilizá-los de forma eficiente, é possível simplificar o código, torná-lo mais legível e facilitar a passagem de informações entre as camadas de uma aplicação, contribuindo para um desenvolvimento mais organizado e eficaz.