红宝石中的“个人”方法

时间:2010-03-31 20:08:58

标签: ruby access-specifier

我正在寻找一种使方法“个人化”的方法 - 注意不是私人的课程

这是一个例子 - “个人”我指的是方法“foo”的行为

class A
  def foo
     "foo"
  end
end

class B < A
  def foo
      "bar"
  end
end

class C < B
end

a=A.new; b=B.new;c=C.new

我正在寻找一种产生以下行为的方法

a.foo #=> "foo"

b.foo #=> "bar"

c.foo #=> "foo" (ultimate base class method called)

6 个答案:

答案 0 :(得分:3)

不要创建“个人”方法,而是更改继承结构。

您似乎希望C类只具有B类的一些相同功能,而不会更改A类。

class A
  def foo
     "foo"
  end
end

class BnC < A
end

class B < BnC
  def foo
      "bar"
  end
end

class C < BnC
end

a=A.new; b=B.new;c=C.new

答案 1 :(得分:2)

没有标准的方法可以做到这一点。它规避了继承的工作方式。您可以实现B的方法来执行这样的逻辑:

def foo
  instance_of?(B) ? "bar" : super
end

当然,你可以在Class上定义一个类似于publicprivate的方法。

class Class
  def personal(*syms)
    special_class = self
    syms.each do |sym|
      orig = instance_method(sym)
      define_method(sym) {|*args| instance_of?(special_class) ? orig.bind(self).call(*args) : super}
    end
  end
end

然后你就可以像personal :foo一样在{B}中private :foo

(这根本没有优化,我没有实现publicprivate所具有的零参数行为,因为坦率地说这是一个巨大的PITA做得对,即便如此它也是一个黑客。)

答案 2 :(得分:2)

似乎可能令人困惑,但这里有一个选择:

class A
  def foo
     "foo"
  end
end

class B < A
 def initialize #when constructing, add the new foo method to each instance
    def self.foo
      "bar"
    end 
 end
end

class C < B
 def initialize #when constructing, do nothing
 end
end

更一般地说,使用类似的方法,您始终可以向给定实例添加方法,这当然不会影响继承的类,也不会影响同一类的其他实例。

如果你告诉我们你最终想要完成的事情,我们可能会更有帮助。

答案 3 :(得分:0)

回答这个问题有点棘手,因为我没有真正看到你想要在实践中完成什么,但是你可以尝试类似

class C < B
  def foo
    self.class.ancestors[-3].instance_method(:foo).bind(self).call
  end
end

ancestors[-3]假设A继承自Object和Kernel,并且您的意图是从最顶层的非内置类访问该方法。当然,您可以用self.class.ancestors[-3]替换A或者自己从数组ancestors中找出类等。)

在实践中,如果您可以在新B之前修改alias :foo_from_A :foo中的class B < A,那么在课程def foo中对原文进行别名会更简单,那么您可以在foo_from_A中拨打C。或者只是在C中重新定义您想要的内容。或者以不同的方式设计整个类层次结构。

答案 4 :(得分:0)

您可以编写快捷方式来处理个性化方法。

def personalize(*methodNames)
  old_init = instance_method(:initialize)
  klass = self
  modul = Module.new {
    methodNames.each { |m|
      define_method(m, klass.instance_method(m)) if klass.method_defined?(m)
    }
  }
  methodNames.each { |m| 
    remove_method(m) if method_defined?(m)
  }
  define_method(:initialize) { |*args|
    # I don't like having to check instance_of?, but this is the only way I 
    # could thing of preventing the extension of child classes. At least it only
    # has to happen once, during initialization.
    extend modul if instance_of?(klass)
    old_init.bind(self).call(*args)
  }
  self
end

class A
  def foo
     "foo"
  end
end

class B < A
  def foo
      "bar"
  end
  def bam
    'bug-AWWK!'
  end
  personalize :foo, :bam, :nometh
end

class C < B
end

a=A.new; b=B.new; c=C.new
a.foo #=> "foo"
b.foo #=> "bar"
b.bam #=> "bug-AWWK!"
c.foo #=> "foo"
C.instance_method(:foo) # => #<UnboundMethod: C(A)#foo>
c.bam #throws NoMethodError

答案 5 :(得分:0)

有时你并不真正想要一个“是一个”(继承)关系。有时你想要的是“嘎嘎叫”。通过使用模块“混合”方法,可以轻松地在“quacks like a”类中共享代码:

#!/usr/bin/ruby1.8

module BasicFoo
  def foo
     "foo"
  end
end

class A
  include BasicFoo
end

class B
  def foo
      "bar"
  end
end

class C
  include BasicFoo
end

p A.new.foo    # => "foo"
p B.new.foo    # => "bar"
p C.new.foo    # => "foo"