第二章 设计模式故事会之策略模式:魔王城里的勇者传说
系列文章目录
第一章 设计模式故事会之楔子:面试还在回答策略、工厂?该升级设计模式库了!
第二章 设计模式故事会之策略模式:魔王城里的勇者传说
文章目录
- 系列文章目录
- 🏰魔王城里的勇者传说
- 🎭 **彩蛋片尾**
- 🤔小A的思考
- 第一次尝试:继承带来的问题
- 第二次尝试:组合与策略模式的雏形
- 决战时刻:策略模式的终极应用
- 策略模式消除`if...else`
- 😩林克的烦恼
- 总结
- 附录
- 第一阶段:硬编码的困境
- 第二阶段:继承带来的“类爆炸”
- 第三阶段:策略模式——组合优于继承
🏰魔王城里的勇者传说
很久很久以前,魔王抓走了公主,以此威胁国王。为了拯救公主,勇者林克带上了村里最好的剑,踏上了讨伐魔王的征途。。。
一路上,出现了各种奇奇怪怪的怪物。林克抄起剑就砍,过关斩将并不是什么难事。林克心里不禁暗想:这些怪物真是逊啊,这样下去,要不了多久就能救出公主了
行不多时,空气逐渐变得灼热了起来,他遇到一个 火焰守卫。这守卫全身冒火,像个移动的火炉。林克依旧举剑就砍,但剑刚碰到守卫,就被烧得通红,根本无法近身。林克心里一凉,在这么整下去,他迟早被烤熟,惹不起还躲不起吗?换路总行了吧
避开了 火焰守卫,林克继续前进,炎热逐渐散去,取而代之的是刺骨的寒冷,林克被冻得直哆嗦。不久,眼前出现一个浑身冒着寒气的 冰霜守卫。得!一看就是惹不起的主,都还没靠近,剑身已经结满冰霜,变得又脆又重。林克脸都绿了,转头就走:这还怎么玩,溜了溜了!!
“傻小子,对付不同的怪物要用不同的剑,你那把剑只能打打普通的怪。。。属性剑就在城堡的入口。” 忽然,一个苍老的声音想起
“老头,你这不早点说,害我在这又被烤又被冻的?”林克满脸不爽的回答,脚下丝毫不停,继续狂奔
“你自己爱跳新手教程,还怨我,别啰嗦了,想活命就快点去拿装备吧。” 苍老的声音渐渐弱了下来,随后彻底消失
林克骂骂咧咧的回到门口,果真发现几把剑:霜之哀伤、火之高兴、木之乃伊。回想起差点把他烤熟的火焰守卫,林克一阵牙痒痒,拿起霜之哀伤就杀回去。有了属性加持,林克非但没感觉到热还有一丝丝冰凉,一剑挥出,寒意四起。属相相克打出暴击,一下子就消灭了火焰守卫
接下来,林克如法炮制,解决了冰霜守卫,一路上一会儿冰,一会儿火,一会儿电。林克就这么往返跑,遇到哪种守卫,就去门口拿对应的属性剑。开始林克还没感觉,渐渐的体力有点不支。路程还没过半,林克已经开始喘粗气了,这么来回倒腾,没被怪打死,自己先得活活累死。遇到新的怪物,还得重新制作一把新剑。着实麻烦。不行,得想个法子。林克回到了门口,仔细扫视了地上的的东西,发现之前没有注意的东西——元素宝石。这玩意可以和普通剑结合,产生属性剑的效果。这就简单了,林克摸了几个不同属性的宝石,提溜上村里最好的剑再次踏上了征程。这次,林克从容不迫,遇到火焰守卫,就换上冰霜宝石;遇到冰霜守卫,就换上火焰宝石。他再也不用像个傻子一样来回跑了。
随着怪物一个个倒下,林克已经到达大魔王面前,决战即将到来!!
“林克,你来了。。百年前,海拉鲁大陆。。” 出乎意料,大战并没有一触即发,魔王反而饶有兴致的讲起故事
有破绽!!!霎那间,剑气纵横,寒意凛冽。林克上来就是一套小连招,附带冰元素剑气全都砍向了魔王
魔王从容不迫,没有任何动作。交错的剑气在魔王身前不断湮灭,他继续开始讲起了故事:“你小子,能不能听人把话说完!!!百年前。。。”
“冰元素无效?!”林克愕然,魔王依旧喋喋不休的讲着故事,林克换上火元素继续出击。这次,热浪滚滚,剑尖吞吐着火舌,似欲焚尽九天
剑气依旧在魔王面前消失,火元素也没用!!不过魔王似乎有点恼火,对着旁边怒道:“导演!!这故事非讲不可吗?这个愣头青一点也不听,就在那钻空砍我!!!!”
“克服克服,魔王同志,不要总是抱怨,找找自己的原因!” 导演摆弄着摄像机,侧头说到
“*%@#!”魔王忍不住骂娘,心里诅咒。就在他说话的空隙,林克又出了好几次手,他得反击了,不然要被砍死了。。。
林克抖了抖背包,所有元素都无效?!这要怎么玩!!!倒不是林克不想跑,实在是因为房间被下了结界,没法跑
似乎看出可林克的窘境,魔王发出了爽朗的笑声:“哈哈哈哈,蠢货!褶子了吧,没招了吧!我是暗属性。。桀桀桀。那就从世界上消失吧。” 言讫,魔王开始读条,准备放大招
就在这千钧一发之际,林克突然想起城堡入口的刻字:“每一个石头都有无尽可能?!”
林克拿出了背包里的一块普通石头,迅速地用魔法符文在上面刻画。在魔王读条的过程中,一块圣光宝石诞生了
当林克把这颗新宝石插进剑时,剑身发出了耀眼的光芒,轻易地击溃了魔王,救出了公主。从此勇者和公主过上了幸福的生活。。。
🎭 彩蛋片尾
“什么魔法符文,是你临时编的吧。” 魔王一脸鄙夷的看着导演
“没办法,我实在想不出怎么收尾。。” 导演无奈的摊摊手
🤔小A的思考
合上书,小A 闭上眼睛思索了一下。勇者林克在讨伐魔王的过程中,面临了几个关键的选择,这些选择恰好对应了软件设计中的不同思路
第一次尝试:继承带来的问题
林克最初用普通剑打怪,遇到特殊怪物束手无策。后来,他拿起不同属性的剑应对不同怪物:
- 基类:
Sword
(普通剑) - 为了处理 火焰守卫,可以继承
Sword
创建IceSword
(冰剑) - 为了处理 冰霜守卫,可以继承
Sword
创建FireSword
(火剑)
林克每次战斗都得回去更换整把剑。更糟的是,如果一把剑不仅有攻击能力,还有格挡、附魔、投掷等多种行为,每增加一种新元素,林克就需要打造一把全新的、包含了所有行为的毒剑或光剑。这不仅导致了类的急剧膨胀,也就是所谓的 类爆炸
第二次尝试:组合与策略模式的雏形
林克发现元素宝石可以嵌入普通剑,动态改变攻击属性。这就是 策略模式 的雏形:
Context
(环境角色): 勇者林克,他持有普通剑Strategy
(抽象策略角色): 元素宝石,表示可替换的攻击方式ConcreteStrategy
(具体策略角色): 冰霜宝石、火焰宝石、雷电宝石
遇到不同怪物时,只需装配不同宝石即可,无需更换整把剑。策略模式 带来的好处:
- 高灵活性: 快速切换策略
- 可扩展性: 新增策略不影响原系统
决战时刻:策略模式的终极应用
在冰、火等宝石对魔王无效时,林克临时创造了 圣光宝石,击败魔王。这说明:
- 策略可以动态创建
- 运行时选择策略,而非编译时固定
策略模式消除if...else
小A 心中的疑惑并未完全解开。他继续思考着一个困扰他已久的问题:很多 设计模式 的书籍和文章都说,策略模式 是用来消除if...else
的。但林克的故事让他疑窦丛生,因为林克依然需要根据不同怪物来选择不同的宝石。这意味着不同场景下的判断和选择是必须存在的,它并没有消失啊
小A 的思绪流转着,他意识到,也许“消除if...else
”这个说法本身就不够准确。策略模式 的精髓,并非是让所有判断都消失,而是将一大坨代码从判断中剥离出来。而各自分支下的代码形成了不同的策略,if...else
依旧存在,只是它不在那么的臃肿和难以阅读了。策略模式关注的是将变化部分封装起来,而不是彻底消灭分支逻辑
当然了,一定要让if...else
也行,整一个映射表,根据不同情况获取不同策略,这也许就是所谓的消灭if...else
的方式了吧
😩林克的烦恼
- 元素宝石的出现。固然让林克省去了背着一大堆剑的麻烦,但是宝石多了,同样不好管理,林克的背包可不是无限的噢,望着一大堆的宝石,林克愁眉不展
- 林克嫌每次遇到怪物都要重新翻一下背包,寻找宝石太过麻烦,如何才能遇到不同的怪物时自动附上对应的宝石呢?
你会怎么帮助林克呢?欢迎留下你的答案。代码在最下面哦~~~~
总结
勇者的故事着实让 小A 收益匪浅,之前 小A 以为 策略模式 就是有多个不同实现,可以替换if...else
,现在看来,自己简直大错特错。策略模式 就是把类行为进行剥离,可以动态切换。并且避免 类爆炸问题
附录
第一阶段:硬编码的困境
勇者林克一开始只用一把普通剑,遇到特殊的怪物时,攻击逻辑直接在代码里写死,导致无法应对变化
import randomclass Hero:def __init__(self, name="林克"):self.name = namedef attack(self, monster):print(f"勇者{self.name}举起普通剑,向{monster.name}砍去!")if monster.type == "火焰":print("啊!剑被烧红了,攻击无效!")elif monster.type == "冰霜":print("剑身结冰,变得又脆又重,攻击无效!")else:print("轻松过关,怪物倒下了。")class Monster:def __init__(self, name, monster_type):self.name = nameself.type = monster_type# 场景:随机生成怪物
monster_types = ["火焰", "冰霜", "普通"]
random_monster_type = random.choice(monster_types)if random_monster_type == "火焰":monster = Monster("火焰守卫", "火焰")
elif random_monster_type == "冰霜":monster = Monster("冰霜守卫", "冰霜")
else:monster = Monster("哥布林", "普通")hero = Hero()
hero.attack(monster)
第二阶段:继承带来的“类爆炸”
林克意识到需要不同的剑,但这种“一把剑对应一种怪物”的思路,在代码中对应着继承关系
import random# 基类:普通的剑
class Sword:def attack(self, monster):return f"用普通剑攻击{monster.name},毫无作用!"# 子类:不同属性的剑,继承自Sword
class IceSword(Sword):def attack(self, monster):return f"发出冰霜剑气,轻松消灭{monster.name}!"class FireSword(Sword):def attack(self, monster):return f"喷出灼热火焰,轻松消灭{monster.name}!"# 勇者现在需要根据怪物类型更换不同的剑
class Hero:def __init__(self, name="林克"):self.name = nameself.current_sword = Sword() # 初始拿着普通剑def equip(self, new_sword):print(f"勇者{self.name}换上了{new_sword.__class__.__name__}。")self.current_sword = new_sworddef attack(self, monster):print(self.current_sword.attack(monster))class Monster:def __init__(self, name, monster_type):self.name = nameself.type = monster_type# 场景:随机生成怪物,并根据类型更换剑
monster_types = ["火焰", "冰霜"]
random_monster_type = random.choice(monster_types)if random_monster_type == "火焰":monster = Monster("火焰守卫", "火焰")sword = IceSword()
elif random_monster_type == "冰霜":monster = Monster("冰霜守卫", "冰霜")sword = FireSword()
else:# 理论上不会出现,但为了严谨monster = Monster("哥布林", "普通")sword = Sword()hero = Hero()
hero.equip(sword)
hero.attack(monster)
第三阶段:策略模式——组合优于继承
林克找到了宝石,将攻击行为从剑上剥离出来,实现了组合。这就是 策略模式 的核心思想
import random# 宝石策略
class Gem:def effect(self, monster):raise NotImplementedErrorclass IceGem(Gem):def effect(self, monster):return f"发出冰霜剑气,轻松消灭{monster.name}!"class FireGem(Gem):def effect(self, monster):return f"喷出灼热火焰,轻松消灭{monster.name}!"class HolyGem(Gem):def effect(self, monster):return f"圣光降临,彻底击败{monster.name}!"class Sword:def __init__(self, name="村里最好的剑"):self.name = nameself.gem = Nonedef insert_gem(self, gem):print(f"【{self.name}】镶嵌了 {gem.__class__.__name__}。")self.gem = gemdef attack(self, monster):if self.gem:print(self.gem.effect(monster))else:print(f"【{self.name}】挥舞普通攻击,砍向{monster.name}。")class Monster:def __init__(self, name, monster_type):self.name = nameself.type = monster_type# ----------------------------------------
# 方法 1:动态策略 + if 判断
# ----------------------------------------
def attack_with_if(hero_sword, gems, monster):selected_gem = Nonefor gem in gems:if monster.type == "火焰" and isinstance(gem, IceGem):selected_gem = gemelif monster.type == "冰霜" and isinstance(gem, FireGem):selected_gem = gemelif monster.type == "暗属性" and isinstance(gem, HolyGem):selected_gem = gemif not selected_gem and gems:selected_gem = gems[0]if selected_gem:hero_sword.insert_gem(selected_gem)print(f"使用 if 判断策略,准备攻击 {monster.name}")hero_sword.attack(monster)# ----------------------------------------
# 方法 2:动态策略 + 映射表
# ----------------------------------------
def attack_with_map(hero_sword, gems, monster):type_to_gem_class = {"火焰": IceGem,"冰霜": FireGem,"暗属性": HolyGem}selected_gem = Nonegem_class = type_to_gem_class.get(monster.type)for gem in gems:if gem_class and isinstance(gem, gem_class):selected_gem = gemif not selected_gem and gems:selected_gem = gems[0]if selected_gem:hero_sword.insert_gem(selected_gem)print(f"使用映射表策略,准备攻击 {monster.name}")hero_sword.attack(monster)# ----------------------------------------
# 场景测试
# ----------------------------------------
hero_sword = Sword()
gems = [IceGem(), FireGem(), HolyGem()]
monster_type = random.choice(["火焰", "冰霜", "暗属性", "普通"])
monster = Monster(f"{monster_type}守卫", monster_type)attack_with_if(hero_sword, gems, monster)
attack_with_map(hero_sword, gems, monster)