なぜ抽象メソッドを使うのか

スポンサーリンク

対象読者

  • 実装のない抽象メソッドを使う意味が分からない方

結論

空っぽの抽象メソッドが無駄に見える

抽象メソッドとは、引数や戻り値が決められただけの中身がないメソッドのことだ。空っぽ故に、抽象メソッドは継承先で上書き(オーバーライド)されることが前提となっている。

抽象メソッドを持つクラスを「抽象クラス」と呼ぶ。

私はオブジェクト指向初学者の頃、抽象メソッドを使う意味が分からなかった。だって、抽象メソッドには中身がないから。。。

抽象クラスを継承しても結局はその中身を実装しなければならない訳だ。クラスを余分に作らなければならないし、無駄にコード量を増やすだけではないか?

抽象クラスなんて用意せずに、それぞれのクラスで同じ名前、同じ引数のメソッドを実装すれば同じことやんけ。

そう思ってた時期が私にもありました、はい。

しかし、今では抽象メソッドをよく使うようになった。

クラスが太っていく…

クラスの第一の意義は、関連する変数と関数をまとめることによって、コードをスッキリさせることだ(詳しくはこちら)。大量のファイルをフォルダ分けすると管理しやすくなることと同じだ。

これだけでもクラスを使うメリットはある。しかし、オブジェクト指向の豊かな表現力を発揮するには不十分である。なぜなら、関連をまとるという視点だけでは、意味合い的には無関係なものをクラス内に押し込んでしまう恐れがあるからだ。

コードをクラスに分割することで可読性・柔軟性を高められる。それらのクラスを組み合わせて動作するコードを作成することがオブジェクト指向のやり方だ。しかし、クラスを組み合わせることと関連は混同されがちだ。

例えば、クラスAとクラスBを組み合わせてある動作を実現するとしよう。組み合わせ方は実に多様だ。

//後で考える

このように、1つのクラスは肥大化していき、遂には多様なプロパティやメソッドを兼ねそろえた便利クラスが誕生する。これは「神クラス」と呼ばれる。そう呼ばれる理由は以下だ。

  • そのクラスは全知全能である
  • そのクラスは複雑すぎて、神にしか理解できない

こうして、コード分離の仕組みであったはずのクラスが本来の役割を果たさなくなる。大量のグローバル変数と関数が羅列された「手続き型プログラミング」の世界へと逆戻りしていく。

単一責任の原則

神クラスにならないように、クラスをシャープに保つ必要がある。そのためには、より積極的にクラスへ意味付けを行い、関連とクラス同士の組み合わせを区別しなければならない。そこで、クラスの構成要素(メンバ)を限定するための考え方が「責務」だ。

責務(Responsibility)と言うと難解な印象を受けるが、要は応答(Response)する能力(Ability)のことだ。役割や担当とも言い換えられる。

君、これやってくれない メソッド呼び出し

責務は要求に対して、特定の

逆に、この仕事はオレの担当ではないってことが大事だ。

プログラミングの要諦は、変更されやすい部分とされにくい部分を分けることにより、変更を容易にすることだ。責務(ある役割を全うする義務)は変更されにくい。

プログラムの目的を達成するための手段にはバリエーションがあり、変更され易い。しかし、その目的を達成するための役割については変更が少ない。

例えば、事務作業はIT化が進んでいっても残り続ける作業だろう。しかし、その作業は自動化されつつある。ヒトによる手動の作業から、ソフトウェアによる処理に置き換わりつつある。この場合、具体的な手続きは変化しているが、事務という役割は不変である。

このように、具体的な手続きよりも高次なものが責務なのだ。責務をプログラムの設計の中心に据えることで、より変更に強いプログラミングが実現できる(責務駆動開発)。

制約によるプログラミングの進化

制約を設けることにより、プログラミングは進化してきた。以下に例を列挙する。

  • 定数は変数の更新を禁止することにより、より安全にプログラミングを行えるようにする。
  • gotoレスプログラミングはgoto文を禁止し、構造化文によりプログラムを表現することで可読性に優れたプログラミングを実現する。
  • 関数型プログラミングは副作用を禁止することにより、バグを排除しやすくする。

抽象メソッドによる制約は、サブクラスのメソッドのインターフェイスを固定することである。この制約により、そのサブクラスを使用する際、振舞いが予測できるため、安心して仕事を依頼することができる(ポリモーフィズム)。

抽象メソッドによる制約は責務を表現する

クラスの責務は振舞い(メソッド)によって達成される。料理をしない料理人なんていない。デュエルしない者はデュエリストではない。クラスが何者なのかは行動(メソッド)によって規定できる。

抽象クラス(抽象メソッドを持つクラス)はサブクラスに抽象メソッドの実装を強制することにより、サブクラスに確実に責務を負わせる。こうしてエセデュエリストが存在する可能性をなくすことができる。

抽象クラスを使わずに、クラス名(○○Visualizerとか)によっても、責務を表現することはできる。それぞれのVisaulizerに共通のインターフェイスを持つメソッド(visualize)を実装することでも、抽象メソッドを実装した場合と同じことができる。

しかし、名前によって責務を表現しただけでは、本当に責務を果たすようなメソッドを持っているかは保証できない。プログラマの判断に依存してしまうからだ。抽象メソッドを使えば、プログラムの制約によって、確実にクラスに責務を負わせることができる。visualizeしてくれないVisualizerは存在する余地がなくなる。これが結論で書いた「抽象メソッドは責務(役割)を表現する」の趣旨だ。

ポリモーフィズム(多態性)とは

ポリモーフィズムは、サブクラスのカプセル化である。同じインターフェイスのメソッドを持つサブクラスならば、どれでも同様に動作させることができる。ポリモーフィズムを実現したクラス群に対しては、どのサブクラスなのか具体的に知る必要はない。故に、制御プログラムの変更なしに、サブクラスを置き換えることができる。

現実世界から見るポリモーフィズムの例

レストランの責務は、顧客に料理を提供することだ。具体的な料理や調理方法は変更されやすいが、料理を提供するという点は変更はない。

料理を提供する責務を持つ抽象クラス(料理人)には,「料理を作る」、という抽象メソッドをとりあえず持たせておく。このメソッドは、材料を引数として渡すと、料理を返すというインターフェイスを持つとする。

具体的な料理人はプロの場合もあるし、業界未経験のアルバイトかもしれない。その調理方法(実装)は超絶テクニックによる調理かもしれないし、レンジでチンかもしれない。

抽象クラス(料理人)を継承しているならば、具体的な方法は分からないが、必ず料理を作ることが保証される(ポリモーフィズム)。

参考

  1. オブジェクト指向のこころ[アラン・シャロウェイ]
  2. コーディングを支える技術[西尾 泰和]
  3. オブジェクト指向でなぜつくるのか[平澤 章]

    コメント

    タイトルとURLをコピーしました