有没有一种方法可以进行类似于串联继承的对象组合?

时间:2019-05-28 18:10:06

标签: vba oop design-patterns dependency-injection

我一直在学习如何使用连接继承在Javascript中进行Object Composition,并且想知道如何在VBA中完成相似(具有继承性。

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


测试模块

这只是Fighter类的使用示例。 Fight方法实际上是在Fight类中调用CanFight方法。它调试一条消息并将耐力降低1。

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

End Sub

战斗机舱

此类具有两个公共属性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

CanFight班

这是可以用于其他字符的类。 示例是Paladin类,可能还需要具有战斗能力

此布局的明显问题是stateObject。除非他们查看代码,否则用户不会知道它需要具有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

我的示例感到很混乱,因为就使用它所需的属性而言,没有适当的结构。同时,我想确保我的游戏角色可以灵活地拥有自己独特的属性(例如,Paladin类可能具有Mana属性)。

我的问题是如何在VBA中实现这样的结构化组成

2 个答案:

答案 0 :(得分:3)

要有效回答IMO,这是一个非常困难的问题,主要是因为该模型大大简化了。

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

如果我制作了一个野蛮人战士,并用一把巨大的战锤进行了战斗,那么“大刀阔斧!”听起来有些轻描淡写。谁是敌人?我知道这全是理论上的并且是简化的(对吗?),但是如果我们在谈论游戏,那么敌人实际上需要在某一时刻死亡,不是吗?

如果我们研究传统的JRPG如何做到这一点,则Fight方法将需要知道战斗机及其目标的状态(现在让目标保持单一),因此首先,它可能像这样:

Public Sub Fight(ByVal fighterState As Object, ByVal targetState As Object)
    '...
End Sub

Fight方法的基本作用是根据涉及targetState和{{1}的多种因素,评估/实施fighterState上需要发生的更改}。因此,更合适的名称可能是targetState,我们可以假设Attack包含有关当前装备的武器以及该武器是否“斜线”,“穿刺”,“击碎”,或干脆“击中”目标。同样,可以假设fighterState包含有关在目标上装备了哪些装甲,以及该设备是否以及如何能够偏转/抵消或减少所受到的伤害的信息。有了这样的机制,我们甚至可以targetState削减目标以造成计算出的76点HP伤害,再加上每回合会重复产生8点HP毒害,除非目标消耗(或给予){{1 }}项目来治愈他们的毒药状态。

现在,无论战斗机是PoisonBlade还是Antidote还是Fighter都没有区别:游戏机制所需的不是每个角色的不同属性和成员类。实际上,游戏机制并不太在乎角色类是什么,机制对每个人都是相同的,无论:Paladin是一个UI命令,与其他功能一样。角色是BlackMage,没有装备武器吗?战斗-造成1点HP伤害(如果有)。字符是Fight,可以决定“战斗” 还是“铸造”? UI命令,而不是角色类设计。

我们如何设计类模块并不像在教科书中使用BlackMagePaladinAnimal那样,其中Cat变成“ woof”而{{1 }}变成“喵”,在这两种情况下,所有的代码都被调用Dog,并且po发出光亮的继承性多态性!

我要说的是,真实世界的代码没有做DogCat类,就像真实世界的JRPG游戏会为每个可能的角色定义不同的类型一样游戏中的类-肯定是Animal.Talk以及不同的资产和资源;在游戏中添加新的角色类应该添加 data ,而不是代码。但是游戏机制不必担心CatDogEnum的不同之处,因为Paladdin的技能和能力不同与BlackMageRedWizard 的地方,而组成应该发挥作用。

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

Paladin没有“没有法术力的概念”,它是一个Fighter实例,可以由BlackBelt对象的组成 FighterPlayableCharacter属性从CharacterStats开始游戏。

因此,我们退后一步,看看大局,然后无需编写任何代码,我们就可以直观地看到事物如何共存以及什么事物需要对什么事物负责,以便为了使游戏能够在MP处产生MaxMP的斜线:当我们分解所需的组件并弄清它们之间的相互关系时,我们很快意识到不需要<

some quick, incomplete and roughly approximate class diagram

在支持类继承的语言中,您可能将0作为PaladinDragonCharacterAbility和其他类之类的基类/抽象类,每个方法的FightAbility方法都有不同的实现。在VBA中,您无法执行此操作,因此您可能拥有一个CastSpellAbility接口以及实现该接口的UseItemAbilityExecuteICharacterAbilityCommand类。

现在,我们可以想象一个FightAbility类,它了解每个演员的所有知识:有一个名为CastSpellAbility的{​​{1}}实例,它产生380 XP和1200金币,具有{{1 }},UseItemAbilityCombatController,当然还有KillableGameCharacter-其Red Dragon使其BiteAbility会造成600到800点的射击-对我们的圣骑士造成了根本的损害。

哈!注意到那个?仅通过说出事物之间如何相互作用,我们就知道ClawAbility需要采用执行角色的WingSpikeAbility才能计算出龙火的凶猛程度。这样一来,我们以后就可以将FireBreathAbility重新用于弱小的CharacterStats怪物。并且由于我们要使用FireBreathAbility对象,因此无论它们是 Paladin 的统计数据还是 Black Mage 的统计数据, 红龙史莱姆的那些,没有任何区别。

而且 听起来很像您一开始要解决的问题-稍微抽象一点,这样您就不会编写看起来像 Dragon的代码战士战斗记录;-)

Kain Attacks!

通过让ICharacterAbilityCommand.Execute影响角色的CharacterStats的装备,并且在获得,装备/激活这些属性后,所有会影响到该属性的瞬态技能都会转化为属性,因此我们无需FireBreathAbility需要英勇的骑士/玩家的Wyvern和龙/怪物的CharacterStats之外的其他东西。

答案 1 :(得分:1)

@Robert,我实际上喜欢您的代码。但是,我不确定它是否符合组成条件。实际上,我认为您已经发现了一种“混合”模式,某种形式(甚至是访客模式),因此对此表示祝贺。这是我所看到的构图。

因此,使用默认的成员技巧,我们提供了Base属性,该属性允许访问所有base的class方法(但不能访问私有状态,这是一件好事,恕我直言)。但是由于在代码中编写foo.Base.Bar很丑陋,因此我们花了很多技巧使Base属性成为默认成员,因此可以用一对方括号将其替换。因此,合成看起来不那么难看,不需要子类来复制所有基类的方法。

'* Test Module
Private Sub StartGame2()

    Dim oPaladin As Paladin
    Set oPaladin = New Paladin
    oPaladin().Name = "Pal"

    oPaladin().Fight '-> Pal slashes at the foe!
    Debug.Print oPaladin().Stamina '-> 99
    Debug.Print oPaladin.Mana
End Sub

格斗班

Option Explicit

Private pName As String
Private pStamina As Long

Private Sub Class_Initialize()
    pStamina = 100
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

Public Sub Fight()
    Debug.Print Me.Name & " slashes at the foe!"
    Me.Stamina = Me.Stamina - 1
End Sub

将Paladin.cls类导出到磁盘,并进行了修改以提取默认的成员技巧。

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Paladin"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Private moBase As Fighter

'* To do the default member trick
'* 1) Export this module to disk;
'* 2) load into text editor;
'* 3) uncomment line with text Attribute Item.VB_UserMemId = 0 ;
'* 4) save the file back to disk
'* 5) remove or rename original file from VBA project to make room
'* 6) Re-import saved file

Private Sub Class_Initialize()
    Set moBase = New Fighter
End Sub

Public Function Base() As Fighter
    Attribute Item.VB_UserMemId = 0
    Set Base = moBase
End Function


Public Function Mana() As String
    Mana = "I don't know what Mana even means"
End Function