C#やJavaなどのオブジェクト指向型プログラミング言語に用意されている抽象クラスとインターフェースですが、コード的な違いは理解していても使い分けまでできているケースは意外と少ないです。中級未満のエンジニアだと、そもそも抽象クラスやインターフェースを使うことすらしない場合も。決して自分が完璧に理解し、使いこなしていると自負しているわけではないのですが、中級者レベルの理解と使い分けについてまとめます。
抽象クラスとインターフェースの違い
文法的(コード)な違い
- インターフェースは実装を持てないが、抽象クラスは実装を持つことができる
- 具象クラスはインターフェースを複数実装できるが、抽象クラスは1つしか継承することができない
教科書的には上記が主な違いになります。ただ、上記の文法的な違いだけを根拠に使い分けているとコードは保守性、拡張性を落としてしまいます。
- 異なる実装の処理を同じインスタンスとして使用したいからインターフェースを使う
- 処理を共通化したいから抽象クラス使う
- 複数使いたいからインターフェースを使う
この考えはオブジェクト指向ではありません。
概念的な違い
個人的には下記のように理解することで使い分けしやすくなりました。
- 抽象クラス: 複数の概念から抽象化されたオブジェクト
- インターフェース: オブジェクトに機能を与えるもの
抽象クラスの理解
抽象クラスの具体例
複数の概念から抽象化されたオブジェクトというのは、複数の概念に存在する共通のバックグラウンド・概念を定義するということです。決して共通の機能を取り出すわけではありません。例えば自動車の場合、
これはトヨタ自動車プリウスのクラス図です。抽象クラスPriusを継承しているのは各世代のプリウスの型式です。今までプリウスには大きく4つの世代と、各マイナーチェンジの兄弟がいます。それらの型式の一部です。ここで、抽象クラスと具象クラスには「ほとんど同じ」という関係になっています。しかし、プリウスという自動車は厳密には存在しません。世代によってデザインは全く異なります。その違いを具象クラスが定義します。全世代のプリウスの共通機能を集約したものがプリウスというわけではありません。
よくある抽象クラスの間違った例
抽象クラスと具象クラスがほとんど同じということは、Carという抽象クラスからPriusの具象クラスの間には相当の数の抽象クラスが存在することになります。が、多くの教科書にはCarクラスをPriusクラスが継承している絵が描かれています。これは間違いです。あまりにも差が大きすぎます。Carクラスを継承している他のクラスにテスラの電気自動車や、マツダのクリーンディーゼル車も並んでいます。こんな抽象クラスはあり得ません。これでは万能クラスです。いずれメンテナンスできなくなり破綻してしまいます。
インターフェースの理解
インターフェースの具体例
インターフェースはオブジェクトに機能を与えるものです。オブジェクトとして異なるもの同士でも同じ機能を有している場合は同じインターフェースのインスタンスとして扱うことができるというものです。抽象クラスとは全然違います。こちらも自動車で例えてみると、
IMoterDrivenはモーター駆動機能のインターフェースです。このインターフェースを実装しているのはトヨタ自動車のPriusクラスやテスラモータースのモデルS、日産自動車リーフがあります。先ほどの抽象クラスではこれらのクラスを1つのスーパークラスで汎化することはできませんでしたが、インターフェースなら可能です。なぜなら、これらの自動車は全てモーター駆動の機能を有しているためです。これらの自動車は、1つのモーター駆動インスタンスとしてモーター駆動に関する動作を表現することができます。このように、インターフェースの場合はオブジェクト的に似ている必要はありません。
抽象クラスとインターフェースを使い分けた具体例
抽象クラスはオブジェクトとしてモデル化されたもので、インターフェースはオブジェクトに機能を与えるです。あくまで抽象クラスやインターフェースを先に設計し、書くべきで、先に書いた具象クラスから抽象クラスやインターフェースを考えたり生み出したりしてはいけません。先に出した抽象クラスやインターフェースを1つの自動車モデルにまとめると下のようになります。
これもまだまだですが、設計の第一段階はいかに抽象クラスやインターフェースに対して実装を書くかが大切かと思います。