112
87

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

オブジェクト指向の継承による肥大化を避ける方法

Last updated at Posted at 2020-02-22

オブジェクト指向の特徴としてよく出る継承という仕組み。
便利ではありますが、調子に乗って使うと痛い目を見ます。

継承の例

わかりやすい例としてはゲームのキャラクターで表すとわかりやすいです。

・キャラクターは名前とHPとMPを持っています。また、回復するためのメソッドを持っています。
・ヒーローは必殺技回数を持っています。
・敵は経験値とお金を持っています。

キャラクター
class Character {
  string name;
  int hp;
  int maxHp; // 最大HP
  int mp;
  int maxMp; // 最大MP
  
  // 回復する
  recoveryHp( value ) {
    int hp = this.hp + value;
    if( maxHp < hp){
      this.hp = maxHp;
    }else{
      this.hp = hp;
    }
  }
}
ヒーロー
class Hero extends Character {
  int skillCount;
}
class Enemy extends Character {
  int exp; // 経験値
  int money;
}

Hero と Enemy は Character を継承しています。
こうすることで、Hero も Enemy も名前と hp と mp の仕組みを両方持つことができます。
また、recoveryHp も使うことができます。

hero = new Hero();
hero.name; // 使える
hero.hp; // これも使える

こうすると、Character自体に power(攻撃力) が増えるなど、追加要素があっても継承元の Character クラスを直すだけで、
Hero にも Enemy にも機能が追加できます。

機能を増やそう

例えば、キャラクターの顔アイコンであるサムネイル画像を返す仕組みが必要になったとしましょう。

class character {
  // 
  // 既存の処理
  //
  string iconUrl;

  createThumbnail() {
    binary = download(character.iconUrl);
    image = Image.create(binary);
    return image;
  }
}

このように処理を増やすと、サムネイル画像を取得するメソッドが作れます。
hero でも enemy でも呼び出すだけで、サムネイル画像が取得できます。

image.png

めでたしめでたし。。。

次第にどんどん機能拡張されていく...

ところが次に起こることとして、Characterにどんどん機能が増えていくのです。
例えば、power という力の項目が増えたり、防御力が増えたり必要な項目がどんどん増えます。
メソッドでも、毒状態になり、HPが減っていく。キャラクターボイスを再生するの機能も増えるかもしれません。

これを対応していくと、次第に Character にメソッドがどんどん増えていって、
1000行を超えることになります。

そして神クラスへ・・・

image.png

神と呼ばれるクラスになって、扱いづらくなり、飲み会での愚痴に繋がっていったりしていきます。
こんなの良くないですよね・・・。

ではどうするか

分割していきます。

先程例で示したサムネイル作成の処理は分けられたりします。
以下のようなクラスを作ります。

class ThumbnailCreator {
  static createCharacter(charcter) {
    binary = download(character.iconUrl);
    image = Image.create(binary);
    return image;
  }
  
  static createItem(item) {
    binary = download(character.iconUrl);
    image = Image.create(binary);
    return image;
  }
}

サムネイルを作る処理は ThumbnailCreator に分割できました。
これで Character から処理を省くことができました。
また、ThumbnailCreator には、 Item のサムネイル作成処理など、
サムネイル作成系は全部入れたりできます。
サムネイル作成のための共通的な処理もここに入れておくことができます。

元の Character には、以下の処理だけいれておいても良いです。

class Character {
  createThumbnail() {
    return ThumbnailCreator.createCharacter(this);
  }
}

こうすることで処理を別クラスへ移動できつつ、呼び出し方も変わらない理想的な状態ができます。

同様にボイス再生処理も、 voiceManager などのクラスを作って分割していくことができます。

出来上がりのイメージはこのような感じです。

image.png

このように1クラスを太らせすぎないように、神にならざるクラスとし、
適切に処理を分割していくことが設計において重要です。

まとめ

とりあえず先に開発して、あとでリファクタリング

ではなく、

リファクタリングと新規開発を 50:50 など常に力を配分しておく

そうすることでプロダクトを高品質に保ち続けることができます。

112
87
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
112
87

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?