我一直在学习如何使用连接继承在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
此类具有两个公共属性Name
和Stamina
。
该类还包含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
这是可以用于其他字符的类。 示例是Paladin
类,可能还需要具有战斗能力。
此布局的明显问题是state
是Object
。除非他们查看代码,否则用户不会知道它需要具有Stamina
和Name
属性。
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中实现这样的结构化组成?
答案 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命令,而不是角色类设计。
我们如何设计类模块并不像在教科书中使用BlackMage
和Paladin
和Animal
那样,其中Cat
变成“ woof”而{{1 }}变成“喵”,在这两种情况下,所有的代码都被调用Dog
,并且po发出光亮的继承性多态性!
我要说的是,真实世界的代码没有做Dog
和Cat
类,就像真实世界的JRPG游戏会为每个可能的角色定义不同的类型一样游戏中的类-肯定是Animal.Talk
以及不同的资产和资源;在游戏中添加新的角色类应该添加 data ,而不是代码。但是游戏机制不必担心Cat
与Dog
或Enum
的不同之处,因为Paladdin
的技能和能力不同与BlackMage
或RedWizard
是的地方,而组成应该发挥作用。
看到它们不是不同的方法,它们是不同的对象。
Paladin
没有“没有法术力的概念”,它是一个Fighter
实例,可以由BlackBelt
对象的组成 Fighter
和PlayableCharacter
属性从CharacterStats
开始游戏。
因此,我们退后一步,看看大局,然后无需编写任何代码,我们就可以直观地看到事物如何共存以及什么事物需要对什么事物负责,以便为了使游戏能够在MP
处产生MaxMP
的斜线:当我们分解所需的组件并弄清它们之间的相互关系时,我们很快意识到不需要<
在支持类继承的语言中,您可能将0
作为Paladin
,Dragon
,CharacterAbility
和其他类之类的基类/抽象类,每个方法的FightAbility
方法都有不同的实现。在VBA中,您无法执行此操作,因此您可能拥有一个CastSpellAbility
接口以及实现该接口的UseItemAbility
,Execute
,ICharacterAbilityCommand
类。
现在,我们可以想象一个FightAbility
类,它了解每个演员的所有知识:有一个名为CastSpellAbility
的{{1}}实例,它产生380 XP和1200金币,具有{{1 }},UseItemAbility
,CombatController
,当然还有KillableGameCharacter
-其Red Dragon
使其BiteAbility
会造成600到800点的射击-对我们的圣骑士造成了根本的损害。
哈!注意到那个?仅通过说出事物之间如何相互作用,我们就知道ClawAbility
需要采用执行角色的WingSpikeAbility
才能计算出龙火的凶猛程度。这样一来,我们以后就可以将FireBreathAbility
重新用于弱小的CharacterStats
怪物。并且由于我们要使用FireBreathAbility
对象,因此无论它们是 Paladin 的统计数据还是 Black Mage 的统计数据, 红龙或史莱姆的那些,没有任何区别。
而且 听起来很像您一开始要解决的问题-稍微抽象一点,这样您就不会编写看起来像 Dragon的代码战士战斗记录;-)
通过让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