🔖

Rubyで抽象クラス風な表現をする

2023/12/13に公開2

抽象クラスとは

抽象クラスとは、抽象メソッドを1つ以上持つクラスのことで、
抽象メソッドとは、メソッド名や引数、返り値の型のみを宣言し、
子クラスに具体処理の実装を強制させるようなメソッドです。

JavaやTypeScriptではクラスやメソッドの宣言時にabstract修飾子をつけることによって、
抽象クラスや抽象メソッドを定義することができますが、Rubyにはそのような機能がありません。

そのため、Rubyでは抽象クラスのような形で子クラスに特定のメソッドの実装を強制させることが難しいです。

ただし、直接的に強制することは難しくても、
継承先であるメソッドのoverrideの必要性を明示することは可能です。
このような抽象クラス風な表現をする方法を紹介します。

Rubyで抽象クラスを表現する方法

やり方はシンプルで、抽象メソッドとしてoverrideさせたいメソッドの中身でNotImplementedErrorをraiseします。

class AbstractClass
  def abstract_method
    raise NotImplementedError, 'Subclasses must implement abstract_method'
  end
end

これにより、子クラスでabstract_methodを呼び出した場合、NotImplementedErrorがraiseされます。

class ConcreteClass < AbstractClass
end

concrete = ConcreteClass.new
concrete.abstract_method
#=> `abstract_method': Subclass must implement abstract method (NotImplementedError)

子クラスでメソッドをoverrideしてあげれば、無事実行できるようになります。

class ConcreteClass < AbstractClass
  def abstract_method
    puts 'abstract_method in Concrete was called'
  end
end

concrete = ConcreteClass.new
concrete.abstract_method
#=> abstract_method in Concrete was called

このように継承先のクラスにおける実装の必要性を明示することで、
抽象クラス風なクラスをRubyでも表現できるようになります。

注意点

Rubyで定義されたクラスのインスタンスメソッドは、呼び出されるまで実行されません。
そのため、NotImplementedErrorを呼んでいたとしても、実際に実行されるまでは、
継承先のクラスでoverrideされているかどうかが分かりません。

継承先のクラスに実装を強制させるというよりも、
継承先のクラスで該当メソッドのoverrideが必要であることを明示する、
ための実装であるということに注意しましょう。

実用例

最後にOSS上でこの実装が使用されている例を紹介します。

普段Railsを使ってカスタムバリデーションを実装したことがある方なら、
触ったことがあるかもしれませんが、
ActiveModel::EachValidatorクラスがその一つです。

このクラスは#validate_eachというメソッドを持っており、内部ではNotImplementedErrorがraiseされています。

def validate_each(record, attribute, value)
    raise NotImplementedError, "Subclasses must implement a validate_each(record, attribute, value) method"
end

record, attribute, valueは引数として受け取ることを明示した上でNotImplementedErrorをraiseしています。
これにより、このクラスの継承先でvalidation_eachの実装が必要であることを明示的に示しています。

GitHubで編集を提案

Discussion