Ruby: DRY class methods calling Singleton instance methods [duplicate]

时间秒杀一切 提交于 2021-01-27 08:44:49

问题


I have the a Singleton class ExchangeRegistry which keeps all the Exchange objects.

Instead of needing to call: ExchangeRegistry.instance.exchanges

I want to be able to use: ExchangeRegistry.exchanges

This works, but I'm not happy with the repetition:

require 'singleton'

# Ensure an Exchange is only created once
class ExchangeRegistry
  include Singleton

  # Class Methods  ###### Here be duplication and dragons

  def self.exchanges
    instance.exchanges
  end

  def self.get(exchange)
    instance.get(exchange)
  end

  # Instance Methods

  attr_reader :exchanges

  def initialize
    @exchanges = {} # Stores every Exchange created
  end

  def get(exchange)
    @exchanges[Exchange.to_sym exchange] ||= Exchange.create(exchange)
  end
end

I'm not happy with the duplication in the class methods.

I've tried using Forwardable and SimpleDelegator but can't seem to get this to DRY out. (Most examples out there are not for class methods but for instance methods)


回答1:


The forwardable module will do this. Since you are forwarding class methods, you have to open up the eigenclass and define the forwarding there:

require 'forwardable'
require 'singleton'

class Foo

  include Singleton

  class << self
    extend Forwardable
    def_delegators :instance, :foo, :bar
  end

  def foo
    'foo'
  end

  def bar
    'bar'
  end

end

p Foo.foo    # => "foo"
p Foo.bar    # => "bar"



回答2:


The accepted answer is clever, but seems needlessly complex (not to mention to performance penalty of method_missing.

The usual way to solve this is to just assign the instance to a constant.

class ExchangeRegistrySingleton
  include Singleton

  # ...
end

ExchangeRegistry = ExchangeRegistrySingleton.instance



回答3:


You can take advantage of method_missing hook and delegate the method calls to instance.

require 'singleton'

class ExchangeRegistry
  include Singleton

  # Missing methods will be delegated to `instance` if an implementation is available.
  # Else `NoMethodError` will be raised via call to `super`
  def self.method_missing method_name, *args
    if instance.respond_to? method_name
        puts "** Defining new method: '#{method_name}'"
        (class << self; self; end).instance_eval do
            define_method(method_name) do |*args|
                instance.send(method_name, *args)
            end
        end
        instance.send(method_name, *args)
    else
        super
    end
  end

  attr_reader :exchanges

  def initialize
    @exchanges = {} # Stores every Exchange created
  end

  def get(exchange)
    @exchanges[Exchange.to_sym exchange] ||= Exchange.create(exchange)
  end
end

# By default, there is no class method - `exchanges`
p ExchangeRegistry.singleton_methods.grep(/exchanges/)
#=> []

p ExchangeRegistry.exchanges
#=> ** Defining new method: 'exchanges'
#=> {}

# After first call to `exchanges`, a new class method is now available
# Future calls will not hit `method_missing` again.
p ExchangeRegistry.singleton_methods.grep(/exchanges/)
#=> [:exchanges]
p ExchangeRegistry.exchanges
#=> {}

Another answer of this question indicates that there is performance penalty for handling method_missing. Hence, I have updated the answer to define the class method when first time a method_missing is reported. The update is based on article: Dynamically create class methods in Ruby



来源:https://stackoverflow.com/questions/34828891/ruby-dry-class-methods-calling-singleton-instance-methods

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!