Menu
Coddy logo textTech

Padrão Singleton

Parte da seção Programação orientada a objetos do Journey de Ruby da Coddy. Lição 42 de 57.

Um padrão de projeto é uma solução comprovada para um problema comum de projeto. O padrão singleton garante que uma classe tenha exatamente uma instância, acessível de qualquer lugar. A biblioteca padrão do Ruby já o inclui: require 'singleton' e include Singleton adicionam um método de classe instance:

require 'singleton'

class Settings
  include Singleton
  attr_accessor :theme

  def initialize
    @theme = "light"
  end
end

Settings.instance.theme = "dark"
puts Settings.instance.theme
puts Settings.instance.equal?(Settings.instance)

Saída:

dark
true

include Singleton também torna new privado, portanto nenhuma segunda instância pode ser criada. Toda parte do programa que solicita instance obtém o mesmo objeto:

require 'singleton'

class Counter
  include Singleton
  attr_reader :hits

  def initialize
    @hits = 0
  end

  def hit
    @hits += 1
  end
end

def page_a
  Counter.instance.hit
end

def page_b
  2.times { Counter.instance.hit }
end

page_a
page_b
puts Counter.instance.hits
begin
  Counter.new
rescue NoMethodError => e
  puts e.message
end

Saída:

3
private method `new' called for class Counter

A mesma ideia escrita manualmente é um método de classe que cria a instância no primeiro uso e um new privado. Isso mostra o que o módulo faz por você:

class Config
  def self.instance
    @instance ||= new
  end
  private_class_method :new

  attr_accessor :level

  def initialize
    @level = 1
  end
end

Config.instance.level = 3
puts Config.instance.level

Saída:

3

Use singletons com moderação. Um singleton é um estado global: qualquer código pode alterá-lo, e os testes não podem começar com uma cópia limpa. Eles são adequados para coisas que realmente existem uma única vez por programa, como um logger ou a configuração; passar um objeto como parâmetro costuma ser mais claro:

# mais fácil de testar: o logger é passado
def process(order, log)
  log.write("processing #{order.id}")
end
challenge icon

Desafio

Fácil

Complete o singleton AppLogger com include Singleton. Ele mantém um array de entradas e possui log(message, level), que armazena [INFO] message ou [ERROR] message (o nível em letras maiúsculas), entries e error_count. Os auxiliares handle_info(message) e handle_error(message) em functions.rb registram ambos por meio de AppLogger.instance, portanto chegam ao mesmo objeto.

O código fornecido lê linhas info,message ou error,message, envia-as pelos auxiliares, imprime cada entrada e errors: 1 e verifica se AppLogger.new é recusado.

Seu código deve estar em app_logger.rb e functions.rb. main.rb contém o código fornecido de entrada/saída e não pode ser editado.

Experimente você mesmo

require_relative 'app_logger'
require_relative 'functions'

# Código de entrada/saída fornecido: mantenha-o como está
input = []
while (line = gets)
  input << line.chomp
end
input.each do |line|
  level, message = line.split(",", 2)
  level == "error" ? handle_error(message) : handle_info(message)
end
AppLogger.instance.entries.each { |e| puts e }
puts "errors: #{AppLogger.instance.error_count}"
begin
  AppLogger.new
  puts "new is allowed"
rescue NoMethodError
  puts "new is private"
end
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação orientada a objetos

Pratique por conta própria: Compilador de Ruby online