Menu
Coddy logo textTech

Patrón Singleton

Parte de la sección Programación orientada a objetos del Journey de Ruby de Coddy. Lección 42 de 57.

Un patrón de diseño es una solución comprobada para un problema de diseño común. El patrón singleton garantiza que una clase tenga exactamente una instancia, accesible desde cualquier lugar. La biblioteca estándar de Ruby lo incluye: require 'singleton' y include Singleton añaden un método de clase 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)

Salida:

dark
true

include Singleton también hace que new sea privado, por lo que no se puede crear una segunda instancia. Cada parte del programa que solicita instance obtiene el mismo 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

Salida:

3
private method `new' called for class Counter

La misma idea escrita manualmente es un método de clase que crea la instancia en el primer uso y un new privado. Esto muestra lo que el módulo hace por ti:

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

Salida:

3

Usa los singleton con moderación. Un singleton es un estado global: cualquier código puede cambiarlo, y las pruebas no pueden comenzar a partir de una copia limpia. Son adecuados para cosas que realmente existen una sola vez por programa, como un registrador o la configuración; a menudo es más claro pasar un objeto como parámetro:

# más fácil de probar: el logger se pasa
def process(order, log)
  log.write("processing #{order.id}")
end
challenge icon

Desafío

Fácil

Completa el singleton AppLogger con include Singleton. Mantiene un array de entradas y tiene log(message, level), que almacena [INFO] message o [ERROR] message (el nivel en mayúsculas), entries y error_count. Los helpers handle_info(message) y handle_error(message) en functions.rb registran ambos mediante AppLogger.instance, por lo que llegan al mismo objeto.

El código proporcionado lee líneas info,message o error,message, las envía mediante los helpers, imprime cada entrada y errors: 1, y comprueba que AppLogger.new sea rechazado.

Tu código va en app_logger.rb y functions.rb. main.rb contiene el código de entrada/salida proporcionado y no se puede editar.

Pruébalo tú mismo

require_relative 'app_logger'
require_relative 'functions'

# Código de entrada/salida suministrado: déjalo tal 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 iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Compilador de Ruby online