扩展 return 实例方法的 class 值

Extend return value of class instance method

我有一个 class 有一个实例方法,returns 一个散列。我无法直接更改 class 的代码,但我可以使用模块对其进行扩展。我需要向该方法的返回散列添加一些新键。像这样:

class Processor
  def process
    { a: 1 }
  end
end

module ProcessorCustom
  def process
    super.merge(b: 2) # Not works :(
  end
end

Processor.send :include, ProcessorCustom

processor = Processor.new
processor.process  # returns { a: 1 }, not { a: 1, b: 2 }

我该怎么做?谢谢

您可以调用 prepend 而不是 include:

Processor.prepend(ProcessorCustom)

processor = Processor.new
processor.process
#=> {:a=>1, :b=>2}

prependinclude 导致不同的祖先顺序:

module A; end
module B; end
module C; end

B.ancestors     #=> [B]

B.include(C)
B.ancestors     #=> [B, C]

B.prepend(A)
B.ancestors     #=> [A, B, C]

备选方案

根据您的用例,您还可以 extend 特定实例:(这不会影响其他实例)

processor = Processor.new
processor.extend(ProcessorCustom)
processor.process
#=> {:a=>1, :b=>2}

或使用SimpleDelegator to implement a decorator pattern:

require 'delegate'
class ProcessorCustom < SimpleDelegator
  def process
    super.merge(b: 2)
  end
end

processor = ProcessorCustom.new(Processor.new)
processor.process  #=> {:a=>1, :b=>2}

我认为创建代理比污染原始更好class。

class Proxy
  def initialize(target)
    @target = target
  end

  # add some syntactic sugar
  singleton_class.class_eval { alias [] new }

  def process
    @target.process.merge!(b: 2)
  end
end

Proxy[Processor.new].process  #=> {a: 1, b: 2}

您甚至可以创建自己的动态代理。

class DynamicProxy < BasicObject
  def initialize(target)
    @target = target
  end

  # again, add some syntactic sugar
  singleton_class.class_eval { alias [] new }

  def method_missing(name, *args, &block)
    super unless @target.respond_to?(name)
    # Do something before calling the target method
    result = @target.send(name, *args, &block)
    # Do something after calling the target method
  end

  def respond_to_missing?(name, include_private = false)
    @target.respond_to?(name, include_private)
  end
end

要隐藏创建 Processor 实例的细节,您可以制作一个简单的工厂来处理创建。

module ProcessorFactory
  def self.create
    DynamicProxy[Processor.new]
  end
end

然后你就可以做你的工作了

ProcessorFactory.create.process

我认为第一个要考虑的选项是 reader 需要最少工作才能理解的选项;在面向对象的软件中,这将是一个子类来专门化超类的行为。当且仅当有令人信服的理由时,我才会偏离这一点。

这个怎么样?:

#!/usr/bin/env ruby

class Processor
  def foo
    { x: 3 }
  end
end


class MyProcessor < Processor
  def foo
    super.merge({ y: 7 })
  end
end

p MyProcessor.new.foo  # outputs: {:x=>3, :y=>7}