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 Color
classe 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