Rubyで抽象クラス風な表現をする
抽象クラスとは
抽象クラスとは、抽象メソッドを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の実装が必要であることを明示的に示しています。
Discussion
facets gem のModule#abstract
を使うと、少し幸せになれるかも。コメントありがとうございます!
ドンピシャなgemが存在していたのですね、試してみます🙇