Menu
Coddy logo textTech

Patron Singleton

Fait partie de la section Programmation orientée objet du Journey Ruby de Coddy. Leçon 42 sur 57.

Un motif de conception est une solution éprouvée à un problème de conception courant. Le motif singleton garantit qu’une classe possède exactement une instance, accessible depuis n’importe où. La bibliothèque standard de Ruby l’intègre : require 'singleton' et include Singleton ajoutent une méthode 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)

Sortie :

dark
true

include Singleton rend également new privé, de sorte qu’aucune seconde instance ne puisse être créée. Chaque partie du programme qui demande instance obtient le même objet :

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

Sortie :

3
private method `new' called for class Counter

La même idée écrite à la main consiste en une méthode de classe qui crée l’instance lors de la première utilisation et en un new privé. Cela montre ce que le module fait pour vous :

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

Sortie :

3

Utilisez les singletons avec parcimonie. Un singleton est un état global : n’importe quel code peut le modifier, et les tests ne peuvent pas commencer avec une copie vierge. Ils conviennent aux éléments qui n’existent réellement qu’une seule fois par programme, comme un logger ou la configuration ; passer un objet en paramètre est souvent plus clair :

# plus facile à tester : le logger est passé en paramètre
def process(order, log)
  log.write("processing #{order.id}")
end
challenge icon

Défi

Facile

Complétez le singleton AppLogger avec include Singleton. Il conserve un tableau d’entrées et possède log(message, level), qui stocke [INFO] message ou [ERROR] message (le niveau en majuscules), ainsi que entries et error_count. Les fonctions auxiliaires handle_info(message) et handle_error(message) dans functions.rb journalisent toutes deux via AppLogger.instance ; elles atteignent donc le même objet.

Le code fourni lit les lignes info,message ou error,message, les transmet aux fonctions auxiliaires, affiche chaque entrée ainsi que errors: 1, et vérifie que AppLogger.new est refusé.

Votre code doit être placé dans app_logger.rb et functions.rb. main.rb contient le code d’entrée/sortie fourni et ne peut pas être modifié.

Essayez vous-même

require_relative 'app_logger'
require_relative 'functions'

# Code d'entrée/sortie fourni : le garder tel quel
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 iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation orientée objet

Entraînez-vous par vous-même : Compilateur Ruby en ligne