Ruby é uma linguagem orientada a objetos, porém usar objetos é diferente de saber programar orientado a objetos.
Objetivo: Entender os fundamentos para escrever código verdadeiramente orientado a objetos.
Uma classe é um "molde" ou "forma". É um modelo que define:
- Características (atributos/propriedades)
- Comportamentos (métodos/ações)
class User
# Características
attr_accessor :name, :email
# Comportamentos
def initialize(name, email)
@name = name
@email = email
end
def greet
"Hello, I'm #{@name}"
end
end
Classe = Receita de bolo Objeto = Bolo feito com a receita
Um objeto é a instância de uma classe.
Características dos objetos:
- Estado: valores dos atributos em um momento específico
- Comportamentos: métodos que pode executar
- Identidade: cada objeto é único na memória
# Criando objetos (instâncias)
user1 = User.new("João", "[email protected]")
user2 = User.new("Maria", "[email protected]")
# Cada objeto tem seu próprio estado
puts user1.name # "João"
puts user2.name # "Maria"
# Mas compartilham os mesmos comportamentos
puts user1.greet # "Hello, I'm João"
puts user2.greet # "Hello, I'm Maria"
Herança permite que uma classe "herde" características e comportamentos de outra classe.
- A classe pai é chamada de superclass, ela que fornece atributo e comportamento para outras classes.
- A classe filha de subclass, ela pode especializar ou sobrescrever métodos (polimorfismo).
Você pode usar herança para especializar comportamentos, por exemplo:
class Usuario
def initialize(nome, email)
@nome = nome
@email = email
end
def perfil
"#{@nome} (#{@email})"
end
def pode_editar?(recurso)
false # Usuário comum não pode editar
end
end
class Moderador < Usuario
def pode_editar?(recurso)
recurso.tipo == 'comentario' # Só pode editar comentários
end
def moderar_conteudo
"Moderando conteúdo..."
end
end
- ✅ Hierarquia Lógica
- ✅ Evita duplicação (DRY)
- ✅ Especialização - Refinamento de Comporamentos (2 tipos de usuários por exemplo)
Polimorfismo = "muitas formas". Regras diferentes para a mesma situação.
class Forma
def area
raise "Método deve ser implementado pela subclasse"
end
def perimetro
raise "Método deve ser implementado pela subclasse"
end
end
class Retangulo < Forma
def initialize(largura, altura)
@largura = largura
@altura = altura
end
def area
@largura * @altura
end
def perimetro
2 * (@largura + @altura)
end
end
class Circulo < Forma
def initialize(raio)
@raio = raio
end
def area
Math::PI * @raio * @raio
end
def perimetro
2 * Math::PI * @raio
end
end
formas = [
Retangulo.new(5, 3),
Circulo.new(2),
Retangulo.new(10, 4)
]
# Mesmo método, comportamentos diferentes
formas.each do |forma|
puts "Área: #{forma.area.round(2)}"
puts "Perímetro: #{forma.perimetro.round(2)}"
puts "---"
end
Duck Typing é um princípio que diz:
"Se anda como um pato, grasna como um pato, então é um pato".
Ou seja, o tipo de um objeto não importa, contanto que ele implemente os métodos necessários. Em Ruby, não checamos o tipo (classe), mas sim se o objeto responde ao método que queremos chamar.
# Não precisamos de herança para polimorfismo em Ruby!
class EmailNotificador
def enviar(mensagem)
puts "📧 Email: #{mensagem}"
end
end
class SlackNotificador
def enviar(mensagem)
puts "💬 Slack: #{mensagem}"
end
end
class SMSNotificador
def enviar(mensagem)
puts "📱 SMS: #{mensagem}"
end
end
class SistemaNotificacao
def initialize
@notificadores = []
end
def adicionar_notificador(notificador)
@notificadores << notificador
end
def notificar_todos(mensagem)
@notificadores.each do |notificador|
notificador.enviar(mensagem) # Polimorfismo!
end
end
end
# Uso polimórfico
sistema = SistemaNotificacao.new
sistema.adicionar_notificador(EmailNotificador.new)
sistema.adicionar_notificador(SlackNotificador.new)
sistema.adicionar_notificador(SMSNotificador.new)
sistema.notificar_todos("Sistema atualizado!")
Tudo em Ruby é um objeto!
# Números são objetos
42.class # Integer
42.methods.count # 162 métodos!
# Strings são objetos
"hello".class # String
"hello".upcase # "HELLO"
# Classes também são objetos
User.class # Class
User.methods.include?(:new) # true
# Até nil é objeto
nil.class # NilClass
Object é a superclasse de todas as classes em Ruby:
class MinhaClasse
end
MinhaClasse.superclass # Object
Object.superclass # BasicObject
Classes definem o comportamento dos objetos:
class Produto
# Método de classe (chamado na classe)
def self.categoria_padrao
"Geral"
end
# Método de instância (chamado no objeto)
def initialize(nome, preco)
@nome = nome
@preco = preco
end
def descricao
"#{@nome}: R$ #{@preco}"
end
end
# Método de classe
Produto.categoria_padrao # "Geral"
# Método de instância
produto = Produto.new("Notebook", 2500)
produto.descricao # "Notebook: R$ 2500"
Classes são fábricas de objetos
Encapsulamento = controlar quem pode acessar o quê.
Em Ruby temos 3 níveis:
- Public -> Métodos podem ser chamados por qualquer código. São herdados.
- Private -> Métodos privados só podem ser chamados pelo próprio objeto. Não podem ser herdados.
- Protected -> Métodos protegidos podem ser chamados por objetos da mesma classe. São herdados.
Modules são "caixas de ferramentas" que podem ser:
- Namespaces: organizar código
- Mixins: compartilhar funcionalidades
# Como Namespace
module Financeiro
class Conta
def saldo
@saldo ||= 0
end
end
end
conta = Financeiro::Conta.new
# Como Mixin
module Auditavel
def log_acao(acao)
puts "[#{Time.now}] #{acao}"
end
end
class Usuario
include Auditavel
def criar
log_acao("Usuário criado")
end
end
Modules = Código reutilizável sem herança
Características das Classes:
- ✅ Podem ser instanciadas
- ✅ Herança (uma superclass apenas)
- ✅ Encapsulam estado e comportamento
- ❌ Não podem ser "misturadas" em outras classes
Modules são contêineres de código que NÃO podem ser instanciados:
module Matematica
PI = 3.14159
def self.area_circulo(raio)
PI * raio * raio
end
end
# Não funciona! Modules não podem ser instanciados
# Matematica.new # NoMethodError
# Mas podemos usar seus métodos e constantes
puts Matematica::PI # 3.14159
puts Matematica.area_circulo(5) # 78.53975
# Sem namespace - pode gerar conflitos
class Conta
def saldo
"Conta bancária: R$ 1000"
end
end
class Conta # Ops! Redefinindo a classe anterior
def saldo
"Conta de email: 5 mensagens"
end
end
module WebServiceParceirosConfig
def self.by_token(token)
WEBSERVICE_PARCEIROS.fetch(token, nil)
end
# represents webservice parceiro
class Parceiro
attr_reader :nome
def initialize(nome)
raise StandardError, 'webservice parceiros invalid configuration' unless nome
@nome = nome.upcase
end
def chatbot?
nome === 'CHATBOT'
end
def vivo?
nome === 'VIVO'
end
def igreen?
nome === 'IGREEN'
end
def inclusao_api_name
nome.downcase
end
end
end
Características dos Modules:
- ❌ NÃO podem ser instanciados
- ✅ Podem ser usados como namespace
- ✅ Podem ser incluídos em classes (Mixin)
- ✅ Podem ter métodos de classe
- ✅ Podem ter constantes
Mixin: Compartilhando Funcionalidades Mixin = Module incluído em uma classe para compartilhar comportamentos:
module Notificavel
def notificar(mensagem, canal = :email)
case canal
when :email
enviar_email(mensagem)
when :sms
enviar_sms(mensagem)
when :push
enviar_push(mensagem)
end
log_notificacao(mensagem, canal)
end
private
def log_notificacao(mensagem, canal)
puts "[#{Time.now}] Notificação via #{canal}: #{mensagem}"
end
end
module Servicos
module Pagamento
class PagSeguro
include Notificavel # Usando o mixin
def processar_pagamento(valor)
# lógica do pagamento
notificar("Pagamento de R$ #{valor} processado", :email)
end
end
class Stripe
include Notificavel # Mesmo mixin, implementação diferente
def processar_pagamento(valor)
# lógica diferente do pagamento
notificar("Payment of $#{valor} processed", :push)
end
end
end
module Email
class Gmail
include Notificavel
def enviar_email(destinatario, assunto)
notificar("Email enviado para #{destinatario}", :sms)
end
end
end
end
# Uso organizado e claro
pagseguro = Servicos::Pagamento::PagSeguro.new
stripe = Servicos::Pagamento::Stripe.new
gmail = Servicos::Email::Gmail.new
pagseguro.processar_pagamento(100)
stripe.processar_pagamento(200)
gmail.enviar_email("[email protected]", "Bem-vindo")
Quando Usar Cada Um? Use Class quando:
- ✅ Precisa criar múltiplas instâncias
- ✅ Cada instância tem estado próprio
- ✅ Representa uma "coisa" do mundo real
- ✅ Exemplo: User, Product, Order
Use Module como Namespace quando:
- ✅ Quer organizar classes relacionadas
- ✅ Evitar conflitos de nomes
- ✅ Criar hierarquias lógicas
- ✅ Exemplo: Admin::User, Api::V1::Controller
Use Module como Mixin quando:
- ✅ Quer compartilhar comportamentos
- ✅ Precisa de "herança múltipla"
- ✅ Funcionalidade cross-cutting (auditoria, log, cache)
- ✅ Exemplo: Timestampable, Cacheable, Searchable
No Rails, Concerns são modules organizados especificamente como Mixins nos models