«Персональный» метод в рубине - PullRequest
4 голосов
/ 01 апреля 2010

Я ищу способ сделать метод "личным" - примечание НЕ ЧАСТНОЕ для класса

вот пример - под «личным» я подразумеваю поведение метода «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 ]

3 голосов
/ 01 апреля 2010

Вместо создания «личных» методов измените структуру наследования.

Похоже, что вы хотите, чтобы класс 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
2 голосов
/ 01 апреля 2010

Похоже, это может сбить с толку, но вот один из вариантов:

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

В целом, используя аналогичный подход, вы всегда можете добавить метод к данному экземпляру, который, конечно, не влияет на унаследованные классы или даже на другие экземпляры того же класса.

Если вы дадите нам конкретные данные о том, чего вы в конечном итоге пытаетесь достичь, мы, вероятно, можем быть более полезными.

2 голосов
/ 01 апреля 2010

Нет стандартного способа сделать это. Обходит как работает наследование. Вы можете реализовать метод B, чтобы сделать логику следующим образом:

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

И вы, конечно, могли бы определить метод класса, который бы сделал это для вас подобно public и 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

Тогда вы можете personal :foo в B точно так же, как вы private :foo.

(Это совсем не оптимизировано, и я не реализовал поведение с нулевым аргументом, которое есть у public и private, потому что, честно говоря, это огромный PITA, чтобы делать правильно, и даже тогда это взлом.)

0 голосов
/ 02 апреля 2010

Иногда вы на самом деле не хотите отношения "есть" (наследование). Иногда то, что вы хотите, это «крякает как». Совместное использование кода между классами «крякает как» легко сделать с помощью модулей для «примешивания» методов:

#!/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 голосов
/ 01 апреля 2010

Вы можете написать функцию ярлыка для обработки методов персонализации.

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
0 голосов
/ 01 апреля 2010

Ответить на этот вопрос немного сложно, так как я не вижу, чего вы хотите добиться на практике, но вы можете попробовать что-то вроде

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. Или разработайте всю иерархию классов по-другому.

...