ACCESS Advent Calendar 2021の8日目の記事は@kokada420が担当します。
新卒一年目で、C#を使う案件に配属されたのですが、大学時代は脳筋Pythonコーダーでオブジェクト指向を意識した実装というのを全くしていませんでした。
配属され色々とやっている時にabstract(抽象)クラスとInterfaceがなぜ必要になるのかが最初はあまりピンときておらず、結構手探りでやっていました。
最近はやっとわかってきた(気がする)ので、自分自身の理解を書いていきたいと思います。
abstarct(抽象)クラスとは
抽象クラスとは、インスタンス化ができない継承される目的で実装されるクラスのことです。
普通のクラスと違う点としては、抽象メソッドを持っている点です。
抽象メソッドとは、それ自体は中身や機能を持っておらず「空」のメソッドになっています。
空になっているため、派生クラスの方で機能を提供してあげないといけません。
抽象メソッドとして宣言する理由としては、派生クラス先で必ず実装をしてほしいのでそれを明確にするためです。
実際の例を見てみましょう。
abstract class AbstractHuman
{
public string Name { get; set;}
public int Age { get; set;}
public AbstractHuman(string name = "アクセス太郎", int age = 30)
{
this.Name = name;
this.Age = age;
}
public abstract void PrintName();
}
class Human: AbstractHuman
{
public override void PrintName()
{
Console.WriteLine($"名前は{this.Name}です");
}
}
抽象メソッドを定義するには、メソッド定義にabstract修飾子を指定するだけです。
抽象メソッドを含んだクラスは、classブロックにもabstract修飾子をつける必要があります。
派生先では、抽象メソッドは必ずOverrideしないといけないため、もしoverrideをし忘れるとコンパイルエラーが発生してしまいます。
Interfaceとは
Interfaceとは、配下のメソッドが全て抽象メソッドであるクラスのことを言います。
abstractクラスでは、フィールドを持つことができますが、Interfaceでは持つことができません。
実際の例を見てみましょう
interface IHuman
{
string Name { get; set; }
int Age { get; set; }
void PrintName();
}
class Human: IHuman
{
public string Name { get; set; }
public int Age { get; set; }
public Human(string name = "アクセス太郎", int age = 30)
{
this.Name = name;
this.Age = age;
}
public void PrintName()
{
Console.WriteLine($"名前は{this.Name}です");
}
}
Interfaceでは、class命令の代わりにInterface命令で宣言をします。
また、クラス名はInterfaceであることを明確にするために頭にIをつけて宣言を行います。
抽象クラスでは、抽象メソッドであることを明確にするためにabstract修飾子をつけてメソッドを宣言しましたが、Interfaceでは全てが抽象メソッドであるために、abstract修飾子を付与しませんししてはいけません。
抽象クラス、インターフェースの違い
抽象クラス
、インターフェースの簡単な実装をみてきましたが、これらの違いはどこにあるでしょうか。
抽象クラスとインターフェースの大きな差は二つあります。
- 抽象クラスは実装を持つことができる
- Interfaceは複数継承できる
1つずつみていきましょう
抽象クラスは実装を持つことができる
抽象クラスは抽象メソッドを持つクラスであり、インスタンス化できないクラスであるため、普通のクラスのように実装を含むことができます。
Interfaceは全てが抽象クラスであるため、実装を持つことができません。
そのため、抽象クラスでは共通化された処理が明確に存在しているが、派生先にて実装が異なるメソッドが存在している場合に用いるのが良いでしょう。
正直あまり使うことは多くないです。
しかし、実装を持てるという点は利点としてあるため、そこを意識して使うと良いでしょう。
Interfaceは複数継承できる
C#においては、クラスの多重継承は許可されていません。
それと比較して、Interfaceは多重継承を許可されています。
オブジェクト指向におけるクラス継承はとてもよくできているのですが、継承の都合上、派生クラスは親クラスの全てを含んでいる必要があります。
そのため、必ずしも派生クラスで必要としない機能に対しても必ずオーバーライドしないといけません。
必要のない機能を実装することは、コードが冗長になり派生クラスの役割がわかりにくくなるため望ましい状態ではありません。
抽象クラスは必要なのか
ここで疑問に思うのがInterfaceが多重継承可能であれば、抽象クラスの抽象メソッドをInterfaceとして継承すれば良いのではないかということです。
例を見てみましょう。
interface IMetaHuman
{
void PrintName();
}
class Human
{
public string Name { get; set; }
public int Age { get; set; }
public Human(string name = "アクセス太郎", int age = 30)
{
this.Name = name;
this.Age = age;
}
}
class MetaHuman : Human, IMetaHuman
{
public void PrintName()
{
Console.WriteLine($"名前は{this.Name}です");
}
}
こういうふうにすれば、継承先で実装が必要な部分はInterfaceとして宣言することで抽象クラスと同様のことを実現できます。
正直なところ、抽象クラスはあまり使いません。
しかし、Interfaceがたくさん存在してくると継承するものが増えてきてしまい、どれを継承するべきなのかわからなくなってくる場合もあります。
そのため、明確に共通化処理が存在しているような場合に関しては、抽象クラスを利用しても良いでしょう。
しかし、基本的に迷った場合はInterfaceを利用すれば良いでしょう。
Interfaceは型階層から独立しているため、特定の機能を割り込ませるのが自由にできます。
どっちにすればわからないという場合はInterfaceを使っておけば(基本的に)問題ありません。
まとめ
今回は、抽象クラスとInterfaceの違いについてまとめてみました。
自分自身最初はどちらを使えば良いかわからなくなったりしましたが、先輩たちの実装をみてもInterfaceを使った実装が多い(abstractは数えられるくらいしかない)ため自分もそれらに倣ってInterfaceで実装をするようにしています。
両方に共通する点としては、実装してほしいメソッドを明確にするという点です。
そこさえ理解しておけば問題はないと考えています。
まだまだ経験不足な点が多く色々指摘いただけると嬉しいです。
オブジェクト指向の利点としてポリモーフィズムがありますが、これはまた別の機会にまとめてみたいと思います。