今回はStateパターン。なかなかエキセントリックなデザインパターンです。
要は
- メソッドの実装をある状態に応じて切り替える(条件分岐させる)
- ある状態というクラスがメソッドの実装を記述する
もう少し概念的な説明すると、
- 普通の条件分岐…『この状態ならこういう処理にする』
- Stateパターン…『この状態がこういう処理をする』
というように、状態が主語になるイメージです。
いつ使うのか。メリットは何か。
使う場面を間違えると却って混乱しそうなStateパターン。自分で例を考えて実装してみて、有効だと感じた点を列挙します。
- どのメソッドにも同じような条件分岐が散らばるほどに重要な『状態』がある時
まあこれは当たり前っちゃあ当たり前。『この状態の時に呼ばれたらこう動く』という説明のメソッドがあったら要チェック。
- 将来状態が増減しそうな時
これも当たり前かな
- 状態を矛盾なく取り扱える
本書でも触れてますが、このパターンを適用する時は各Stateは排反である必要があるので、必竟自己矛盾が起きません。
これはプログラミングする上でとても大事です。『このifはelse ifにしないと動かない』みたいな分かりづらさから解放されます。
- 状態が増えすぎるとしんどい
特にベクトルの違う複数の状態の組み合わせだと、状態数が指数的に増えるおそれがある
今回は完全オリジナル。どこかのブログのパクリで、『マリオでStateパターン』です。
1ターンにランダムで
- きのこを食べる
- フラワーを食べる
- スターを食べる(スターは3ターンで切れる)
- 敵に当たる
- 穴に落ちる
のなかから一つの行動をします。State(状態)は
- チビマリオ
- デカマリオ
- フラワーマリオ
- 上記3種のスター状態
の計6種類です。
これだと、例えば『しっぽマリオを追加する』なんて時にSippoMarioStateクラスとStarSippoMarioStateクラスを追加するだけで実現できてしまいます(他所はいじる必要なし!)。Stateパターンの有効なケースですね。
実行結果
[残5:チビマリオ] スターを食べた! チビマリオ★ になった! [残5:チビマリオ★] ... [残5:チビマリオ★] キノコを食べた! デカマリオ★ になった! [残5:デカマリオ★] スターが切れた! デカマリオ になった! [残5:デカマリオ] スターを食べた! デカマリオ★ になった! [残5:デカマリオ★] 敵に接触! 敵を倒した! [残5:デカマリオ★] スターを食べた! デカマリオ★ になった! [残5:デカマリオ★] 穴に落ちた! 死んだ! 残り残機:4 [残4:チビマリオ] キノコを食べた! デカマリオ になった! [残4:デカマリオ] ... [残4:デカマリオ] 穴に落ちた! 死んだ! 残り残機:3 [残3:チビマリオ] ... [残3:チビマリオ] フラワーを食べた! デカマリオ になった! [残3:デカマリオ] スターを食べた! デカマリオ★ になった! [残3:デカマリオ★] フラワーを食べた! ファイアーマリオ★ になった! [残3:ファイアーマリオ★] フラワーを食べた! 意味はない! [残3:ファイアーマリオ★] スターが切れた! ファイアーマリオ になった! [残3:ファイアーマリオ] ... [残3:ファイアーマリオ] 穴に落ちた! 死んだ! 残り残機:2 [残2:チビマリオ] フラワーを食べた! デカマリオ になった! [残2:デカマリオ] 敵に接触! チビマリオ になった! [残2:チビマリオ] 穴に落ちた! 死んだ! 残り残機:1 [残1:チビマリオ] キノコを食べた! デカマリオ になった! [残1:デカマリオ] スターを食べた! デカマリオ★ になった! [残1:デカマリオ★] フラワーを食べた! ファイアーマリオ★ になった! [残1:ファイアーマリオ★] キノコを食べた! 意味はない! [残1:ファイアーマリオ★] スターが切れた! ファイアーマリオ になった! [残1:ファイアーマリオ] フラワーを食べた! 意味はない! [残1:ファイアーマリオ] 敵に接触! チビマリオ になった! [残1:チビマリオ] フラワーを食べた! デカマリオ になった! [残1:デカマリオ] 敵に接触! チビマリオ になった! [残1:チビマリオ] 敵に接触! 死んだ! 残り残機:0 [残0:チビマリオ] 穴に落ちた! GAME OVER 30ターンプレイしました
ソースコード
#!/usr/bin/env python # -*- coding: utf8 -*- class __MarioState: def __init__(self): raise NotImplementedError def doGetKinoko(self, context): raise NotImplementedError # キノコ取得 def doGetFlower(self, context): raise NotImplementedError # フラワー取得 def doGetStar(self, context): raise NotImplementedError # スター取得 def doLostStar(self, context): raise NotImplementedError # スター消滅 def doEncounter(self, context): raise NotImplementedError # 敵に接触 def doFall(self, context): raise NotImplementedError # 穴に落ちる def __str__(self): raise NotImplementedError class NormalMario(__MarioState): def __init__(self): pass def doGetKinoko(self, context): context.changeState(BigMario()) def doGetFlower(self, context): context.changeState(BigMario()) def doEncounter(self, context): context.dead() def doGetStar(self, context): context.changeState(StarNormalMario()) def doLostStar(self, context): print "bug" def doFall(self, context): context.dead() def __str__(self): return "チビマリオ" class BigMario(__MarioState): def __init__(self): pass def doGetKinoko(self, context): context.nope() def doGetFlower(self, context): context.changeState(FlowerMario()) def doEncounter(self, context): context.changeState(NormalMario()) def doGetStar(self, context): context.changeState(StarBigMario()) def doLostStar(self, context): print "bug" def doFall(self, context): context.dead() def __str__(self): return "デカマリオ" class FlowerMario(__MarioState): def __init__(self): pass def doGetKinoko(self, context): context.nope() def doGetFlower(self, context): context.nope() def doEncounter(self, context): context.changeState(NormalMario()) def doGetStar(self, context): context.changeState(StarFlowerMario()) def doLostStar(self, context): print "bug" def doFall(self, context): context.dead() def __str__(self): return "ファイアーマリオ" class StarNormalMario(__MarioState): def __init__(self): pass def doGetKinoko(self, context): context.changeState(StarBigMario()) def doGetFlower(self, context): context.changeState(StarBigMario()) def doGetStar(self, context): context.changeState(StarNormalMario()) def doLostStar(self, context): context.changeState(NormalMario()) def doEncounter(self, context): context.defeat() def doFall(self, context): context.dead() def __str__(self): return "チビマリオ★" class StarBigMario(__MarioState): def __init__(self): pass def doGetKinoko(self, context): context.nope() def doGetFlower(self, context): context.changeState(StarFlowerMario()) def doGetStar(self, context): context.changeState(StarBigMario()) def doLostStar(self, context): context.changeState(BigMario()) def doEncounter(self, context): context.defeat() def doFall(self, context): context.dead() def __str__(self): return "デカマリオ★" class StarFlowerMario(__MarioState): def __init__(self): pass def doGetKinoko(self, context): context.nope() def doGetFlower(self, context): context.nope() def doGetStar(self, context): context.changeState(StarFlowerMario()) def doLostStar(self, context): context.changeState(FlowerMario()) def doEncounter(self, context): context.defeat() def doFall(self, context): context.dead() def __str__(self): return "ファイアーマリオ★" class __Context: def __init__(self): raise NotImplementedError def changeState(self, mariostate): raise NotImplementedError # 状態が変化した def defeat(self): raise NotImplementedError # 敵を倒した def dead(self): raise NotImplementedError # 死んだ def nope(self): raise NotImplementedError # 何も起きない import sys from threading import Timer class Game(__Context): def __init__(self): self.state = NormalMario() self.zanki = 5 # 残機 self.turncount = 0 # 何ターン目か self.starcount = 99999 # スター用カウンタ def changeState(self, mariostate): self.state = mariostate print str(mariostate)+" になった!" def defeat(self): print "敵を倒した!" def dead(self): self.zanki -= 1 if self.zanki < 0: print "GAME OVER" print "%dターンプレイしました" % self.turncount sys.exit(0) print "死んだ! 残り残機:%d" % self.zanki self.state = NormalMario() self.starcount = 99999 def nope(self): print "意味はない!" #-- def youGetKinoko(self): print "[残%d:%s] キノコを食べた! " % (self.zanki, self.state), self.state.doGetKinoko(self) def youGetFlower(self): print "[残%d:%s] フラワーを食べた! " % (self.zanki, self.state), self.state.doGetFlower(self) def youEncounter(self): print "[残%d:%s] 敵に接触! " % (self.zanki, self.state), self.state.doEncounter(self) def youFall(self): print "[残%d:%s] 穴に落ちた! " % (self.zanki, self.state), self.state.doFall(self) def nothingHappened(self): print "[残%d:%s] ..." % (self.zanki, self.state) def youGetStar(self): print "[残%d:%s] スターを食べた! " % (self.zanki, self.state), self.state.doGetStar(self) self.starcount = 0 def __youLostStar(self): print "[残%d:%s] スターが切れた! " % (self.zanki, self.state), self.state.doLostStar(self) def youCountTurn(self): self.turncount += 1 self.starcount += 1 if self.starcount == 3: self.__youLostStar() from time import sleep from random import randint def main(): m_game = Game() while True: # random action m_game.youCountTurn() act = randint(1,6) if act==1: m_game.youGetKinoko() elif act==2: m_game.youGetFlower() elif act==3: m_game.youGetStar() elif act==4: m_game.youEncounter() elif act==5: m_game.youFall() elif act==6: m_game.nothingHappened() sleep(0.1) if __name__=='__main__': main()
0 件のコメント:
コメントを投稿