第二章 设计模式故事会之策略模式:魔王城里的勇者传说

系列文章目录

第一章 设计模式故事会之楔子:面试还在回答策略、工厂?该升级设计模式库了!
第二章 设计模式故事会之策略模式:魔王城里的勇者传说


文章目录


🏰魔王城里的勇者传说

很久很久以前,魔王抓走了公主,以此威胁国王。为了拯救公主,勇者林克带上了村里最好的剑,踏上了讨伐魔王的征途。。。

一路上,出现了各种奇奇怪怪的怪物。林克抄起剑就砍,过关斩将并不是什么难事。林克心里不禁暗想:这些怪物真是逊啊,这样下去,要不了多久就能救出公主了

行不多时,空气逐渐变得灼热了起来,他遇到一个 火焰守卫。这守卫全身冒火,像个移动的火炉。林克依旧举剑就砍,但剑刚碰到守卫,就被烧得通红,根本无法近身。林克心里一凉,在这么整下去,他迟早被烤熟,惹不起还躲不起吗?换路总行了吧

避开了 火焰守卫 ,林克继续前进,炎热逐渐散去,取而代之的是刺骨的寒冷,林克被冻得直哆嗦。不久,眼前出现一个浑身冒着寒气的 冰霜守卫。得!一看就是惹不起的主,都还没靠近,剑身已经结满冰霜,变得又脆又重。林克脸都绿了,转头就走:这还怎么玩,溜了溜了!!

"傻小子,对付不同的怪物要用不同的剑,你那把剑只能打打普通的怪。。。属性剑就在城堡的入口。" 忽然,一个苍老的声音想起

"老头,你这不早点说,害我在这又被烤又被冻的?"林克满脸不爽的回答,脚下丝毫不停,继续狂奔

"你自己爱跳新手教程,还怨我,别啰嗦了,想活命就快点去拿装备吧。" 苍老的声音渐渐弱了下来,随后彻底消失

林克骂骂咧咧的回到门口,果真发现几把剑:霜之哀伤、火之高兴、木之乃伊。回想起差点把他烤熟的火焰守卫 ,林克一阵牙痒痒,拿起霜之哀伤就杀回去。有了属性加持,林克非但没感觉到热还有一丝丝冰凉,一剑挥出,寒意四起。属相相克打出暴击,一下子就消灭了火焰守卫

接下来,林克如法炮制,解决了冰霜守卫,一路上一会儿冰,一会儿火,一会儿电。林克就这么往返跑,遇到哪种守卫,就去门口拿对应的属性剑。开始林克还没感觉,渐渐的体力有点不支。路程还没过半,林克已经开始喘粗气了,这么来回倒腾,没被怪打死,自己先得活活累死。遇到新的怪物,还得重新制作一把新剑。着实麻烦。不行,得想个法子。林克回到了门口,仔细扫视了地上的的东西,发现之前没有注意的东西------元素宝石。这玩意可以和普通剑结合,产生属性剑的效果。这就简单了,林克摸了几个不同属性的宝石,提溜上村里最好的剑再次踏上了征程。这次,林克从容不迫,遇到火焰守卫,就换上冰霜宝石;遇到冰霜守卫,就换上火焰宝石。他再也不用像个傻子一样来回跑了。

随着怪物一个个倒下,林克已经到达大魔王面前,决战即将到来!!

"林克,你来了。。百年前,海拉鲁大陆。。" 出乎意料,大战并没有一触即发,魔王反而饶有兴致的讲起故事

有破绽!!!霎那间,剑气纵横,寒意凛冽。林克上来就是一套小连招,附带冰元素剑气全都砍向了魔王

魔王从容不迫,没有任何动作。交错的剑气在魔王身前不断湮灭,他继续开始讲起了故事:"你小子,能不能听人把话说完!!!百年前。。。"

"冰元素无效?!"林克愕然,魔王依旧喋喋不休的讲着故事,林克换上火元素继续出击。这次,热浪滚滚,剑尖吞吐着火舌,似欲焚尽九天

剑气依旧在魔王面前消失,火元素也没用!!不过魔王似乎有点恼火,对着旁边怒道:"导演!!这故事非讲不可吗?这个愣头青一点也不听,就在那钻空砍我!!!!"

"克服克服,魔王同志,不要总是抱怨,找找自己的原因!" 导演摆弄着摄像机,侧头说到

"*%@#!"魔王忍不住骂娘,心里诅咒。就在他说话的空隙,林克又出了好几次手,他得反击了,不然要被砍死了。。。

林克抖了抖背包,所有元素都无效?!这要怎么玩!!!倒不是林克不想跑,实在是因为房间被下了结界,没法跑

似乎看出可林克的窘境,魔王发出了爽朗的笑声:"哈哈哈哈,蠢货!褶子了吧,没招了吧!我是暗属性。。桀桀桀。那就从世界上消失吧。" 言讫,魔王开始读条,准备放大招

就在这千钧一发之际,林克突然想起城堡入口的刻字:"每一个石头都有无尽可能?!"

林克拿出了背包里的一块普通石头,迅速地用魔法符文在上面刻画。在魔王读条的过程中,一块圣光宝石诞生了

当林克把这颗新宝石插进剑时,剑身发出了耀眼的光芒,轻易地击溃了魔王,救出了公主。从此勇者和公主过上了幸福的生活。。。

🎭 彩蛋片尾

"什么魔法符文,是你临时编的吧。" 魔王一脸鄙夷的看着导演

"没办法,我实在想不出怎么收尾。。" 导演无奈的摊摊手

🤔小A的思考

合上书,小A 闭上眼睛思索了一下。勇者林克在讨伐魔王的过程中,面临了几个关键的选择,这些选择恰好对应了软件设计中的不同思路

第一次尝试:继承带来的问题

林克最初用普通剑打怪,遇到特殊怪物束手无策。后来,他拿起不同属性的剑应对不同怪物:

  • 基类:Sword(普通剑)
  • 为了处理 火焰守卫 ,可以继承 Sword 创建 IceSword (冰剑)
  • 为了处理 冰霜守卫 ,可以继承 Sword 创建 FireSword (火剑)

林克每次战斗都得回去更换整把剑。更糟的是,如果一把剑不仅有攻击能力,还有格挡、附魔、投掷等多种行为,每增加一种新元素,林克就需要打造一把全新的、包含了所有行为的毒剑或光剑。这不仅导致了类的急剧膨胀,也就是所谓的 类爆炸

第二次尝试:组合与策略模式的雏形

林克发现元素宝石可以嵌入普通剑,动态改变攻击属性。这就是 策略模式 的雏形:

  • Context(环境角色): 勇者林克,他持有普通剑
  • Strategy(抽象策略角色): 元素宝石,表示可替换的攻击方式
  • ConcreteStrategy(具体策略角色): 冰霜宝石、火焰宝石、雷电宝石

遇到不同怪物时,只需装配不同宝石即可,无需更换整把剑。策略模式 带来的好处:

  1. 高灵活性: 快速切换策略
  2. 可扩展性: 新增策略不影响原系统

决战时刻:策略模式的终极应用

在冰、火等宝石对魔王无效时,林克临时创造了 圣光宝石,击败魔王。这说明:

  • 策略可以动态创建
  • 运行时选择策略,而非编译时固定

策略模式消除if...else

小A 心中的疑惑并未完全解开。他继续思考着一个困扰他已久的问题:很多 设计模式 的书籍和文章都说,策略模式 是用来消除if...else的。但林克的故事让他疑窦丛生,因为林克依然需要根据不同怪物来选择不同的宝石。这意味着不同场景下的判断和选择是必须存在的,它并没有消失啊

小A 的思绪流转着,他意识到,也许"消除if...else"这个说法本身就不够准确。策略模式 的精髓,并非是让所有判断都消失,而是将一大坨代码从判断中剥离出来。而各自分支下的代码形成了不同的策略,if...else依旧存在,只是它不在那么的臃肿和难以阅读了。策略模式关注的是将变化部分封装起来,而不是彻底消灭分支逻辑

当然了,一定要让if...else也行,整一个映射表,根据不同情况获取不同策略,这也许就是所谓的消灭if...else的方式了吧

😩林克的烦恼

  1. 元素宝石的出现。固然让林克省去了背着一大堆剑的麻烦,但是宝石多了,同样不好管理,林克的背包可不是无限的噢,望着一大堆的宝石,林克愁眉不展
  2. 林克嫌每次遇到怪物都要重新翻一下背包,寻找宝石太过麻烦,如何才能遇到不同的怪物时自动附上对应的宝石呢?

你会怎么帮助林克呢?欢迎留下你的答案。代码在最下面哦~~~~


总结

勇者的故事着实让 小A 收益匪浅,之前 小A 以为 策略模式 就是有多个不同实现,可以替换if...else,现在看来,自己简直大错特错。策略模式 就是把类行为进行剥离,可以动态切换。并且避免 类爆炸问题

附录

第一阶段:硬编码的困境

勇者林克一开始只用一把普通剑,遇到特殊的怪物时,攻击逻辑直接在代码里写死,导致无法应对变化

python 复制代码
import random

class Hero:
    def __init__(self, name="林克"):
        self.name = name

    def 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 = name
        self.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)

第二阶段:继承带来的"类爆炸"

林克意识到需要不同的剑,但这种"一把剑对应一种怪物"的思路,在代码中对应着继承关系

python 复制代码
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 = name
        self.current_sword = Sword() # 初始拿着普通剑

    def equip(self, new_sword):
        print(f"勇者{self.name}换上了{new_sword.__class__.__name__}。")
        self.current_sword = new_sword

    def attack(self, monster):
        print(self.current_sword.attack(monster))

class Monster:
    def __init__(self, name, monster_type):
        self.name = name
        self.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)

第三阶段:策略模式------组合优于继承

林克找到了宝石,将攻击行为 上剥离出来,实现了组合。这就是 策略模式 的核心思想

python 复制代码
import random

# 宝石策略
class Gem:
    def effect(self, monster):
        raise NotImplementedError

class 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 = name
        self.gem = None

    def insert_gem(self, gem):
        print(f"【{self.name}】镶嵌了 {gem.__class__.__name__}。")
        self.gem = gem

    def 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 = name
        self.type = monster_type

# ----------------------------------------
# 方法 1:动态策略 + if 判断
# ----------------------------------------
def attack_with_if(hero_sword, gems, monster):
    selected_gem = None
    for gem in gems:
        if monster.type == "火焰" and isinstance(gem, IceGem):
            selected_gem = gem
        elif monster.type == "冰霜" and isinstance(gem, FireGem):
            selected_gem = gem
        elif monster.type == "暗属性" and isinstance(gem, HolyGem):
            selected_gem = gem
    if 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 = None
    gem_class = type_to_gem_class.get(monster.type)
    for gem in gems:
        if gem_class and isinstance(gem, gem_class):
            selected_gem = gem
    if 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)
相关推荐
A7bert77729 分钟前
【YOLOv5部署至RK3588】模型训练→转换RKNN→开发板部署
c++·人工智能·python·深度学习·yolo·目标检测·机器学习
冷月半明1 小时前
时间序列篇:Prophet负责优雅,LightGBM负责杀疯
python·算法
巧克力791 小时前
js数组去重的方法
javascript·面试
教练我想打篮球_基本功重塑版1 小时前
L angChain 加载大模型
python·langchain
本末倒置1832 小时前
Svelte邪修的JSDoc,到底是个啥?
前端·javascript·面试
再学一点就睡2 小时前
手撕前端常用 7 种设计模式:从原理到实战,附完整代码案例
前端·设计模式
yvvvy3 小时前
前端性能优化全家桶:从重绘重排到面试连招,一篇搞懂
前端·javascript·面试
跟橙姐学代码3 小时前
手把手教你玩转 multiprocessing,让程序跑得飞起
前端·python·ipython
天天摸鱼的java工程师3 小时前
谈谈你对 Seata 的理解?8 年 Java 开发:从业务踩坑到源码级解析(附实战代码)
java·后端·面试
绝无仅有3 小时前
常用 Kubernetes (K8s) 命令指南
后端·面试·github