Menu
Coddy logo textTech

Singleton-Muster

Teil des Abschnitts Objektorientierte Programmierung der Ruby-Journey von Coddy. Lektion 42 von 57.

Ein Entwurfsmuster ist eine bewährte Lösung für ein häufig auftretendes Entwurfsproblem. Das Singleton-Muster stellt sicher, dass eine Klasse genau eine Instanz besitzt, die von überall aus erreichbar ist. Rubys Standardbibliothek hat es integriert: require 'singleton' und include Singleton fügen eine Klassenmethode instance hinzu:

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)

Ausgabe:

dark
true

include Singleton macht außerdem new privat, sodass keine zweite Instanz erstellt werden kann. Jeder Teil des Programms, der nach instance fragt, erhält dasselbe Objekt:

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

Ausgabe:

3
private method `new' called for class Counter

Dieselbe Idee von Hand geschrieben ist eine Klassenmethode, die die Instanz bei der ersten Verwendung erstellt, sowie ein privates new. Dies zeigt, was das Modul für dich übernimmt:

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

Ausgabe:

3

Verwende Singleton-Objekte sparsam. Ein Singleton ist ein globaler Zustand: Jeder Code kann ihn ändern, und Tests können nicht mit einer sauberen Kopie starten. Sie eignen sich für Dinge, die wirklich nur einmal pro Programm existieren, etwa einen Logger oder die Konfiguration; ein Objekt als Parameter zu übergeben, ist oft klarer:

# einfacher zu testen: der Logger wird übergeben
def process(order, log)
  log.write("processing #{order.id}")
end
challenge icon

Aufgabe

Einfach

Vervollständige das Singleton AppLogger mit include Singleton. Es verwaltet ein Array von Einträgen und verfügt über log(message, level), das [INFO] message oder [ERROR] message (die Ebene in Großbuchstaben), entries und error_count speichert. Die Hilfsfunktionen handle_info(message) und handle_error(message) in functions.rb protokollieren beide über AppLogger.instance, sodass sie dasselbe Objekt erreichen.

Der bereitgestellte Code liest Zeilen im Format info,message oder error,message, leitet sie über die Hilfsfunktionen weiter, gibt jeden Eintrag und errors: 1 aus und überprüft, dass AppLogger.new abgelehnt wird.

Dein Code gehört in app_logger.rb und functions.rb. main.rb enthält den bereitgestellten Eingabe-/Ausgabecode und darf nicht bearbeitet werden.

Probier es selbst

require_relative 'app_logger'
require_relative 'functions'

# Gelieferter Ein-/Ausgabecode: belasse ihn wie er ist
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 dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-Ruby-Compiler