逆転の発想でオブジェクト指向の「継承」を使いこなす

2020/04/27

エンジニアの平山です。
前回の記事ではオブジェクト指向のカプセル化について書きました。
今回はオブジェクト指向の「継承」について、私の経験も踏まえお話いたします。

そもそも「継承」とは?

Wikipediaの説明では以下のように記載されています。

既存オブジェクトのデータ構成とメソッド構成を引き継いで、新しい派生オブジェクトを定義する仕組みが継承と呼ばれる。
引き継ぐ際には新たなデータとメソッドを自由に追加できるので、派生オブジェクトの構成は既存内容+追加内容になる。

また、その他の一般的な継承の説明も以下のような表現が多いです。
・あるクラスの機能を引き継いで、新しいクラスを作成すること
・クラス定義の共通部分を別クラスにまとめる仕組み

クラスに親子関係を築き、親の性質を子に継承させることができるということで、処理の共通化を行う(プログラムの冗長化を防ぐ)目的でよく使われます。

ただ、これをそのまま実装すると「親クラスに共通処理を作る」という風に使ってしまいがちで、「継承」の本質とは外れています。
その結果、メンテナンス性の悪いプログラムが出来上がってしまうことも少なくありません。

「継承」の本質とは?

私も含め多くの方は最初のプログラミングで処理を一つのメソッドの中にすべて入れるいわゆる「べた書き」からスタートすると思います。
その後メソッド化(処理の切り分け)を覚え、処理を切り出したり、切り出した処理を共通的に使う(処理の共通化)というステップを踏みます。

その延長線上でオブジェクト指向の「継承」を考えてしまうと「処理の共通化」として使ってしまうのはある意味自然な流れといえます。

ですが、継承の本質は「親クラスに処理の流れを作る」というところにあると私は考えています。
この考えに至るには従来の共通処理のイメージから脱却し、「逆転の発想」が必要です。

親クラスに処理の流れを作る

これは順に処理を行って共通化できるところをくくり出す(呼び出し先を共通化する)という従来の手続き的な考え方ではなく、順に処理を行うところを共通化(呼び出し元を共通化する)という逆転の発想です。

以前のMVCの記事で使ったECサイトでDVD、衣類、書籍を販売するために、商品情報を表示する機能のModelを例に比較します。
クラス図で示したいと思いますが、その前に処理を整理しておきます。
参考記事:クラス図(IT専科)

ModelはControllerからpublicメソッド(データ取得)をコールされて処理が実行されます。
処理の流れは、以下のような動きを想定します。
1.データベース接続
2.SQL作成、クエリ実行
3.Viewデータに保存
4.データベース切断

以下のクラス図はこれらの機能を実装したものをイメージしています。

なぜこれが良いのでしょうか。
それには大きく3つのポイントがあります。
・リスクを抑える
・機能追加を容易にする
・分岐の削減

上記のポイントを詳しく説明します。

リスクを抑える

修正のパターンとしては大きく2つ考えられます。
・SQLの変更やViewの変更等による局所的な修正
・チェック処理やエラー処理の追加など処理の制御に対する修正

このうちの前者の局所的な修正はどちらでも影響範囲は小さく、修正も容易なことが多いと思います。
しかし、後者の処理の制御を修正するような場合はどうでしょうか。
従来の共通処理のイメージだと「DVDデータModel」、「衣類データModel」、「書籍データMode」のすべての「データ取得」メソッドに修正が発生します。
しかもメインである処理の制御部分を変更することになるため、当然テストもやり直しになり、影響がとても大きくなります。(リスク大)

対して「親クラスに処理の流れを作る」のイメージでは「商品データModel」の「データ取得」メソッドのみの修正で対応できます。
それによりテストも最小限で済むため、対応工数が3分の1になります。(リスク小)

「そんな大きな修正が頻繁に発生するの?」と思う方もいるかもしれませんが、実際の現場ではもっと機能が複雑で処理の制御も増えます。
処理の制御が増えれば不整合も起きやすくなり、思わぬエラーが発生する可能性も大いにあります。
そのため、例で挙げたチェック処理やエラー処理等は結合テストなど下流フェーズで不備が発見されたり、他の機能と動きを統一するために修正されることも少なくありません。
確かに局所的な修正と比べれば発生頻度は少ないですが、1回の修正がとても大きな影響となり修正が「足踏み」してしまう場面もあります。
リスクを最小限に抑え、発生しても迅速に対応できるというのはかなり大きなメリットになります。

機能追加を容易にする

例えば商品として新たに「家具」や「家電」、「PC」などラインナップを増やすことを想定してみます。
従来の共通処理のイメージでも「親クラスに処理の流れを作る」のイメージでも「DVDデータModel」をコピーし、「SQL作成」メソッドを書き換えることになるでしょう。
ただし、従来の共通処理のイメージの場合「データ取得」メソッドは3つから6つに冗長化していき、テストも追加した3クラスは全ての仕様を網羅する必要があります。
さらに機能が複雑になればなるほどテストの工数が膨らみ、商品が増えた分「リスクを抑える」で挙げたようなリスクもどんどん膨れ上がっていきます。

対して「親クラスに処理の流れを作る」のイメージでは最低限必要となる個別の「SQL作成」メソッドのみの作成で済みます。
これであれば「SQLが正しく作成されたか」という観点だけテストすれば良いため、対応工数もかなり抑えられます。
機能追加をしても他の機能に影響を与えずかつ最小限の対応で済ませられるという点は運用保守ではとても重要なポイントです。

分岐の削減

従来の共通処理のイメージにある「商品データModel」の「Viewデータに保存」メソッドはViewが共通のため、処理を共通化できるという前提があります。
しかし、実際には商品の違いによって異なるデータの設定や編集を行いたい箇所が後になって出てくるという状況は往々にしてあり得ます。
そういった場合の対応として大きく以下の2つがあると思います。
1:「DVDだったら」というような分岐を作り、処理の振り分ける。
2:「商品データModel」の「Viewデータに保存」メソッドを子クラスに移し、共通部分はコピペまたはメソッドに切り出して共通化して個別部分を実装する。

「1」の方法
この方法が一番単純で修正も楽です。
しかし、顧客からの要望等の対応により仕様が膨らんで複雑化するリスクがあります。
そうなった場合は小さな分岐だった処理がいつしか大きな分岐になり、共通化する意味がなくなってきてしまいます。
当然修正したメソッドはテストする必要があるため、軽微な修正でもテストの工数が膨れ上がります。

「2」の方法
この方法は「1」に比べてかなり手がかかります。
クラス構造に手を入れることになり、修正ステップ数も多くなります。
さらにそれに伴いテストをやり直す箇所も多くなるため、非常に工数がかかります。
要求内容によっては「見合わない」と判断されることも多いでしょう。
ただ、手がかかるものの「1」のようなリスクは抑えられるため、メリットもあります。
「最初からそういう構造にしておけば良い」という考えもあるかもしれませんが開発当初が「Viewは共通」という前提である以上余分なメソッドを作っておくという選択は実際には難しいのではないでしょうか。

「親クラスに処理の流れを作る」の場合
対して「親クラスに処理の流れを作る」の場合は各子クラスに「Viewデータに保存」メソッドを実装(オーバーライドと言います)して、個別のデータ設定や編集の部分だけ行えば良いです。
修正ステップも最小かつ分岐なしで済みますし、親クラスに影響を与えないのでテストも実装した部分(Viewに正しくデータが保存されているか)の観点だけで良いです。
これにより「1」のリスクを避けて「2」の対応工数問題も抑えられます。

このようにあらゆる場面において「親クラスに処理の流れを作る」継承は効果を発揮します。
どれも製造段階ではなく、その後のフェーズや運用保守での効果が大きいものですが、私の経験上でもこの差はかなり大きく間違いなく実感できるくらいの効果があります。

過去の達人達もオススメしている

ご存知の方も多いかと思いますが、「デザインパターン」というオブジェクト指向のプログラミングについて、達人達が残した「ベストプラクティス」があります。
以下はWikipediaより引用したデザインパターンについての過去の達人の言葉です。

コンピュータのプログラミングで、素人と達人の間では驚くほどの生産性の差があり、その差はかなりの部分が経験の違いからきている。
達人は、さまざまな難局を、何度も何度も耐え忍んで乗り切ってきている。
そのような達人たちが同じ問題に取り組んだ場合、典型的にはみな同じパターンの解決策に辿り着く。
これがデザインパターンである。

このデザインパターンの中に「Template Method パターン」というものがあり、これが正に今回説明した「親クラスに処理の流れを作る」というものです。
さらにTemplate Methodパターンはabstract(抽象化)を使い、子クラスに実装を強制する事で実装漏れを防ぎ、プログラムの統一性を保たせる効果もあります。
WEB上でもデザインパターンについて紹介している記事は多数ありますので、是非調べて挑戦してみたください。
参考記事:デザインパターン(TECHSCORE)

私も幾度となく失敗し、難局を耐えてきましたが、今回紹介した方法を取り入れることで、いつまでたっても落ち着かない状況から脱却し、下流フェーズに進むにつれてどんどん楽になっていくのがわかりました。
また、MVC、カプセル化、ポリモーフィズムなどその他の技術とも組み合わせる事でさらに効果が上がります。
まだ効果を実感していない方は是非実際の現場で使って、最終的には使いこなせるようになって欲しい技術です。

この記事を書いた人について

平山 和昭
平山 和昭
オーシャン・アンド・パートナーズ株式会社 システムエンジニア

顧客をリードし、最適なシステムを構築、提供できる技術者を目指しています。
コラムでは若手技術者向けを中心に今までの経験を踏まえた実際の開発現場で役立つ情報を発信していきます。
プライベートでは家族の足(専属運転手)となり、今ではかねてからやりたかった日々の送り迎えが日課になっています。