Objetos de valor – um guia completo para código Ruby que é testável, legível e simples

Introdução

Independentemente do tipo de arquitetura que você mais gosta no Rails, você achará o padrão de design de objetos de valor útil e, o que é tão importante, fácil de manter, implementar e testar. O padrão em si não apresenta nenhum nível desnecessário de abstração e visa tornar seu código mais isolado, mais fácil de entender e menos complicado.

Uma rápida olhada no nome do padrão

Vamos apenas analisar rapidamente seu nome antes de prosseguirmos:

  • valor – em sua aplicação existem muitas classes, algumas delas são complexas, o que significa que têm muitas linhas de código ou executam muitas ações, e algumas delas são simples, o que significa o contrário. Este padrão de design se concentra em fornecer valores , por isso é tão simples – não se preocupa em se conectar ao banco de dados ou APIs externas.
  • objeto – você sabe o que é um objeto na programação com objetos e, da mesma forma, objeto de valor é um objeto que fornece alguns atributos e aceita alguns parâmetros de inicialização.

A definição

Não há necessidade de reinventar a roda, então usarei a definição criada por Martin Fowler:

Um pequeno objeto simples, como dinheiro ou um intervalo de datas, cuja igualdade não é baseada na identidade.

Não seria lindo se uma parte de nosso aplicativo fosse composta de objetos pequenos e simples? Parece um paraíso e podemos facilmente pegar um pedaço deste paraíso e colocá-lo em nosso aplicativo. Vamos ver como.

Mãos no teclado

Eu gostaria de discutir as vantagens de usar objetos de valor e regras para escrever uma boa implementação, mas antes de fazer isso, vamos dar uma olhada rápida em alguns exemplos de objetos de valor para dar a você uma melhor compreensão de todo o conceito.

Cores – exemplo de comparação de igualdade

Se você estiver usando cores dentro de seu aplicativo, provavelmente acabará com a seguinte representação de uma cor:

class Color
CSS_REPRESENTATION
= {
'black' => '#000000',
'white' => '#ffffff'
}.freeze

def initialize(name)
@name = name
end

def css_code
CSS_REPRESENTATION
[@name]
end

attr_reader
:name
end

A implementação é autoexplicável, então não vamos nos concentrar em percorrer as linhas. Agora, considere o seguinte caso: dois usuários escolheram a mesma cor e você deseja comparar as cores e quando elas estiverem combinando, execute alguma ação:

user_a_color = Color.new('black')
user_b_color
= Color.new('black')

if user_a_color == user_b_color
# perform some action
end

Com a implementação atual, a ação nunca seria realizada porque agora os objetos são comparados usando sua identidade e é diferente para cada novo objeto:

user_a_color.object_id # => 70324226484560
user_b_color
.object_id # => 70324226449560

Lembra das palavras do Fowler de Martin? Um objeto de valor é comparado não pela identidade, mas com seus atributos. Levando isso em consideração, podemos dizer que nossa Colorclasse não é um objeto de valor verdadeiro. Vamos mudar isso:

class Color
CSS_REPRESENTATION
= {
'black' => '#000000',
'white' => '#ffffff'
}.freeze

def initialize(name)
@name = name
end

def css_code
CSS_REPRESENTATION
[@name]
end

def ==(other)
name
== other.name
end

attr_reader
:name
end

Agora, a ação de comparação faz sentido, pois comparamos não os ids dos objetos, mas os nomes das cores, de modo que os mesmos nomes das cores serão sempre iguais:

Color.new('black') == Color.new('black') # => true
Color.new('black') == Color.new('white') # => false

Com o exemplo acima, acabamos de aprender sobre o primeiro fundamento do objeto de valor – sua igualdade não é baseada na identidade.

Preço – exemplo de digitação de pato

Outro exemplo muito comum, mas significativo, de um objeto de valor é um objeto de preço. Vamos supor que você tenha um aplicativo de loja e um objeto separado por um preço:

class Price
def initialize(value:, currency:)
@value = value
@currency = currency
end

attr_reader
:value, :currency
end

e você deseja exibir o preço ao usuário final:

juice_price = Price.new(value: 2, currency: 'USD')
puts
"Price of juice is: #{juice_price.value} #{juice_price.currency}"

o objetivo foi alcançado, mas não parece bom. Outro recurso frequentemente visto em objetos de valor é a digitação de pato e este exemplo é um caso perfeito onde podemos tirar proveito disso. Em palavras simples, a digitação de pato significa que o objeto se comporta como um objeto diferente se implementar um determinado método – no exemplo acima, nosso objeto de preço deve se comportar como uma string:

class Price
def initialize(value:, currency:)
@value = value
@currency = currency
end

def to_s
"#{value} #{currency}"
end

attr_reader
:value, :currency
end

agora podemos atualizar nosso snippet:

juice_price = Price.new(value: 2, currency: 'USD')
puts
"Price of juice is: #{juice_price}"

Acabamos de descobrir outro fundamento dos objetos de valor e, como você pode ver, ainda retornamos apenas valores e mantemos o objeto muito simples e testável.

Continue lendo em https://pdabrowski.com/articles/rails-design-patterns-value-object