標籤:記錄 磁碟 開放 地址 不用 pat design 目的 功能
github地址:https://github.com/cheesezh/python_design_patterns
題目
用代碼類比以下情境,一個遊戲角色有生命力,攻擊力,防禦力等資料,在打Boss前後的資料值會發生變化,如果玩家挑戰Boss失敗,我們允許玩家可以將遊戲資料恢複到與Boss決鬥之前的狀態。
基礎版本
class GameRole(): def __init__(self): self.vitality = 0 self.attack = 0 self.defense = 0 def state_display(self): print("當前角色狀態:") print("體力:",self.vitality) print("攻擊:",self.attack) print("防禦:",self.defense) def get_init_state(self): """ 從伺服器或者本地磁碟載入初始狀態 """ self.vitality = 100 self.attack = 100 self.defense = 100 def fight(self): """ 與Boss決鬥,資料損耗為0 """ self.vitality = 0 self.attack = 0 self.defense = 0 def main(): mario = GameRole() mario.get_init_state() # 大戰Boss前,擷取初始角色狀態 mario.state_display() backup = GameRole() # 儲存遊戲進度 backup.vitality = mario.vitality backup.attack = mario.attack backup.defense = mario.defense mario.fight() # 大戰Boss mario.state_display() mario.vitality = backup.vitality # 讀取進度,重新來過 mario.attack = backup.attack mario.defense = backup.defense mario.state_display() main()
當前角色狀態:體力: 100攻擊: 100防禦: 100當前角色狀態:體力: 0攻擊: 0防禦: 0當前角色狀態:體力: 100攻擊: 100防禦: 100
點評
主要問題在於用戶端調用:
- 遊戲角色細節暴露給了用戶端,用戶端需要知道遊戲角色的生命力,攻擊力,防禦力等細節;
- 用戶端還要對遊戲角色進行備份;
- 如果以後增加“魔法值”或者修改現有資料,都要更改用戶端;
- 恢複角色的過程也存在同樣問題;
備忘錄模式
備忘錄模式,在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外儲存這個狀態。這樣以後就可將該對象恢複到原先儲存的狀態。主要包括三種類:
- 發起人(Originator):負責建立一個備忘錄Memento,用以記錄當前時刻它的內部狀態,並可使用備忘錄恢複內部狀態。Originator可以根據需要決定Memento儲存Originator的哪些內部狀態;
- 備忘錄(Memento):負責儲存Originator對象的內部狀態,並可防止Originator以外的其他對象訪問備忘錄Memento。備忘錄有兩個介面,Caretaker只能看到備忘錄的窄介面,它只能將備忘錄傳遞給其他對象。Originator可以看到一個寬介面,允許它訪問返回到先前狀態的所有資料;
- 管理者(Caretaker):負責儲存好備忘錄Memento,不能對備忘錄的內容進行操作或檢查。
在題目的情境中,遊戲角色類相當於Originator,使用同樣的“遊戲角色”執行個體“backup”來做備忘錄,這在當需要儲存全部資訊時,是可以考慮的,而用clone的方式來實現Memento的狀態儲存可能是更好的方法,但是這樣就相當於對上層應用開放了Originator的全部介面,這對於儲存備份有時候並不合適。
那麼當我們不需要儲存全部資訊以備使用時,需要怎麼辦?當我們需要儲存的並不是全部資訊,而只是部分資訊,那麼就應該有獨立的備忘錄類Memento,它只擁有需要儲存的資訊的屬性。
改進版本——備忘錄模式
class GameRole(): def __init__(self): self.vitality = 0 self.attack = 0 self.defense = 0 def state_display(self): print("當前角色狀態:") print("體力:",self.vitality) print("攻擊:",self.attack) print("防禦:",self.defense) def get_init_state(self): """ 從伺服器或者本地磁碟載入初始狀態 """ self.vitality = 100 self.attack = 100 self.defense = 100 def fight(self): """ 與Boss決鬥,資料損耗為0 """ self.vitality = 0 self.attack = 0 self.defense = 0 def save_state(self): """ 新增“儲存狀態”的方法 """ return RoleStateMemento(self.vitality, self.attack, self.defense) def recovery_state(self, memento): """ 新增“恢複狀態”的方法 """ self.vitality = memento.vitality self.attack = memento.attack self.defense = memento.defense class RoleStateMemento(): """ 遊戲角色狀態儲存箱 """ def __init__(self, vitality, attack, defense): self.vitality = vitality self.attack = attack self.defense = defense class RoleStateCaretaker(): """ 角色狀態管理者類 """ def __init__(self): self.memento = None def main(): mario = GameRole() mario.get_init_state() mario.state_display() state_admin = RoleStateCaretaker() state_admin.memento = mario.save_state() # 遊戲角色的儲存對用戶端是透明的 mario.fight() mario.state_display() mario.recovery_state(state_admin.memento) # 遊戲角色的恢複對用戶端是透明的 mario.state_display() main()
當前角色狀態:體力: 100攻擊: 100防禦: 100當前角色狀態:體力: 0攻擊: 0防禦: 0當前角色狀態:體力: 100攻擊: 100防禦: 100
點評
將需要儲存的細節封裝在Memento類中,那一天要更改儲存的細節也不用影響用戶端。
Memento模式比較適用於功能複雜的,但需要維護或記錄屬性曆史的類,或者需要儲存的屬性只是眾多屬性中的一小部分時,Originator可以根據儲存的Memento資訊還原到前一狀態。
命令模式也有類似的撤銷作用,如果在某個系統中使用命令模式時,需要實現命令的撤銷功能,那麼命令模式可以使用備忘錄模式來儲存可撤銷操作的狀態。有時候一些對象的內部資訊必須儲存在對象以外的地方,但是必須要由對象自己讀取,這時,使用備忘錄可以把複雜的對象內部資訊對其他的對象屏蔽起來,從而可以恰當的保持封裝的邊界。
當角色的狀態改變的時候,有可能這個狀態無效,這時候就可以使用暫時儲存起來的備忘錄將狀態複原。
[Python設計模式] 第18章 遊戲角色備份——備忘錄模式