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


121

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、例外が発生する内部で呼び出されます。

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


1
モジュールは混在させることができますが、他の理由で古典的な継承が必要だと思いますか?
Zach

6
抽象クラスを実装する必要があるということではありません。私はそれを行う必要がある場合、どうすればよいのか疑問に思っていました。プログラミングの問題。それでおしまい。
チランタン2009

127
raise "Doh! You are trying to write Java in Ruby"
Andrew Grimm、

回答:


61

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


これは実際にはインターフェースに似ていますが、私はそのアイデアを理解しました。ありがとう。
Chirantan 2009年

6
これは、NotImplementedError本質的に「プラットフォームに依存し、あなたのプラットフォームでは利用できない」ことを意味する有効なユースケースとしては聞こえません。 docsを参照してください
スケーリー2014

6
1行のメソッドをメタプログラミングに置き換えます。これで、ミックスインを含めてメソッドを呼び出す必要があります。これはもっと宣言的だとは思いません。
Pascal

1
私はこの回答を編集していくつかのバグを修正し、コードを更新したので、実行されます。また、次の理由により、インクルードの代わりに拡張を使用するためにコードを少し簡略化しました:yehudakatz.com/2009/11/12/better-ruby-idioms
Magne

1
@ManishShrivastava:ここでENDを使用することの重要性に関するコメントについては、このコードを今すぐ参照してください
Magne

113

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

Rubyのようなダックタイピング言語は、実行時にメソッドの有無や動作を使用して、それらを呼び出すかどうかを決定します。したがって、あなたの質問は、抽象メソッドに適用されるので、理にかなっています

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

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

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


3
@クリストファー・ペリー:理由は何ですか?
SasQ 2013

10
@ChristopherPerryまだわかりません。結局のところ、親と兄弟関連していて、この関係を明示的にしたいのであれば、なぜこの依存関係を望まないのですか?また、あるクラスのオブジェクトを他のクラスの中に作成するには、その定義も知っておく必要があります。継承は通常、合成として実装されます。それは、合成されたオブジェクトのインターフェースを、それを埋め込むクラスのインターフェースの一部にするだけです。したがって、埋め込みオブジェクトまたは継承オブジェクトの定義が必要です。それとも何か別のことを話しているのですか?それについてもう少し詳しく説明できますか?
SasQ 2013

2
@SasQ、それを作成するために親クラスの実装の詳細を知る必要はありません。APIを知っているだけで十分です。ただし、継承する場合は、親の実装に依存します。実装が変更されると、コードが予期しない方法で破損する可能性があります。詳細はこちら
クリストファー・ペリー

16
申し訳ありませんが、「継承よりも好きな構成」は「常にユーザー構成」とは言いません。一般的に継承は避けるべきですが、より適切に適合するユースケースがいくつかあります。本を盲目的にフォローしないでください。
Nowaker 2014

1
@Nowaker非常に重要なポイント。そのため、「この場合の実用的なアプローチは何か」と考えるのではなく、読んだり聞いたりしたものに目がくらむ傾向があります。完全に黒や白になることはめったにありません。
ルンドベルクあたり2017

44

これを試して:

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

class B < A
  def initialize
  end
end

38
#initializeBのスーパーを使用できるようにしたい場合は、実際にはA#initializeで何でも発生させることができますif self.class == A
mk12 2010

17
class A
  private_class_method :new
end

class B < A
  public_class_method :new
end

7
さらに、親クラスの継承されたフックを使用して、コンストラクターメソッドをすべてのサブクラスで自動的に表示することもできます。def A.inherited(subclass); subclass.instance_eval {public_class_method:new}; 終了
2010

1
とてもいいt6d。コメントとして、それが意外な振る舞いであることから、文書化されていることを確認してください(最小の意外な違反)。
bluehavana 2010年

16

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

self.abstract_class = true

12

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

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


1
編集:良い方法として、このアプローチはdefine_methodを使用しているため、バックトレースが損なわれていないことを確認したい場合があります。例:err = NotImplementedError.new(message); err.set_backtrace caller()YMMV
Anthony Navarre

このアプローチは非常にエレガントだと思います。この質問にご協力いただきありがとうございます。
wes.hysell

12

Rubyのプログラミングのこの6年半の間、私は抽象クラスを一度も必要としませんでした。

抽象クラスが必要だと考えている場合、Ruby自体ではなく、抽象クラスを提供/要求する言語で考えすぎています。

他の人が示唆しているように、ミックスインはインターフェース(Javaが定義するもの)であることに適しているため、設計を再考することは、C ++のような他の言語からの抽象クラスを「必要とする」ものにより適しています。

アップデート2019:16年間の使用でRubyの抽象クラスを必要としませんでした。私の返答についてコメントしているすべての人々が言っ​​ていることはすべて、実際にRubyを学習し、モジュールなどの適切なツールを使用することで対処されます(これにより、一般的な実装も可能になります)。私が管理しているチームには、基本的な実装が失敗するクラス(抽象クラ​​スなど)を作成した人がいますが、これらは、本番NoMethodError環境とまったく同じ結果を生成するため、コーディングの無駄ですAbstractClassError


24
Javaの抽象クラスも/必要としません。これは、それが基本クラスであり、クラスを拡張する人のためにインスタンス化されるべきではないことを文書化する方法です。
fijiaaron 2010年

3
IMO、オブジェクト指向プログラミングの概念を制限する言語はほとんどありません。特定の状況で何が適切かは、パフォーマンスに関連する理由(またはより説得力のあるもの)がない限り、言語に依存するべきではありません。
thekingoftruth 2013年

10
@fijiaaron:そう思うなら、抽象基底クラスが何であるかは間違いなく理解できません。クラスをインスタンス化してはならないことを「文書化」することはあまりありません(むしろ、それが抽象であることの副作用です)。それは、派生クラスの束の共通インターフェースを述べることであり、それが実装されることを保証します(そうでない場合、派生クラスも抽象のままです)。その目的は、インスタンス化があまり意味をなさないクラスに対して、リスコフの代入原理をサポートすることです。
SasQ 2013

1
(続き)もちろん、いくつかの一般的なメソッドとプロパティを含むクラスをいくつか作成することもできますが、コンパイラ/インタープリタはこれらのクラスが何らかの方法で関連していることを知りません。メソッドとプロパティの名前は、これらの各クラスで同じにすることができますが、これだけでは、それらが同じ機能を表すことを意味するわけではありません(名前の対応が偶然である可能性があります)。この関係についてコンパイラーに伝える唯一の方法は、基本クラスを使用することですが、この基本クラス自体のインスタンスが存在することが常に意味があるとは限りません。
SasQ 2013


4

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


しかし、それがインスタンス化されないようにする方法は?
チランタン2009

個人的にはRubyを使い始めたばかりですが、Pythonでは__init ___()メソッドが宣言されたサブクラスは、スーパークラスの__init __()メソッドを自動的に呼び出しません。Rubyにも同様の概念があるといいのですが、先ほど述べたように、まだ始まったばかりです。
ザック

Rubyでは、initialize明示的にsuperで呼び出されない限り、親のメソッドは自動的に呼び出されません。
mk12

4

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

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


4

抽象クラスを提供する目的は何ですか?ルビでそれを行うにはおそらくもっと良い方法がありますが、詳細は何も伝えていません。

私のポインタはこれです。継承ではなくミックスインを使用します。


確かに。モジュールを混合することAbstractClassを使用するのと同じようになります:wiki.c2.com/?AbstractClass PS:モジュールは、彼らが何であるかで、それらを混合して、あなたが彼らと何をすべきかですので、レッツ・コール彼らは、ミックスインモジュールとありません。
マグネ

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

私はそれをこのようにしたので、新しいon子クラスを再定義して、新しいon非抽象クラスを見つけます。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

この小さなabstract_type宝石もあり、抽象クラスとモジュールを控えめな方法で宣言できます。

例(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

1

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

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

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

したがって、それをどのように構成するかによって異なります。モジュールは「他のクラスが使用できるように設計されたものをどのように書くのですか」の問題を解決するためのより明確な解決策のように見えますが


私もそうしたくありません。だから子供たちは「スーパー」と呼べない。
オースティンジーグラー


0

これは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>
>>
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.