Removendo hash-driven development com Struct e OpenStruct
Criado em: 06/07/2014
Temos que criar uma classe que exiba as informações de um Pokémon baseado no seu id nacional consumindo estes valores de uma API. Para isso simplesmente acessamos a API e parseamos a resposta do JSON a retornando por completo.
class FetchPokemon
def initialize(national_id)
@national_id = national_id
build_info
end
def call
info
end
private
attr_reader :national_id, :info
def endpoint
URI("http://pokeapi.co/api/v1/pokemon/#{national_id}/")
end
def build_info
response = Net::HTTP.get(endpoint)
@info = JSON.parse(response)
end
endEste código atende a nossa necessidade mais podemos melhorar o seu retorno, pois hoje precisamos apenas do nome, altura e peso. Então vamos alterar o nosso método call para retornar apenas estes atributos do info.
class FetchPokemon
# ...
def call
{ name: info['name'], height: info['height'], weight: info['weight'] }
end
# ...
endAgora temos apenas as informações que necessitamos. No entanto seria melhor se tivessemos um objeto Pokemon e não um hash, com um objeto sabemos exatamente com que estamos lidando, qual a entidade. E assim evitamos o hash-driven development e utilizamos uma abordagem orientada a objetos.
Para isso criamos uma inner class Pokemon que define exatamente os atributos que precisamos e os expõe através de um attr_accessor.
class FetchPokemon
# ...
def call
Pokemon.new(info['name'], info['height'], info['weight'])
end
private
# ...
class Pokemon
attr_accessor :name, :height, :weight
def initialize(name, height, weight)
@name, @height, @weight = name, height, weight
end
end
endNão se esqueça que uma inner class possui o comportamento diferente de quando estamos utilizando módulos como namespace. No nosso caso não é um problema dado que estamos utilizando a classe Pokemon apenas como um container, não nos importamos com nada declarado na classe FetchPokemon.
Agora nossa classe FetchPokemon retorna um FetchPokemon::Pokemon no método call, bem legal. No entanto estamos repetindo 3 vezes cada atributo na declaração da classe.
Para remover esta repetição podemos criar um Struct que faz exatamente o que precisamos: cria accessors para cada um dos atributos passados no initialize.
class FetchPokemon
# ...
def call
Pokemon.new(info['name'], info['height'], info['weight'])
end
private
# ...
Pokemon = Struct.new(:name, :height, :weight)
endContinuamos com o FetchPokemon::Pokemon como classe no entanto removemos a repetição ao se declarar a classe Pokemon.
OpenStruct
Para casos em que desejamos transformar um hash completo em um objeto temos o OpenStruct. Voltando lá no inicio quando tínhamos um hash reduzido com apenas as informações que precisamos podemos criar uma classe Pokemon a partir dele utilizando do OpenStruct.
Para isso criamos uma classe Pokemon que herda de OpenStruct.
class FetchPokemon
# ...
def call
Pokemon.new(name: info['name'], height: info['height'], weight: info['weight'])
end
private
# ...
class Pokemon < OpenStruct
end
endSeja utilizando o Struct ou o OpenStruct teremos um objeto FetchPokemon::Pokemon a diferença é que um será herdado de Struct e outro de OpenStruct.
fetch_pokemon = FetchPokemon.new(6)
pokemon = fetch_pokemon.call # => #<FetchPokemon::Pokemon name="Charizard", height="17", weight="905">
pokemon.name # => "Charizard"Com este refactoring saimos de um hash para uma entidade Pokémon, que deixa claro o que é aquele objeto. Afinal um hash que tem informações de um pokémon é um pokémon, então deixe isso explícito.
Comentários
Comentários powered by Disqus