Menu
Coddy logo textTech

Паттерн «Одиночка»

Часть раздела Объектно-ориентированное программирование путешествия по Ruby на Coddy. Урок 42 из 57.

Шаблон проектирования — это проверенное решение распространённой задачи проектирования. Шаблон одиночка гарантирует, что у класса будет ровно один экземпляр, доступный из любого места. Стандартная библиотека Ruby поддерживает его из коробки: require 'singleton' и include Singleton добавляют метод класса 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)

Результат:

dark
true

include Singleton также делает new закрытым, поэтому второй экземпляр создать нельзя. Каждая часть программы, которая запрашивает instance, получает один и тот же объект:

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

Вывод:

3
private method `new' called for class Counter

Та же идея, написанная вручную, представляет собой метод класса, который создаёт экземпляр при первом использовании, и закрытый new. Это показывает, что модуль делает за вас:

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

Вывод:

3

Используйте одиночки умеренно. Одиночка — это глобальное состояние: любой код может его изменить, и тесты не могут начинаться с чистой копии. Они подходят для вещей, которые действительно существуют в единственном экземпляре на программу, например для логгера или конфигурации; часто понятнее передавать объект в качестве параметра:

# легче тестировать: логгер передаётся
def process(order, log)
  log.write("processing #{order.id}")
end
challenge icon

Задание

Легко

Завершите реализацию одиночки AppLogger с помощью include Singleton. Он хранит массив записей и имеет log(message, level), который сохраняет [INFO] message или [ERROR] message (уровень в верхнем регистре), а также entries и error_count. Вспомогательные функции handle_info(message) и handle_error(message) в functions.rb обе записывают данные через AppLogger.instance, поэтому обращаются к одному и тому же объекту.

Предоставленный код считывает строки info,message или error,message, передаёт их через вспомогательные функции, выводит каждую запись и errors: 1, а также проверяет, что создание AppLogger.new запрещено.

Ваш код должен находиться в app_logger.rb и functions.rb. В main.rb содержится предоставленный код ввода/вывода, и его нельзя редактировать.

Попробуйте сами

require_relative 'app_logger'
require_relative 'functions'

# Предоставленный код ввода/вывода: оставьте его как есть
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 iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Ruby