「で、結局オブジェクト指向って何が良いわけ?」という手続き型脳の貴男へ


オブジェクト指向言語が普及してかなりの年月が経ちますが、いまだにオブジェクト指向って何が良いのかよく分かんない・・・という方をたまに見かけます。彼らなりにネット等の情報を漁っているようですが、どれもイマイチ腑に落ちない様子。

「やれカプセル化とかポリモフィズムとか、もう意味不明。結局何かメリット有るの?

彼らのPCからVisual Basic 6をアンインストールするためにも、今回は「これ1つだけ覚えといて」っていうオブジェクト指向のメリットを紹介してみようと思います。

そのメリットというのは・・・

呼び出しを共通化できるのがオブジェクト指向

ということです、はい。これだけ覚えといて下さい。

しかし、あなたが手続き型言語の猛者ならば、きっとこう叫ぶに違いありません。

「おみゃあ、何言っとんじゃあ? 呼び出しの間違いじゃろうがあ!」

「他所様から呼び出される関数を共通関数にまとめる。これで決まりじゃろうがあ?」

「同じ呼び出し元から別々の関数を呼び出すっちゅうのか、お前さん? ・・こりゃあ傑作じゃあ!」

共通関数(笑)。散々なじられましたが、それも仕方ありません。それこそが手続き型→オブジェクト指向のパラダイム・シフトなのですから。

例として登録画面が複数あるアプリケーションを想定してみましょう。手続き型処理の大まかな流れは、以下のようになると思います。

Untitled

手続き型の共通処理は受動的です。必要なタイミングで他所様から呼び出してもらう必要があるということです。この実装モデルには次のようなデメリットがあります。

  1. 呼び出し方・タイミング等を間違えてしまう可能性がある
  2. もしくは呼び出すことを忘れてしまう可能性がある
  3. 上記のようなミスを犯さないよう、コーディング規約やコメント等の人間系運用で周知を徹底する必要がある

私は特に上記3が一番の問題ではないかと思います。ちょっとした規模のアプリケーションになると、それなりの画面数になるため、当然共通化すべき事項も多くなるはずです。それらが人間系運用で担保しきれるかというと、非常に難しい、もしくは相応のコストがかかると言うしかありません。

一方で、呼び出し元を共通化するオブジェクト指向では、先の図は次のようになります。

オブジェクト指向

共通処理から各画面の個別処理を能動的に呼び出す形になっています。ポイントは赤字で書いてある通り、個別の処理を実装しないとコンパイルエラーになるということです。必要な個別処理だけ実装を強制することができます。

このオブジェクト指向の実装モデルを、かなり都合よくデフォルメしたJavaコードで見てみましょう(Javaが分からない方でも意図は分かると思います)。まずは共通処理からです。

📄共通処理.java
abstract class 共通処理 {

  //登録ボタンがクリックされた場合に呼び出される
  public void 登録() {
    if (入力チェック()) {
      try {
        トランザクション開始();
        個別登録処理();
        コミット();
      } catch (Exception e) {
        ロールバック();
      }
    } else {
      入力エラーメッセージ表示();
    }
  }

  abstract protected boolean 入力チェック();
  abstract protected void 個別登録処理();

  protected void トランザクション開始() {
    //トランザクション開始処理をここに実装
    //...
    //...
  }
  protected void コミット() {
    //コミット処理をここに実装
    //...
    //...
  }
  protected void ロールバック() {
    //ロールバック処理をここに実装
    //...
    //...
  }
  protected void 入力エラーメッセージ表示() {
    //入力エラーメッセージを表示する処理をここに実装
    //...
    //...
  }
}

登録メソッドは、このアプリケーション全ての画面に共通の登録処理です。まず入力チェックを行い、エラーが無ければトランザクション処理を実行するというベタベタなパターンです。トランザクションに関する処理(開始・コミット・ロールバック)は共通なので、このソースファイルに実装しています。逆に各画面で個別に実装しなければいけないメソッドはここでは実装しません。それを指示しているのが

📄抽象メソッド定義
  abstract protected boolean 入力チェック();
  abstract protected void 個別登録処理();

この2行です。詳しい説明は省きますが、これが「おめーら、自分で実装しねえと、鮫島先輩にコンパイルエラーにしてもらっからな」というヤキ入れ印になってます。

続いて、個別画面のソースを見てみましょう。

📄登録画面A
class 登録画面A extends 共通処理 {

  protected boolean 入力チェック() {
    //画面Aの登録処理をここに実装
    //...
    //...
  }

  protected void 個別登録処理() {
    //画面Aの登録処理をここに実装
    //...
    //...
  }
}

見事に個別処理だけのソースコードですね。共通部分との差分のみを実装すればよいのです。仮にこの各画面の実装を後輩君やパートナーさん、オフショア先なんかにお任せしたとしましょう。彼らは個別処理の実装に集中できますし、あたりまえの共通処理に関与しなくて済みます。

もしこれが手続き型言語で実装されていたとしたら、

  • 各画面に登録メソッド(上記 共通処理クラスの)が乱立することになり
  • たまにトランザクション処理を呼び出し忘れる輩が現れ
  • 不運にもそれがコピペで大量生産されていき
  • やがてアプリケーションは世紀末状態に・・・

なんてことになるでしょう。いや、まじで。その傍らでソース管理者が激おこぷんぷん状態で

激おこ

これでは誰も幸せにはなれません。

一方、オブジェクト指向は

  • 共通処理のフロー(この例だと登録メソッド)は1箇所にしか定義していないので、変更が簡単だし
  • 個別の画面には影響を与えないし
  • 個別の処理の実装を人間系ではなく、言語的に強制できる

というメリットがあります。これなら幸せになれそうだと思いませんか?

というわけで、オブジェクト指向の『呼び出し元を共通化する』という意味が分かって頂けたでしょうか。実は1点だけまだ触れていないことがあります。それは登録画面Aクラスの最初の1行です。

📄登録画面A.java
class 登録画面A extends 共通処理 {

このextends 共通処理です。これが共通処理クラスを継承してサブクラスを作れという命令なのです。ええ、あの『継承』です。憧れの継承。この1点さえ守っていれば、親クラスの機能を利用することができるのです。

なんだ、このルールを守らないとダメなんじゃないか。それじゃあ、手続き型と一緒で継承し忘れるかもしれないじゃないか、と思う方がいるかもしれません。しかし人間は、ルールを逸脱するメリットが無ければルールを順守するもんです。仮に継承しないと、データベース接続処理やらトランザクション処理やら入力エラーを表示する処理やらアレやらコレやらを自力で実装しないといけなくなります。清く正しくズボラなプログラマーは、こんな割に合わないことはしません。秩序は保たれるのです。

抽象化するということ

実装をプログラミングするというのは具体的なアプローチですが、アプリケーションというものは、まずは抽象的に捉えるべきものです。まー大雑把に言うと、虫食い問題を作るイメージでしょうか?

括弧の中を実装しなさい

虫食い問題

虫食い問題を個別の実装者に解いてもらうイメージです(ちなみに上記のようなパターンをTemplate Methodパターンとか言ったりします)。管理者なり基盤チームなりSIerさんなりフレームワーク作っちゃったりする人たちは、具体的な実装は置いといて、抽象的な枠組みを設計することに専心できるのです。

最後に

何となくオブジェクト指向で作るメリットがお分かり頂けたでしょうか? ここで紹介しているのはオブジェクト指向のほんの一部分に過ぎませんが、私はこれこそがオブジェクト指向で作る最大のメリットだと思ってます。コレを皮切りに、是非ともオブジェクト指向言語にチャレンジしていただければ、あー記事書いた甲斐があったなあ、と感慨ひとしおです。

なお本内容、昔読んだ↓この本に↓インスパイアされました。興味がある方はぜひ読んでみてください。

関連する記事