※本記事はあくまでゲームの内部設計に関する考察です。実際のゲームとは関係ありません。
よくある光景
よくあるゲームのプレゼントボックスを考えます。
アイテム名 | 取得理由 | 受け取り期限 |
---|---|---|
薬草 × 10 | ダンジョン4のクリア報酬です | あと100日 |
魔石 | 4日目のログインボーナスです | あと100日 |
10000ゴールド | 6日目のログインボーナスです | あと99日 |
薬草 × 5 | ダンジョン3のクリア報酬です | あと99日 |
5000ゴールド | 3日目のログインボーナスです | あと96日 |
戦士キータ | 1日目のログインボーナスです | あと93日 |
item_type によってアイテムやお金等の区別をします。
仮にこうなっているものとします。
item_type | 内容 |
---|---|
1 | お金 |
2 | アイテム |
3 | キャラ |
4 | 魔石 |
これを受け取る時(プレゼントボックスから手持ちに移す時)のコードはこうなるでしょうか。
def acquire_present(user, present):
if present.item_type == 1:
"""お金を受け取る処理"""
elif present.item_type == 2:
"""アイテムを受け取る処理"""
elif present.item_type == 3:
"""キャラを受け取る処理"""
...
else:
raise Exception()
if文地獄です。
しかもそれぞれの受け取り処理で色々やる可能性大です(手持ち確認とかログとか)。
そうなると acquire_present関数とそれぞれのデータクラスでがっちり結合度が高まります。
さらに今後のゲームの拡張によってitem_typeが増えればさらに長くなります。
長いif文と密結合はメンテナンス性がよろしくないのでなんとかしたいです。
受け取り処理の分離
そこでプレゼントの受け取り処理を委譲するクラスを考えます。
class AqruireDalegatorBase(object):
"""受け取り処理委譲ベースクラス"""
@classmethod
def acquire(user, present):
raise NotImplementedError()
受け取りの共通インターフェースを定義しました。
継承してそれぞれの受け取り処理委譲クラスを実装します。
class MoneyAqruireDalegator(AqruireDalegatorBase):
"""お金の受け取り処理委譲クラス"""
@classmethod
def acquire(user, present):
user.add_money(present.item_quantity)
class ItemAqruireDalegator(AqruireDalegatorBase):
"""アイテムの受け取り処理委譲クラス"""
@classmethod
def acquire(user, present):
user.add_item(present.item_id, present.item_quantity)
... その他に続く
実際に受け取り関数に組み込んでみます。
def acquire_present(user, present):
if present.type == 1:
MoneyAqruireDalegator.acquire(user, present)
elif present.type == 2:
ItemAqruireDalegator.acquire(user, present)
elif ...
それぞれのタイプごとの受け取るロジックを切り出したことで疎結合感が出てきました!
拡張も楽になるようにしてみる
でもまだif文が長くなるしタイプが追加される度にこの関数を修正しないといけないので、
DELEGATOR_MAP = {
1: MoneyAqruireDalegator,
2: ItemAqruireDalegator,
...
}
とタイプと委譲クラスのマッピングをします。
def acquire_present(user, present):
delegator_class = DELEGATOR.get(present.item_type)
delegator_class.acquire_present(user, present)
かなり短くなりました!
そしてこれでタイプが増えた場合でも、
- AqruireDalegatorBaseを継承した受け取り委譲クラスを実装する
- DELEGATOR_MAPに追加する
で拡張可能です。acquire_present 関数を修正する必要はありません!
テストも追加した委譲クラス分だけすればOKです!
まとめ(?)
長いif文は不具合調査時も拡張時も厄介なので疎結合な感じになっていると現在と未来の人達に優しいですね!
(これはファサードパターンなのだろうか・・・?若干DIっぽい気もする)