O que são Classes e Objetos (na prática)
Em Ruby, classe é um molde que descreve estado (dados) e comportamento (ações). Um objeto é uma instância dessa classe: um “exemplar” com valores próprios.
- Estado: atributos como
nome,preco,quantidade. - Comportamento: métodos como
aplicar_desconto,subtotal,adicionar_item. - Encapsulamento: proteger regras internas e expor apenas o necessário (via métodos públicos), evitando que qualquer parte do sistema altere o objeto de forma inválida.
Criando uma classe: estrutura mínima
Uma classe começa com class e termina com end. Dentro dela, você define métodos e atributos.
class Produto
end
produto = Produto.newAté aqui, Produto não tem estado nem comportamento. Vamos evoluir para algo útil.
Inicialização com initialize: definindo o estado inicial
O método initialize é chamado automaticamente quando você faz Produto.new(...). Use-o para garantir que o objeto nasce em um estado válido.
class Produto
def initialize(nome, preco_em_centavos)
@nome = nome
@preco_em_centavos = preco_em_centavos
end
end
p1 = Produto.new("Camiseta", 7990)Variáveis com @ são variáveis de instância (estado do objeto). Cada objeto tem as suas.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Passo a passo: adicionando validações no initialize
Regras de negócio devem impedir estados inválidos. Por exemplo: preço não pode ser negativo e nome não pode ser vazio.
class Produto
def initialize(nome, preco_em_centavos)
raise ArgumentError, "nome inválido" if nome.nil? || nome.strip.empty?
raise ArgumentError, "preço inválido" if preco_em_centavos.nil? || preco_em_centavos < 0
@nome = nome
@preco_em_centavos = preco_em_centavos
end
endAssim, qualquer tentativa de criar um produto inválido falha cedo, no ponto certo.
Atributos: attr_reader, attr_writer e attr_accessor
Em Ruby, você pode expor atributos com métodos gerados automaticamente:
attr_reader: cria apenas leitura (getter).attr_writer: cria apenas escrita (setter).attr_accessor: cria leitura e escrita.
Exemplo com leitura pública e escrita controlada:
class Produto
attr_reader :nome, :preco_em_centavos
def initialize(nome, preco_em_centavos)
raise ArgumentError, "nome inválido" if nome.nil? || nome.strip.empty?
raise ArgumentError, "preço inválido" if preco_em_centavos.nil? || preco_em_centavos < 0
@nome = nome
@preco_em_centavos = preco_em_centavos
end
endPor que não usar attr_accessor para tudo? Porque permitir escrita livre pode quebrar regras. Se o preço precisa de validação, é melhor criar um método específico para alterar preço com segurança.
Setter com regra (em vez de attr_writer)
class Produto
attr_reader :nome, :preco_em_centavos
def initialize(nome, preco_em_centavos)
@nome = nome
definir_preco(preco_em_centavos)
end
def definir_preco(novo_preco_em_centavos)
raise ArgumentError, "preço inválido" if novo_preco_em_centavos.nil? || novo_preco_em_centavos < 0
@preco_em_centavos = novo_preco_em_centavos
end
endNote o nome: definir_preco comunica intenção e permite validação.
Métodos de instância: comportamento do objeto
Métodos de instância operam sobre o estado do próprio objeto (usando @variaveis).
class Produto
attr_reader :nome, :preco_em_centavos
def initialize(nome, preco_em_centavos)
@nome = nome
@preco_em_centavos = preco_em_centavos
end
def preco_em_reais
@preco_em_centavos / 100.0
end
end
p = Produto.new("Caneca", 2590)
puts p.preco_em_reaisUm bom sinal de design: métodos pequenos, com nomes descritivos e responsabilidade clara.
Modelagem prática: Produto e Carrinho com regras de negócio
Vamos modelar um cenário simples: um carrinho que recebe itens (produto + quantidade), calcula subtotal, aplica cupom e calcula total. A ideia é mostrar estado, comportamento e encapsulamento.
1) Criando Produto com desconto controlado
Regra: desconto percentual deve estar entre 0 e 30% (por exemplo, para evitar descontos “livres” em qualquer parte do sistema).
class Produto
attr_reader :nome, :preco_em_centavos
DESCONTO_MAXIMO_PERCENTUAL = 30
def initialize(nome, preco_em_centavos)
raise ArgumentError, "nome inválido" if nome.nil? || nome.strip.empty?
raise ArgumentError, "preço inválido" if preco_em_centavos.nil? || preco_em_centavos <= 0
@nome = nome
@preco_em_centavos = preco_em_centavos
end
def aplicar_desconto(percentual)
validar_percentual_de_desconto(percentual)
desconto = (@preco_em_centavos * percentual / 100.0).round
@preco_em_centavos -= desconto
end
private
def validar_percentual_de_desconto(percentual)
raise ArgumentError, "percentual inválido" if percentual.nil?
raise ArgumentError, "desconto deve ser entre 0 e #{DESCONTO_MAXIMO_PERCENTUAL}" if percentual < 0 || percentual > DESCONTO_MAXIMO_PERCENTUAL
end
endA validação ficou em um método private, porque é um detalhe interno: quem usa Produto só precisa saber que existe aplicar_desconto.
2) Criando ItemDeCarrinho para encapsular produto + quantidade
Em vez de guardar hashes soltos no carrinho, criamos um objeto com responsabilidade clara.
class ItemDeCarrinho
attr_reader :produto, :quantidade
def initialize(produto, quantidade)
raise ArgumentError, "produto inválido" if produto.nil?
raise ArgumentError, "quantidade inválida" if quantidade.nil? || quantidade <= 0
@produto = produto
@quantidade = quantidade
end
def incrementar(quantidade_extra)
raise ArgumentError, "quantidade extra inválida" if quantidade_extra.nil? || quantidade_extra <= 0
@quantidade += quantidade_extra
end
def subtotal_em_centavos
@produto.preco_em_centavos * @quantidade
end
endRepare como o carrinho não precisa saber calcular subtotal de item: isso é responsabilidade do item.
3) Criando Carrinho com regras e métodos pequenos
Regras de exemplo:
- Não aceitar quantidade <= 0.
- Se o produto já existe no carrinho, somar quantidades (em vez de duplicar item).
- Cupom percentual entre 0 e 20%.
class Carrinho
CUPOM_MAXIMO_PERCENTUAL = 20
def initialize
@itens = []
@cupom_percentual = 0
end
def adicionar_produto(produto, quantidade = 1)
raise ArgumentError, "quantidade inválida" if quantidade.nil? || quantidade <= 0
item = encontrar_item_por_produto(produto)
if item
item.incrementar(quantidade)
else
@itens << ItemDeCarrinho.new(produto, quantidade)
end
end
def aplicar_cupom(percentual)
validar_cupom(percentual)
@cupom_percentual = percentual
end
def subtotal_em_centavos
@itens.sum { |item| item.subtotal_em_centavos }
end
def desconto_em_centavos
(subtotal_em_centavos * @cupom_percentual / 100.0).round
end
def total_em_centavos
subtotal_em_centavos - desconto_em_centavos
end
def itens
@itens.dup
end
private
def encontrar_item_por_produto(produto)
@itens.find { |item| item.produto == produto }
end
def validar_cupom(percentual)
raise ArgumentError, "cupom inválido" if percentual.nil?
raise ArgumentError, "cupom deve ser entre 0 e #{CUPOM_MAXIMO_PERCENTUAL}" if percentual < 0 || percentual > CUPOM_MAXIMO_PERCENTUAL
end
endDetalhes importantes de encapsulamento:
itensretorna@itens.duppara evitar que código externo façacarrinho.itens << ...e burle regras.- Métodos auxiliares (
encontrar_item_por_produto,validar_cupom) sãoprivateporque não fazem parte da API pública do carrinho.
4) Usando as classes juntas (fluxo completo)
camiseta = Produto.new("Camiseta", 7990)
caneca = Produto.new("Caneca", 2590)
camiseta.aplicar_desconto(10)
carrinho = Carrinho.new
carrinho.adicionar_produto(camiseta, 2)
carrinho.adicionar_produto(caneca)
carrinho.aplicar_cupom(15)
puts carrinho.subtotal_em_centavos
puts carrinho.desconto_em_centavos
puts carrinho.total_em_centavosEncapsulamento com private e protected
private: esconder detalhes internos
private torna métodos acessíveis apenas dentro do próprio objeto. Isso ajuda a:
- Evitar que outras partes do sistema chamem validações diretamente.
- Reduzir a “superfície” pública da classe (API menor, mais fácil de manter).
- Forçar o uso de métodos públicos que garantem regras.
No exemplo, validar_cupom e validar_percentual_de_desconto são privados.
protected: colaboração entre objetos do mesmo tipo
protected é útil quando objetos da mesma classe (ou subclasses) precisam acessar um detalhe interno uns dos outros, mas você não quer expor isso publicamente.
Exemplo: comparar carrinhos por um “código interno” sem expor esse código fora da classe.
class Carrinho
def initialize
@itens = []
@codigo_interno = gerar_codigo
end
def mesmo_codigo?(outro)
codigo_interno == outro.codigo_interno
end
protected
def codigo_interno
@codigo_interno
end
private
def gerar_codigo
rand(1000..9999)
end
endAqui, outro.codigo_interno funciona dentro da classe, mas código externo não consegue chamar carrinho.codigo_interno.
Boas práticas de design: métodos pequenos e nomes descritivos
Ao modelar classes:
- Prefira métodos que expressem intenção:
aplicar_cupom,subtotal_em_centavos,incrementar. - Evite “métodos Deus” (muito longos). Extraia validações e cálculos para métodos privados.
- Proteja invariantes: se um objeto não pode ter estado inválido, valide no
initializee em métodos de alteração. - Exponha o mínimo: use
attr_readerpor padrão e crie setters com regra quando necessário.
Checklist prático para criar uma classe bem encapsulada
| Item | Pergunta | Exemplo |
|---|---|---|
| Estado | Quais dados o objeto precisa guardar? | @preco_em_centavos, @itens |
| Inicialização | Como garantir que nasce válido? | validar no initialize |
| API pública | Quais ações o mundo externo pode fazer? | adicionar_produto, aplicar_cupom |
| Regras internas | O que deve ficar escondido? | validar_cupom como private |
| Colaboração | Objetos do mesmo tipo precisam acessar algo interno? | protected para comparação |