Я изучал, как сделать Состав объекта в Javascript, используя Сцепное наследование, и задавался вопросом, как я могу выполнить что-то аналогичный в VBA (которое не имеет наследования).
Состав объекта: Я пытаюсь выяснить, как выполнить отношение "имеет" по сравнению с отношением "является". Я хочу иметь возможность писать простые классы поведение, где их можно использовать, комбинируя их вместе для создания более сложных классов.
Я создал простой пример, чтобы продемонстрировать, чего я хотел бы достичь.
Вот несколько примеров того, что можно использовать. Однако для этого вопроса я просто сосредоточусь на примере использования класса Fighter
.
Метод Fight
на самом деле вызывает метод Fight
в классе CanFight
. Он отлаживает сообщение и снижает выносливость на 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
Этот класс имеет два общедоступных свойства 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
Мой пример кажется сломанным, так как нет структуры в отношении того, какие свойства необходимы для его использования.
В то же время я хочу убедиться, что мои игровые персонажи могут быть гибкими, обладая своими собственными отличительными свойствами. Примеры сверху:
Fighter
использует: canFight
(выносливость)Mage
использует: canCast
(мана)Paladin
использует оба: canFight
(выносливость) и canCast
(мана)Если бы я создал класс интерфейса ICharacter
, я бы чувствовал, что он будет заблокирован, чтобы иметь все свойства для всех типов персонажей.
Мой вопрос: как мне добиться структурированного, но гибкого Сочинение, подобного этому, в VBA?
Очень похоже: stackoverflow.com/q/53873314/1188513
@MathieuGuindon Я прочитал этот пост, прежде чем задать этот вопрос. Может быть, меня смущает то, что если бы у меня был класс ICharacter
, разве мне не пришлось бы иметь все свойства, перечисленные в этом классе? Тогда у моего Fighter
тоже будет Mana
и все остальное, о чем я могу подумать заранее? Поправьте меня, если я ошибаюсь! Я действительно борюсь по какой-то причине в понимании интерфейсов.
Fighter
наличие свойства Mana
похоже на Cat
наличие метода Purr
, который интерфейс IAnimal
не предоставляет: если остальная часть кода работает с ICharacter
, приведение к какому-то классу Fighter
приводит к тому, что LSP терпит поражение =) @SMeaden CanFight
сорт, целью которого является поведение сочинять — немного похожее на то, как AIPlayer получает IGameStrategy в моей игре Морской бой.
@MathieuGuindon =) будет ли ICharacter
содержать только базовые свойства, которые я могу придумать для всех возможных персонажей (Name
, Stamina
, Health
)? А какое уникальное свойство есть в конкретных классах? Если это так, то что, если бы у меня был класс CanCast
, который истощает Mana
. Это не сможет работать с ICharacter
, поскольку оно не содержит Mana
.
У меня есть дела прямо сейчас, но я обязательно свяжусь с вами (если не с ответом здесь, то я прокомментирую со ссылкой на новую статью в блоге) - но да, я бы ICharacter
со всеми основные свойства персонажа.
Я просто добавил пример с дополнительными классами в Гитхаб на случай, если это поможет получить лучшую картину.
Но ... вы можете сделать своего рода полиморфизм в VBA: vitoshacademy.com/vba-interfaces-in-vba-как-и-почему также, пожалуйста, объясните, что вы подразумеваете под «композицией объекта», было бы лучше объяснить вашу потребность, просто используя ссылку.
@pdem Я внес больше ясности в то, что я имею в виду под композицией объектов. Я также добавил примечание о том, почему я могу нервничать, используя интерфейс, так как мне кажется, что он заставит моих персонажей делиться всеми теми же свойствами, включая те, которые я бы не хотел, чтобы они имели.
@MathieuGuindon просто смотрит, будет ли статья в блоге или ответ на столе когда-нибудь в будущем? Никакого давления, просто не получая лучшего понимания, которое я надеялся получить от этого вопроса.
Спасибо за напоминание - я начал работать над этим, отвлекся, и оно просто исчезло ... Сейчас иду с ответом.
@ Роберт, мне действительно нравится твой код. Однако я не уверен, что это можно назвать композицией. На самом деле, я думаю, вы обнаружили своего рода шаблон «микширования» (или, может быть, даже шаблон посетителя), так что поздравляю с этим. Вот композиция, как я ее вижу.
Таким образом, с помощью трюка с членом по умолчанию мы отправляем свойство Base, которое разрешает доступ ко всем методам базовых классов (но не к частному состоянию, что, ИМХО, хорошо). Но поскольку писать 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
Не будет ли выставление Fighter
в качестве базового класса в Paladin
, а затем выставление всего Fighter
? Кажется, что это тесно связывает их вместе. Это сделало бы Paladin
все, что есть в боевике, и даже больше! На самом деле у Fighter
, вероятно, есть свои преимущества, такие как больше атак и различные свойства и методы. На самом деле все, что я хотел бы показать, это метод Fight
в этом примере.
хорошо, я начинаю лучше понимать, что вы хотите. Возможно, именно поэтому вы отметили вопрос «Внедрение зависимостей». Недавно я посетил это в своем блоге exceldevelopmentplatform.blogspot.com/2019/05/…
@SMeaden Я так рад, что нашел ваш блог. Любить это!
Это очень сложный вопрос, чтобы с пользой ответить ИМО, в основном потому, что модель сильно упрощена.
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
, на основе ряда факторов, включающих как fighterState
, так и targetState
. Таким образом, лучшим названием для него могло бы быть Attack
, и мы можем предположить, что fighterState
содержит информацию о том, какое оружие в настоящее время экипировано и является ли это оружие «рубящим», «протыкающим», «раздавливающим» или просто «ударяющим». цель. Точно так же можно предположить, что targetState
содержит информацию о том, какие элементы брони экипированы на цели, и способно ли и каким образом это снаряжение отражать/обнулять или уменьшать количество получаемого урона. С такой механикой мы можем даже нанести PoisonBlade
порез цели, чтобы нанести рассчитанный урон в 76 HP, плюс повторяющийся урон от яда в 8 HP каждый ход, если только цель не потребляет (или не получает иным образом) предмет Antidote
для лечения своего отравления. .
Теперь не имеет значения, является ли боец Fighter
, Paladin
или BlackMage
: игровая механика нуждается не в разных свойствах и членах в каждом классе персонажей. На самом деле игровой механике все равно, какие классы персонажей, механика одинакова для всех независимо от того: Fight
— это команда пользовательского интерфейса, такая же способность, как и любая другая. Персонаж BlackMage
и у него нет оружия? Отбиться - и нанести 1 урона ХП, если есть. Персонаж является Paladin
и может решить «сразиться» с или «кастом»? Команды пользовательского интерфейса, а не дизайн классов персонажей.
Мы разрабатываем модули классов не совсем так, как в учебниках с Animal
, Cat
и Dog
, где Dog
говорит "гав", а Cat
говорит "мяу", и весь код вызывал Animal.Talk
в обоих случаях и пуф, сверкающий полиморфизм- сквозное наследство!
Я имею в виду, что в реальном коде не используются классы Cat
и Dog
, точно так же, как реальная JRPG не определяет разные типы для каждого возможного класса персонажей в игре — Enum
, может быть, и разные активы и ресурсы, однозначно; добавление нового класса персонажей в вашу игру должно быть добавлением данные, а не кода. Но игровой механике не нужно беспокоиться о том, насколько Paladdin
может отличаться от BlackMage
или RedWizard
, потому что разные навыки и способности Paladin
отличаются от навыков и способностей Fighter
или BlackBelt
являются, где сочинение должен вступить в игру.
Смотрите, они не разные методы, они разные объекты.
Fighter
не имеет «понятия маны», это экземпляр PlayableCharacter
, который может быть составленный объекта CharacterStats
, где свойства MP
и MaxMP
начинают игру с 0
.
Итак, мы делаем шаг назад и смотрим на общую картину, и не написав ни строчки кода мы визуализируем, как вещи должны сосуществовать и что за что отвечать, чтобы игра могла сделать Paladin
косую черту Dragon
: как мы разбираем необходимые компоненты и выясняем, как они все связаны друг с другом, мы быстро понимаем, что нет необходимости, чтобы композиция сила происходила где угодно, это просто случается по необходимости!
В языке, который поддерживает наследование классов, у вас может быть CharacterAbility
в качестве базового/абстрактного класса для таких вещей, как FightAbility
, CastSpellAbility
, UseItemAbility
и других классов, каждый из которых имеет совершенно разные реализации для своего метода Execute
. В VBA вы не можете этого сделать, поэтому вместо этого у вас может быть интерфейс ICharacterAbilityCommand
и классы FightAbility
, CastSpellAbility
, UseItemAbility
, которые его реализуют.
Теперь мы можем представить класс CombatController
, который знает все о каждом актере: есть экземпляр KillableGameCharacter
по имени Red Dragon
, который дает 380 XP и 1200 золота, имеет BiteAbility
, ClawAbility
, WingSpikeAbility
и, конечно же, FireBreathAbility
- его CharacterStats
такие что его FireBreathAbility
нанесет нашему паладину от 600 до 800 урона от стихии огня.
Ха! Заметил это? Просто произнося, как вещи взаимодействуют друг с другом, мы знаем, что ICharacterAbilityCommand.Execute
нужно взять CharacterStats
исполняющего персонажа, чтобы иметь возможность вычислить, насколько свиреп этот драконий огонь. Таким образом, мы можем позже повторно использовать FireBreathAbility
для более слабого Wyvern
монстра. И поскольку мы принимаем объект CharacterStats
, не имеет значения, являются ли они статистикой Паладин, статистикой Черный маг, статистикой Красный дракон или слизь.
И это звучит очень точно так же, как проблема, которую вы пытались решить в первую очередь, - просто немного более абстрактно, так что вы не пишете код, который читается как стенограмма битвы воин-Дракон ;-)
За счет того, что CharacterEquipment
влияет на CharacterStats
персонажа при экипировке, а любые временные навыки, влияющие на характеристики, запекаются в характеристиках, как только они приобретаются/экипируются/активируются, мы устраняем необходимость в том, чтобы ICharacterAbilityCommand.Execute
требовалось что-либо, кроме CharacterStats
доблестного рыцарь/игрок и CharacterStats
дракона/монстра.
Я не могу перестать смеяться, как вы описали Красного Дракона со всеми его методами, я изобразил массивного зверя, а не дракона на картинке LOL
Попробуйте это exceldevelopmentplatform.blogspot.com/2018/09/…