【问题标题】:What design patterns can help model objects whose behaviors change dynamically, or objects with many optional behaviors?哪些设计模式可以帮助对行为动态变化的对象或具有许多可选行为的对象进行建模?
【发布时间】:2023-03-07 10:55:01
【问题描述】:

对不起,冗长的例子,但我最近遇到了这种类型的软件设计问题,并且一直在思考它。不确定是否有一个术语,所以我将给出 2 个通用示例来给出一个想法:

示例 1:您正在开发一款 RPG 游戏,并且为主角设置了一个课程。该角色对游戏世界的反应会根据您所穿/持有的物品、分配的技能,基本上是您对象的内部状态而变化。

假设你在游戏中有以下物品:

  • 再生之戒:让你的角色随着时间的推移恢复生命值
  • 偷偷摸摸的运动鞋:增加偷偷摸摸
  • 魔镜:反射受到伤害的百分比
  • 傻瓜眼镜:突出制作资源

示例 2:您有一辆丰田卡罗拉。它有不同的配置,但它们都是丰田卡罗拉。 ABS 和牵引力控制是您可以添加到基准模型中的可选功能(行为)。

  • 基线模型在运行时没有做任何额外的事情。
  • 借助 ABS,汽车在行驶时会检查并响应突然停止
  • 借助牵引力控制,汽车在行驶时会检查并响应失去的牵引力
  • 很明显,当您拥有一辆同时具备这两种行为的汽车时,您将在汽车行驶时同时执行这两种行为。

两个例子的共同属性:

  • 重要性等级有一个具体的白板,它可以从它开始
  • 可选项目/部件可以为该对象添加能力或额外行为;每场比赛/在运行时做一些额外的事情
  • 在添加或删除项目/行为时可能会或可能不会改变对象(穿上运动鞋时增加潜行,脱下时减少它)

可能的解决方案(但不充分):

if 语句:

if ch.isWearing 'doofy glasses':
    render with doofy shader
else if ch.isWearing ...

不起作用。需要为每个部分添加一个子句。类会很快变得庞大而复杂。

策略模式:

class TractionControlStrategy
class ABSstrategy

class Toyota:
    TractionControlStrategy tcs
    ABSstrategy abs
    run():
        if tcs not null:
            tcs.run()
        if abs not null:
            abs.run()

carWithTCS = new Toyota(new TractionControlStrategy())

没有比之前的解决方案好多少,因为您仍然有很长的 if 语句列表

子类策略:

class Toyota:
    run():
        // does nothing

class ToyotaWithABS : Toyota
    ABSstrategy abs = new ABSstrategy()
    run():
        abs.run

class ToyotaWithTCS : Toyota ...

我认为满足开放/封闭原则。也许比上一个更好?但是现在您必须为每种配置组合创建一个类。如果您后来发现还有其他可选功能,那么您需要实现的每个功能的类数量都会翻倍......

如何使用 OOP 对这些类型的交互和行为进行建模?什么样的设计模式或设计模式的组合促进了这种事情?

真的不确定这是否是一个好问题,或者我是否清楚我在问什么,因为我从未真正练习过好的软件设计。

我正在学习 OpenGL,正在研究我的 3D 网格/模型类。这个问题是相关的,因为在我的渲染器中,索引和纹理对于网格来说是可选的。所以一个网格只能是顶点,索引顶点,顶点和纹理,或全部3个。另外,我无法预见将来我可能想要添加哪些功能,因为我不知道我会像学习一样学习什么一个月后,所以我需要课程灵活且可扩展

【问题讨论】:

    标签: oop design-patterns game-engine


    【解决方案1】:

    您是对的,任何需要描述所有可能组合的选项(无论是通过 switch/if 还是通过类层次结构)都不好。

    一种方法是使用decorator pattern 来包装您的主类并添加动态属性。 或者,您可以将单独的 Stats 类作为主类中的一个字段,并用其他项目装饰它。

    class Thing
    
        BasicStats stats
    
        constructor() 
            this->stats = new BasicStats()
    
        addItem(Item item)
            // Decorate current stats with new stats
            item->setComponent(this->stats)
            this->stats = item
            return this
    
        int getHealth()
            return this->stats->getHealth()
    

    你可以这样使用它:

     thing = new Thing() // has basic stats
     thing->addItem(new MagicMirror)->addItem(new SilverBullet)
     // will go through the chain of decorators to get the value
     health = thing->getHealth()
    

    另一种方法是在主类中列出动态选项(或项目):

    class Thing
    
        Stats stats
        ItemList items
    
        updateStats() 
            for item in this->items
                item->updateStats(this->stats)
                // OR if we want to not disclose the stats to items
                //    we can pass this and items should use 
                //    character's methods to change stats
                item->updateStats(this)
    
        add(Item item)
            this->items->append(item)
            return this
    

    可以这样使用:

    thing = new Thing()
    thing->add(new MagicMirror())->add(new RingOfRegeneration)->updateStats()
    

    Memento 如果您不想直接更改角色统计信息,也很有用。例如,如果您有一些“比较”功能,用户(或玩家)可以组合不同的项目集以查看影响,然后“应用”它们。

    还可以查看chain-of-responsibility,类似于带有统计信息列表的选项 - 您可以创建“项目”链并从该链中请求统计信息。就像您可以从传递基础健康值开始,然后每个项目都会对其进行转换,并且您将在链结束时获得“升级”值。

    更新:还有一个想法,visitor 也很有用:

    # Base class for stats, Element
    class Stat
    
       abstract accept(StatVisitor visitor)
    
    class Health extends Stat
    
       private int health
    
       accept(StatVisitor visitor)
           # or you can have visitor->visitHealth(this)
           visitor->visit(this)
    
       multiply(int mult)
          this->health = this->health * mult
    
    class Strength extends Stat     
    
       private int strength
    
       accept(StatVisitor visitor)
           visitor->visit(this)
    
       add(int strength)
          this->strength = this->strength + strength
    

    统计数据是访客模式的“元素”。 Thing 类代表“客户”:

    # Thing contains stats, this is Client
    class Thing
    
       StatsList stats
    
       accept(StatsVisitor visitor)
          for stat in this->stats
              stat->visit(visitor)
    

    而“Visitors”是我们的物品,可以修改统计:

    # Base visitor class
    class StatVisitor
    
       abstract visit(Health health)
       abstract visit(Strength strength)
    
    class MagicMirror
    
       # magic mirror multiplies health by 10
       visit(Health health)
          health->multiply(10)
    
       # magic mirror increases strength +5
       visit(Strength strength)
          strength->add(5)
    

    现在你可以这样做了:

    thing = new Thing()
    item = MagicMirror()
    # now update all the stats with MagicMirror
    thing->accept(item)
    

    【讨论】:

      【解决方案2】:

      不要把事情复杂化:

      1. 再生之戒:让你的角色随着时间的推移恢复健康

        这根本不应该修改您的对象。例如。戴上戒指可能会添加一个计时器,该计时器会尝试增加实体(即佩戴者的)生命值,而取下戒指会再次移除此计时器。

      2. 偷偷摸摸的运动鞋:增加偷偷摸摸

        这可以建模为对统计数据的修改:

        class Entity
        
            Map<Skill, SortedList<Modifier>> skillModifiers
        
            getSneakChance()
                sneakChance = .. // compute base value from attributes
                for each mod in skillModifiers[SneakChance]
                    sneakChance = mod.apply(sneakChance)
                return sneakChance
        

        地图避免了 decoratorchain-of-responsibility 的两个缺点:不是每个修饰符都会为评估添加另一个间接性,您可以使用更简单的基于值的修饰符(将值增加 x%),而不是将要修改的统计/技能的逻辑或如何访问它的逻辑也推送到修改器中。

        一个困难是决定以什么顺序应用修改器:你应该先增加 20 到你的攻击伤害,然后再增加 5% 还是反过来,失去 1 点伤害?某些修饰符是否只适用于基值,即您是否需要同时传递mod.apply 中的累积值和基值?

      3. 魔镜:反射受到伤害的百分比

        与 2 相同。这次将修改器和反应映射到特定事件(即受到伤害)。这允许将相同的效果应用于多个实体,例如光环或其他 AOE 法术。

      4. 傻瓜眼镜:突出制作资源

        这又是不影响实体的东西。 gui 控制器需要此类信息,因此只需向玩家查询视觉修饰符(除非游戏中的每个实体都需要操纵 gui 的行为和外观)。

      【讨论】:

        猜你喜欢
        • 2010-10-09
        • 2012-07-21
        • 2011-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-02-28
        • 1970-01-01
        • 2013-06-01
        相关资源
        最近更新 更多