如何实现灵活的对象组成?

Rob*_*dar 6 oop vba design-patterns dependency-injection

问题背后的动机

我一直在学习如何使用串联继承在Javascript中进行对象组合,并且想知道如何在VBA(不具有继承)中完成类似的工作。

对象组成:我试图弄清楚如何实现“具有”关系与“是”关系。我希望能够编写简单的行为类,将它们组合在一起以构成更复杂的类,从而可以在其中使用它们。

我创建了一个简单的示例来演示我想要完成的工作。


用例范例

测试模块

以下是一些可能使用的示例。不过,对于这个问题,我将只关注Fighter类的示例用法。

Fight方法实际上是FightCanFight类中调用该方法。它调试一条消息并将耐力降低1。

'MOST EXCITING GAME OF ALL TIME! =)
Private Sub StartGame()

    Dim Slasher As Fighter
    Set Slasher = New Fighter
    Slasher.Name = "Slasher"

    Slasher.Fight '-> Slasher slashes at the foe!
    Debug.Print Slasher.Stamina '-> 99

    'MAGES CAN ONLY CAST (ONLY HAS MANA)
    Dim Scorcher As Mage
    Set Scorcher = New Mage
    Scorcher.Name = "Scorcher"
    Scorcher.Cast "fireball" '->Scorcher casts fireball!
    Debug.Print Scorcher.Mana '-> 99

    'CAN BOTH FIGHT & CAST (HAS BOTH STAMINA & MANA)
    Dim Roland As Paladin
    Set Roland = New Paladin
    Roland.Name = "Roland"
    Roland.Fight '-> Roland slashes at the foe!
    Roland.Cast "Holy Light" '-> Roland casts Holy Light!

End Sub
Run Code Online (Sandbox Code Playgroud)

战斗机

此类具有两个公共属性NameStamina

该类还包含FightAbility哪个是CanFight该类的实例。这是我尝试完成构图的尝试。

Option Explicit

Private FightAbility As CanFight
Private pName As String
Private pStamina As Long

Private Sub Class_Initialize()
    pStamina = 100
    Set FightAbility = New CanFight
End Sub

Public Property Get Name() As String
    Name = pName
End Property

Public Property Let Name(ByVal Value As String)
    pName = Value
End Property

Public Property Get Stamina() As String
    Stamina = pStamina
End Property

Public Property Let Stamina(ByVal Value As String)
    pStamina = Value
End Property

'This is the function that uses the ability to fight.
'It passes a reference to itself to the `CanFight` class
'giving it access to its public properties.
'This is my attempt at composition.
Public Sub Fight()
    FightAbility.Fight Me
End Sub
Run Code Online (Sandbox Code Playgroud)

格斗类

这是可以用于其他字符的类。一个Example是一个Paladin类可能还需要具有战斗的能力

布局的state一个明显问题是Object。除非他们查看代码,否则用户将不知道它需要具有StaminaName属性。

Option Explicit

Public Sub Fight(ByRef State As Object)
    Debug.Print State.Name & " slashes at the foe!"
    State.Stamina = State.Stamina - 1
End Sub
Run Code Online (Sandbox Code Playgroud)

总结问题

由于没有足够的结构才能使用它,因此我的示例感觉很糟糕。

同时,我想确保我的游戏角色可以灵活地拥有自己独特的属性。上面的例子

  • Fighter用途:(canFight耐力)
  • Mage用途:(canCast法力)
  • Paladin同时使用:(canFight耐力)和canCast(法力)

如果我创建了一个ICharacter接口类,那么我感觉它将被锁定为具有所有类型的Characters的所有属性。

我的问题是我如何在VBA中实现结构化但灵活的合成

Mat*_*don 5

这是一个很难有效回答 IMO 的问题,主要是因为该模型过于简单。

Public Sub Fight(ByRef State As Object)
    Debug.Print State.Name & " slashes at the foe!"
    State.Stamina = State.Stamina - 1
End Sub
Run Code Online (Sandbox Code Playgroud)

如果我制作了一个使用巨大战锤进行战斗的野蛮战士,“砍杀敌人!” 听起来像是一些有趣的轻描淡写。谁/什么是敌人?我知道这都是理论上的和简化的(对吗?),但是如果我们谈论的是游戏,那么敌人需要在某一时刻真正死去,不是吗?

如果我们看看传统的 JRPG 是如何处理这个问题的,一个Fight方法需要知道战斗机及其目标的状态(让我们暂时保持目标单一),所以首先,它可能是这样的:

Public Sub Fight(ByVal fighterState As Object, ByVal targetState As Object)
    '...
End Sub
Run Code Online (Sandbox Code Playgroud)

基本上,Fight方法的作用是targetState基于涉及fighterState和的许多因素来评估/实施需要发生的变化targetState。因此,它可能是一个更好的名称Attack,我们可以假设它fighterState包含有关当前装备的武器以及该武器是“斜线”、“刺穿”、“粉碎”还是只是“击中”目标的信息。 . 类似地,targetState可以假设 包含有关目标上装备了哪些装甲,以及该装备是否以及如何能够偏转/抵消或减少受到的伤害量的信息。有了这样的机制,我们甚至可以有一个PoisonBlade挥砍目标造成 76 HP 伤害,外加每回合重复 8 HP 中毒伤害,除非目标消耗(或以其他方式给予)Antidote物品来治愈中毒状态。

现在,战士是 aFighter还是 aPaladin或 aBlackMage没有区别:游戏机制需要的不是每个角色类中的不同属性和成员。事实上,游戏机制根本不在乎角色类是什么,机制对每个人都是一样的:Fight是一个 UI 命令,一个和其他任何一样的能力。角色是aBlackMage并且没有装备武器?击退 - 并造成 1 HP 伤害(如果有)。角色是一个Paladin并且可以决定“战斗”还是“施放”?UI 命令,而不是字符类设计。

我们如何设计类的模块是不是很喜欢他们在教科书做Animal,并CatDog其中Dog去“纬”,而Cat变为“喵喵”,所有的代码所做的是调用Animal.Talk在两种情况下和噗,闪闪发光的多态性,通过继承!

我的意思是,现实世界的代码不会做CatDog类,就像现实世界的 JRPG 游戏会为游戏中的每个可能的角色类定义不同的类型 - 一个Enum,也许,以及不同的资产和资源,当然;向您的游戏添加新角色类应该是添加数据,而不是代码。但不需要游戏机制有怎样不同的困扰Paladdin可以是一个BlackMage或一个RedWizard,因为一个不同的技巧和能力PaladinVS那些的FighterBlackBelt 地方组成应该发挥作用。

看它们不是不同的方法,它们是不同的对象

AFighter没有“没有法力的概念”,它是一个PlayableCharacter实例,它可能一个CharacterStats对象组成,其中MPMaxMP属性都从 开始游戏0

所以我们退一步看大局,不用写一行代码,我们形象化了事物需要如何共存,什么需要负责什么,这样游戏才能做出Paladin斜线at a Dragon:当我们分解所需的组件并弄清楚它们如何相互关联时,我们很快意识到没有必要强制组合发生在任何地方,它只是发生,出于必要!

一些快速、不完整和大致近似的类图

在一个支持类继承的语言,则可能CharacterAbility为东西基/抽象类一样FightAbilityCastSpellAbilityUseItemAbility,和其他类,各有其完全不同的实现Execute方法。在VBA中,你不能这样做,因此,你可能有一个ICharacterAbilityCommand接口,并且FightAbilityCastSpellAbilityUseItemAbility类实现它。

现在我们可以想象一个CombatController了解每个演员的一切的类:有一个KillableGameCharacter命名的实例,Red Dragon它产生 380 XP 和 1200 金币,有 a BiteAbility, a ClawAbility, a WingSpikeAbility,当然还有 a FireBreathAbility- 它CharacterStatsFireBreathAbility交易额在 600 到对我们的圣骑士造成 800 点火元素伤害。

哈!注意到?仅仅通过说出事物如何相互作用,我们就知道ICharacterAbilityCommand.Execute需要取CharacterStats执行角色的 ,以便能够计算出龙火的凶猛程度。这样我们以后就可以将FireBreathAbility用于较弱的Wyvern怪物。而且因为我们要接收一个CharacterStats物体,无论它们是圣骑士的数据,黑法师的数据,红龙的数据还是史莱姆的数据,都没有任何区别。

声音非常酷似你想在第一时间来解决这个问题-只是稍微抽象,这样你不写代码,读起来就像是龙战士的战斗副本;-)

凯恩出击!

通过CharacterEquipment影响角色的CharacterStats装备,以及在获得/装备/激活后立即将任何影响统计数据的瞬态技能烘焙到统计数据中,我们消除了需要ICharacterAbilityCommand.ExecuteCharacterStats英勇骑士/玩家以外的任何东西的需要,和CharacterStats龙/怪物的。

  • _噗,闪闪发光的多态性通过继承_ + (2认同)