开发者

"Personal" method in ruby

I'm looking for a way of making a method "personal" - note NOT PRIVATE to a class

here is an example - by "personal" I mean the behaviour of method "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

I'm looking for a way of producing the following behaviour

a.foo #=> "foo"

b.foo #=> "bar开发者_如何学运维"

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


Instead of creating 'personal' methods, change your inheritance structure.

It appears that you want the C class to have only some of the same functionality of the B class while not making changes to the A class.

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


There's no standard way of doing this. It circumvents how inheritance works. You could implement B's method to do the logic like this:

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

And you could of course define a method on Class that would do this for you similar to public and private.

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

Then you can personal :foo in B just like you'd private :foo.

(This isn't at all optimized and I didn't implement the zero-argument behavior that public and private have because frankly it's a huge PITA to do right and even then it's a hack.)


Seems like it could be confusing, but here's one option:

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

More generally, using a similar approach, you can always add a method to a given instance, which of course has no effect on inherited classes or indeed on other instances of the same class.

If you give us specifics of what you're ultimately trying to accomplish we can probably be more helpful.


Answering this is a bit tricky since I don't really see what you want to accomplish in practice, but you could try something like

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

(The ancestors[-3] assumes that A inherits from Object and Kernel and your intent was to access the method from the topmost non-builtin class. Of course you could substitute self.class.ancestors[-3] with just A, or figure out the class from the Array ancestors yourself, etc.)

In practice it would be simpler to alias the original in class B if you can modify it (i.e. alias :foo_from_A :foo in class B < A before the new def foo, then you can call foo_from_A in C). Or just redefine what you want in C. Or design the whole class hierarchy differently.


You can write a shortcut function to handle personalizing methods.

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


Sometimes you don't really want an "is a" (inheritance) relationship. Sometimes what you want is "quacks like a." Sharing code among "quacks like a" classes is easily done by using modules to "mix in" methods:

#!/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"
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜