web-dev-qa-db-ja.com

Rubyで抽象クラスを実装する方法は?

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これは例外を発生させます。

また、モジュールはインスタンス化できませんが、継承することもできません。新しいメソッドをプライベートにすることもできません。ポインタはありますか?

111
Chirantan

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例外が発生します。

57
nakajima

遅くここでチャイムするために、誰かが抽象クラスをインスタンス化するのを止める理由はないと思います 特に、その場でメソッドを追加できるので

Rubyなどのダックタイピング言語は、実行時にメソッドの有無または動作を使用して、メソッドを呼び出す必要があるかどうかを判断します。したがって、抽象メソッドに適用される質問は意味があります

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

そして、それは物語の終わりについてであるべきです。 Javaで抽象クラスを使用する唯一の理由は、特定のメソッドが「埋められる」一方で、他のメソッドは抽象クラスで動作することを主張することです。ダックタイピング言語では、クラス/タイプではなくメソッドであるため、心配はそのレベルに移動する必要があります。

あなたの質問では、基本的にJavaからabstractキーワードを再作成しようとしています。これはRubyでJavaを実行するためのコード臭です。

111
Dan Rosenstark

これを試して:

class A
  def initialize
    raise 'Doh! You are trying to instantiate an abstract class!'
  end
end

class B < A
  def initialize
  end
end
42
Andrew Peters
class A
  private_class_method :new
end

class B < A
  public_class_method :new
end
14
bluehavana

私の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

もちろん、この場合、基本クラスを初期化するための別のエラーを追加するのは簡単です。

12
Anthony Navarre

Railsの世界の誰にとっても、ActiveRecordモデルを抽象クラスとして実装するには、モデルファイルの次の宣言を使用します。

self.abstract_class = true
11
Fred Willmore

Rubyのプログラミングの過去6年半の間、私は必要抽象クラスを一度もしていません。

抽象クラスが必要だと思っているなら、Rubyとしてではなく、それらを提供/要求する言語で考えすぎている。

他の人が示唆しているように、ミックスインはインターフェースであると想定されるもの(Java定義))により適切であり、設計を再考することは、 C++などの他の言語。

更新2019:Ruby使用16年半で抽象クラスを必要としませんでした。私の応答にコメントするすべての人々が言っ​​ているすべては、実際に学習することによって対処されますRubyそして、モジュールのような適切なツールを使用します(共通の実装も提供します)。失敗したベース実装を持つクラスを作成したチームの人々がいます(抽象クラ​​スのような)が、これらのほとんどはNoMethodErrorは生産中のAbstractClassErrorとまったく同じ結果を生成するため、コーディングの無駄です。

10
Austin Ziegler

次の3つのrubygemsを試すことができます。
インターフェース
要約
単純な要約

6
Nicklasos

インスタンス化できないクラスを使用したい場合は、A.newメソッドで、エラーをスローする前にself == Aかどうかを確認してください。

しかし、実際には、モジュールはここで必要なもののように見えます。たとえば、Enumerableは他の言語の抽象クラスのようなものです。技術的にはサブクラス化できませんが、include SomeModuleはほぼ同じ目標を達成します。これがうまくいかない理由はありますか?

4
Chuck

抽象クラスで提供しようとしているのはどのような目的ですか? Rubyでそれを行うより良い方法はおそらくありますが、詳細は提供しませんでした。

私の指針はこれです。継承ではなく mixin を使用します。

4
jshen

この小さな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
3
シリル

別の答え:

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

これは、実装されていないメソッドを報告する通常の#method_missingに依存しますが、抽象クラスが実装されないようにします(初期化メソッドがある場合でも)

class A
  include Abstract
end
class B < A
end

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

他のポスターが言ったように、おそらく抽象クラスではなく、ミックスインを使用する必要があります。

3
rampion

このようにしたので、子クラスで新しいものを再定義して、非抽象クラスで新しいものを見つけます。 Rubyで抽象クラスを使用するのにまだ実用的とは思えません。

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
3
ZeusTheTrueGod

個人的には、抽象クラスのメソッドでNotImplementedErrorを発生させます。しかし、あなたが言及した理由のために、「新しい」方法から除外したいかもしれません。

3
Zack
1
Nikos D

アプローチに問題はありません。もちろん、すべてのサブクラスがinitializeをオーバーライドする限り、initializeでエラーを発生させることは問題ないようです。しかし、あなたはself.newをそのように定義したくありません。これが私がすることです。

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

別のアプローチでは、すべての機能をモジュールに配置します。これは、先ほど述べたように、決して開始することはできません。次に、別のクラスから継承するのではなく、クラスにモジュールを含めます。ただし、これはスーパーのようなものを壊します。

そのため、どのように構造化するかによって異なります。モジュールは、「他のクラスが使用するように設計されたものをどのように書くのか」という問題を解決するためのよりクリーンなソリューションのように見えますが

1
Alex Wayne