代码之家  ›  专栏  ›  技术社区  ›  Chirantan

如何在ruby中实现抽象类?

  •  107
  • Chirantan  · 技术社区  · 16 年前

    我知道ruby中没有抽象类的概念。但如果真的需要实施的话,该怎么做呢?我尝试了一些像。。。

    class A
      def self.new
        raise 'Doh! You are trying to write Java in Ruby!'
      end
    end
    
    class B < A
      ...
      ...
    end
    

    但当我尝试实例化B时,它会在内部调用 A.new

    此外,模块不能实例化,但也不能继承。将新方法私有化也不起作用。有什么建议吗?

    16 回复  |  直到 9 年前
        1
  •  122
  •   Dan Rosenstark    15 年前

    最后我想插一句,我认为没有理由阻止某人实例化抽象类, especially because they can add methods to it on the fly

    Duck类型语言,如Ruby,在运行时使用方法的存在/不存在或行为来确定是否应该调用它们。因此,你的问题,因为它适用于一个摘要 方法 ,有道理

    def get_db_name
       raise 'this method should be overriden and return the db name'
    end
    

    这应该是故事的结尾了。在Java中使用抽象类的唯一原因是坚持某些方法是“填充”的,而其他方法的行为是在抽象类中。在duck类型语言中,重点是方法,而不是类/类型,因此您应该将您的担忧转移到这个级别。

    abstract 关键字fromJava,这是在Ruby中使用Java的代码味道。

        2
  •  61
  •   Magne    7 年前

    我不喜欢在Ruby中使用抽象类(几乎总是有更好的方法)。但是,如果您真的认为这是解决这种情况的最佳技术,那么可以使用以下代码片段更明确地说明哪些方法是抽象的:

    module Abstract
      def abstract_methods(*args)
        args.each do |name|
          class_eval(<<-END, __FILE__, __LINE__)
            def #{name}(*args)
              raise NotImplementedError.new("You must implement #{name}.")
            end
          END
          # important that this END is capitalized, since it marks the end of <<-END
        end
      end
    end
    
    require 'rubygems'
    require 'rspec'
    
    describe "abstract methods" do
      before(:each) do
        @klass = Class.new do
          extend Abstract
    
          abstract_methods :foo, :bar
        end
      end
    
      it "raises NoMethodError" do
        proc {
          @klass.new.foo
        }.should raise_error(NoMethodError)
      end
    
      it "can be overridden" do
        subclass = Class.new(@klass) do
          def foo
            :overridden
          end
        end
    
        subclass.new.foo.should == :overridden
      end
    end
    

    abstract_methods NotImplementedError 将提出例外情况。

        3
  •  46
  •   Andrew Peters    16 年前

    试试这个:

    class A
      def initialize
        raise 'Doh! You are trying to instantiate an abstract class!'
      end
    end
    
    class B < A
      def initialize
      end
    end
    
        4
  •  20
  •   Fred Willmore    10 年前

    self.abstract_class = true
    
        5
  •  18
  •   jmhmccr    16 年前
    class A
      private_class_method :new
    end
    
    class B < A
      public_class_method :new
    end
    
        6
  •  13
  •   Anthony Navarre    13 年前

    My 2:我选择简单、轻量级的DSL混音:

    module Abstract
      extend ActiveSupport::Concern
    
      included do
    
        # Interface for declaratively indicating that one or more methods are to be
        # treated as abstract methods, only to be implemented in child classes.
        #
        # Arguments:
        # - methods (Symbol or Array) list of method names to be treated as
        #   abstract base methods
        #
        def self.abstract_methods(*methods)
          methods.each do |method_name|
    
            define_method method_name do
              raise NotImplementedError, 'This is an abstract base method. Implement in your subclass.'
            end
    
          end
        end
    
      end
    
    end
    
    # Usage:
    class AbstractBaseWidget
      include Abstract
      abstract_methods :widgetify
    end
    
    class SpecialWidget < AbstractBaseWidget
    end
    
    SpecialWidget.new.widgetify # <= raises NotImplementedError
    

    当然,在这种情况下,为初始化基类添加另一个错误是微不足道的。

        7
  •  13
  •   Austin Ziegler    6 年前

    在最近6年半的Ruby编程中,我没有 需要

    如果您认为您需要一个抽象类,那么您在提供/需要抽象类的语言中考虑的太多了,而不是在Ruby中。

    NoMethodError 将产生与测试完全相同的结果 AbstractClassError 在生产中。

        8
  •  7
  •   Nicklasos    12 年前

    您可以尝试3种rubygems:
    interface
    abstract
    simple abstract

        9
  •  5
  •   jshen    16 年前

    您试图为抽象类服务的目的是什么?在ruby中可能有更好的方法,但您没有给出任何细节。

    我的指针是这个;使用 mixin 不是继承。

        10
  •  4
  •   Zack    16 年前

        11
  •  4
  •   Chuck    16 年前

    如果要使用不可实例化的类,请在抛出错误之前,在A.new方法中检查self==A。

    include SomeModule 达到大致相同的目标。这对你不起作用有什么原因吗?

        12
  •  4
  •   rampion    16 年前

    另一个答案是:

    module Abstract
      def self.append_features(klass)
        # access an object's copy of its class's methods & such
        metaclass = lambda { |obj| class << obj; self ; end }
    
        metaclass[klass].instance_eval do
          old_new = instance_method(:new)
          undef_method :new
    
          define_method(:inherited) do |subklass|
            metaclass[subklass].instance_eval do
              define_method(:new, old_new)
            end
          end
        end
      end
    end
    

    class A
      include Abstract
    end
    class B < A
    end
    
    B.new #=> #<B:0x24ea0>
    A.new # raises #<NoMethodError: undefined method `new' for A:Class>
    

    正如其他海报所说,您可能应该使用mixin,而不是抽象类。

        13
  •  3
  •   ZeusTheTrueGod    14 年前

    我是这样做的,所以它重新定义了new on child类,以找到一个新的on非抽象类。

    puts 'test inheritance'
    module Abstract
      def new
        throw 'abstract!'
      end
      def inherited(child)
        @abstract = true
        puts 'inherited'
        non_abstract_parent = self.superclass;
        while non_abstract_parent.instance_eval {@abstract}
          non_abstract_parent = non_abstract_parent.superclass
        end
        puts "Non abstract superclass is #{non_abstract_parent}"
        (class << child;self;end).instance_eval do
          define_method :new, non_abstract_parent.method('new')
          # # Or this can be done in this style:
          # define_method :new do |*args,&block|
            # non_abstract_parent.method('new').unbind.bind(self).call(*args,&block)
          # end
        end
      end
    end
    
    class AbstractParent
      extend Abstract
      def initialize
        puts 'parent initializer'
      end
    end
    
    class Child < AbstractParent
      def initialize
        puts 'child initializer'
        super
      end
    end
    
    # AbstractParent.new
    puts Child.new
    
    class AbstractChild < AbstractParent
      extend Abstract
    end
    
    class Child2 < AbstractChild
    
    end
    puts Child2.new
    
        14
  •  3
  •   シリル    10 年前

    abstract_type gem,允许以非结构化的方式声明抽象类和模块。

    示例(来自 README.md

    class Foo
      include AbstractType
    
      # Declare abstract instance method
      abstract_method :bar
    
      # Declare abstract singleton method
      abstract_singleton_method :baz
    end
    
    Foo.new  # raises NotImplementedError: Foo is an abstract type
    Foo.baz  # raises NotImplementedError: Foo.baz is not implemented
    
    # Subclassing to allow instantiation
    class Baz < Foo; end
    
    object = Baz.new
    object.bar  # raises NotImplementedError: Baz#bar is not implemented
    
        15
  •  1
  •   Alex Wayne    16 年前

    class A
      class AbstractClassInstiationError < RuntimeError; end
      def initialize
        raise AbstractClassInstiationError, "Cannot instantiate this class directly, etc..."
      end
    end
    

    另一种方法是将所有这些功能放在一个模块中,正如您所提到的,这是无法灌输的。然后将模块包含在类中,而不是从另一个类继承。然而,这将打破像超级的东西。

        16
  •  1
  •   Nikos D    14 年前
        17
  •  1
  •   lulalala    5 年前

    虽然这感觉不像Ruby,但您可以这样做:

    class A
      def initialize
        raise 'abstract class' if self.instance_of?(A)
    
        puts 'initialized'
      end
    end
    
    class B < A
    end
    

    >> A.new
      (rib):2:in `main'
      (rib):2:in `new'
      (rib):3:in `initialize'
    RuntimeError: abstract class
    >> B.new
    initialized
    => #<B:0x00007f80620d8358>
    >>