Chirantan
Chirantan

Reputation: 15654

How to make a base class method non-overridable in ruby?

I have some base class A with a method that is not to be overridden.

class A
  def dont_override_me
    puts 'class A saying, "Thank you for not overriding me!"'
  end
end

And another class B that extends A and tries to override the dont_override_me method.

class B < A
  def dont_override_me
    puts 'class B saying, "This is my implementation!"'        
  end
end

If I instantiate B and call dont_override_me, class B's instance method will be called.

b = B.new
b.dont_override_me # => class B saying, "This is my implementation!"

This is because of ruby's properties. Understandable.

However, how do I force the base class method dont_override_me to be non-overridable by it's derived classes? I could not find a keyword like final in java for ruby. In C++, the base class methods can be made non-virtual so that they become non-overridable by the derived classes. How do I achieve this in ruby?

Upvotes: 5

Views: 5791

Answers (4)

MVP
MVP

Reputation: 1111

One way to prevent a method from being overridden by a subclass (but not recommend) :

class Class
  def frozen_method(method)
    if class_variable_defined?(:@@__frozen_methods__)
      add= class_variable_get(:@@__frozen_methods__) | [method]
      class_variable_set(:@@__frozen_methods__,add)
    else
      class_variable_set(:@@__frozen_methods__,[method])
    end
    class << self
      def inherited(child)
        def method_added(method)
          if class_variable_get(:@@__frozen_methods__).include? method
            send(:remove_method, method)
            error="Cannot change method #{method} because it's not overridde"
            raise TypeError, error
          end
        end
      end
    end
  end
end

class Foo
  def hello
    'hello'
  end
  def foo
    'foo'
  end

  frozen_method :foo
end

class Bar < Foo
  def foo
    'new foo'
  end
end

#=> TypeError: Cannot change method foo because it's not overridde

Bar.new.foo  #=> 'foo'

Warning: this example is not complete. If you add frozen_method for a previously defined method in the subclass, when this method will be modified in the subclass, it will lose its implementation.

Upvotes: 0

M. Lanza
M. Lanza

Reputation: 6790

I recommend:

class A #This is just as you've already defined it.
  def dont_override_me
    puts 'class A saying, "Thank you for not overriding me!"'
  end
end

module BehaviorForB
  def dont_override_me
    puts 'class B saying, "This is my implementation!"'        
  end

  def greet
    "Hello, Friend."
  end
end

class B < A
  include BehaviorForB
end

b = B.new
b.dont_override_me #=> class A saying, "Thank you for not overriding me!"
b.greet #=> Hello, Friend.

By keeping B's methods tucked away in an mixin, you get exactly what you desire. Any method of B's methods that are not already in A will be available. Methods that are already in A will not be overridden.

Upvotes: 2

Gdeglin
Gdeglin

Reputation: 12618

Here's a way to do it: http://www.thesorensens.org/2006/10/06/final-methods-in-ruby-prevent-method-override/

This has also been packaged into a gem called "finalizer" (gem install finalizer)

This makes use of the method_added callback and compares the new method name with a list of methods that you wish to make final.

Upvotes: 4

Steven Robbins
Steven Robbins

Reputation: 26599

You can do it, by hooking the change event and changing it back, but it seems a bit smelly to me:

http://scie.nti.st/2008/9/17/making-methods-immutable-in-ruby

It's one of those things that sort of defines Ruby, so fighting against it seems a little pointless imo. If someone redefines something so it breaks horribly.. that's their problem ;-)

Upvotes: 6

Related Questions