Передача аргументов конструктору в VBA
Как вы можете конструировать объекты, передающие аргументы непосредственно вашим собственным классам?
Что-то вроде этого:
Dim this_employee as Employee
Set this_employee = new Employee(name:="Johnny", age:=69)
Невозможность сделать это очень раздражает, и вы оказываетесь в грязных решениях для работы над этим.
Ответы
Ответ 1
Вот небольшой трюк, который я использую в последнее время и приносит хорошие результаты. Я хотел бы поделиться с теми, кому приходится часто сражаться с VBA.
1.- Внедрить общедоступную подпрограмму инициации в каждом из ваших пользовательских классов. Я называю его InitiateProperties во всех моих классах. Этот метод должен принимать аргументы, которые вы хотите отправить конструктору.
2.- Создайте модуль под названием factory и создайте публичную функцию со словом "Создать" с тем же именем, что и класс, и теми же входящими аргументами, что и конструктор. Эта функция должна создать экземпляр вашего класса и вызвать подпрограмму инициации, описанную в пункте (1), передав полученные аргументы. Наконец, вернул метод, инициированный и инициированный.
Пример:
Скажем, у нас есть пользовательский класс Employee. Как предыдущий пример, должен быть создан экземпляр с именем и возрастом.
Это метод InitiateProperties. m_name и m_age - это наши частные свойства, которые нужно установить.
Public Sub InitiateProperties(name as String, age as Integer)
m_name = name
m_age = age
End Sub
И теперь в модуле factory:
Public Function CreateEmployee(name as String, age as Integer) as Employee
Dim employee_obj As Employee
Set employee_obj = new Employee
employee_obj.InitiateProperties name:=name, age:=age
set CreateEmployee = employee_obj
End Function
И наконец, когда вы хотите создать экземпляр сотрудника
Dim this_employee as Employee
Set this_employee = factory.CreateEmployee(name:="Johnny", age:=89)
Особенно полезно, когда у вас есть несколько классов. Просто поместите функцию для каждого из модулей factory и создайте экземпляр, просто позвонив factory.CreateClassA(аргументы), factory.CreateClassB(other_arguments) и т.д..
ИЗМЕНИТЬ
Как указал Stenci, вы можете сделать то же самое с синтаксисом терминов, избегая создания локальной переменной в конструкторских функциях. Например, функция CreateEmployee может быть написана следующим образом:
Public Function CreateEmployee(name as String, age as Integer) as Employee
Set CreateEmployee = new Employee
CreateEmployee.InitiateProperties name:=name, age:=age
End Function
Что лучше.
Ответ 2
Я использую один модуль Factory
, который содержит один (или более) конструктор для каждого класса, который вызывает член Init
каждого класса.
Например, класс Point
:
Class Point
Private X, Y
Sub Init(X, Y)
Me.X = X
Me.Y = Y
End Sub
A Line
класс
Class Line
Private P1, P2
Sub Init(Optional P1, Optional P2, Optional X1, Optional X2, Optional Y1, Optional Y2)
If P1 Is Nothing Then
Set Me.P1 = NewPoint(X1, Y1)
Set Me.P2 = NewPoint(X2, Y2)
Else
Set Me.P1 = P1
Set Me.P2 = P2
End If
End Sub
И модуль Factory
:
Module Factory
Function NewPoint(X, Y)
Set NewPoint = New Point
NewPoint.Init X, Y
End Function
Function NewLine(Optional P1, Optional P2, Optional X1, Optional X2, Optional Y1, Optional Y2)
Set NewLine = New Line
NewLine.Init P1, P2, X1, Y1, X2, Y2
End Function
Function NewLinePt(P1, P2)
Set NewLinePt = New Line
NewLinePt.Init P1:=P1, P2:=P2
End Function
Function NewLineXY(X1, Y1, X2, Y2)
Set NewLineXY = New Line
NewLineXY.Init X1:=X1, Y1:=Y1, X2:=X2, Y2:=Y2
End Function
Одним из приятных аспектов этого подхода является упрощение использования функций factory внутри выражений. Например, можно сделать что-то вроде:
D = Distance(NewPoint(10, 10), NewPoint(20, 20)
или
D = NewPoint(10, 10).Distance(NewPoint(20, 20))
Он чист: factory делает очень мало, и он последовательно выполняет все объекты, только создание и один вызов Init
для каждого создателя.
И он довольно объектно-ориентированный: функции Init
определены внутри объектов.
ИЗМЕНИТЬ
Я забыл добавить, что это позволяет мне создавать статические методы. Например, я могу сделать что-то вроде (после внесения параметров необязательно):
NewLine.DeleteAllLinesShorterThan 10
К сожалению, каждый экземпляр объекта создается каждый раз, поэтому любая статическая переменная будет потеряна после выполнения. Набор строк и любая статическая переменная, используемая в этом псевдостатическом методе, должны быть определены в модуле.
Ответ 3
Когда вы экспортируете модуль класса и открываете файл в Блокноте, вы заметите в верхней части группу скрытых атрибутов (VBE не отображает их и не предоставляет возможности для настройки большинства из них). Одним из них является VB_PredeclaredId
:
Attribute VB_PredeclaredId = False
Установите для него значение True
, сохраните и повторно импортируйте модуль в проект VBA.
Классы с PredeclaredId
имеют "глобальный экземпляр", который вы получаете бесплатно - точно так же, как и модули UserForm
(экспортируйте пользовательскую форму, вы увидите, что для его атрибута beforeclaredId установлено значение true).
Многие люди просто с радостью используют предварительно объявленный экземпляр для хранения состояния. Это неправильно - это как сохранение состояния экземпляра в статическом классе!
Вместо этого вы используете этот экземпляр по умолчанию для реализации фабричного метода:
[Employee
class]
'@PredeclaredId
Option Explicit
Private Type TEmployee
Name As String
Age As Integer
End Type
Private this As TEmployee
Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As Employee
With New Employee
.Name = emplName
.Age = emplAge
Set Create = .Self 'returns the newly created instance
End With
End Function
Public Property Get Self() As Employee
Set Self = Me
End Property
Public Property Get Name() As String
Name = this.Name
End Property
Public Property Let Name(ByVal value As String)
this.Name = value
End Property
Public Property Get Age() As String
Age = this.Age
End Property
Public Property Let Age(ByVal value As String)
this.Age = value
End Property
С этим вы можете сделать это:
Dim empl As Employee
Set empl = Employee.Create("Johnny", 69)
Employee.Create
работает с экземпляром по умолчанию, т.е. считается членом типа и вызывается только из экземпляра по умолчанию.
Проблема в том, что это также совершенно законно:
Dim emplFactory As New Employee
Dim empl As Employee
Set empl = emplFactory.Create("Johnny", 69)
И это отстой, потому что теперь у вас запутанный API. Вы можете использовать атрибуты '@Description
annotations/VB_Description
для документирования, но без Rubberduck в редакторе нет ничего, что показывало бы эту информацию на сайтах вызовов.
Кроме того, члены Property Let
доступны, поэтому ваш экземпляр Employee
изменчив:
empl.Name = "Jane" ' Johnny no more!
Хитрость заключается в том, чтобы ваш класс реализовал интерфейс, который предоставляет только то, что необходимо показать:
[IEmployee
class]
Option Explicit
Public Property Get Name() As String : End Property
Public Property Get Age() As Integer : End Property
И теперь вы заставляете Employee
реализовывать IEmployee
- конечный класс может выглядеть так:
[Employee
class]
'@PredeclaredId
Option Explicit
Implements IEmployee
Private Type TEmployee
Name As String
Age As Integer
End Type
Private this As TEmployee
Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As IEmployee
With New Employee
.Name = emplName
.Age = emplAge
Set Create = .Self 'returns the newly created instance
End With
End Function
Public Property Get Self() As IEmployee
Set Self = Me
End Property
Public Property Get Name() As String
Name = this.Name
End Property
Public Property Let Name(ByVal value As String)
this.Name = value
End Property
Public Property Get Age() As String
Age = this.Age
End Property
Public Property Let Age(ByVal value As String)
this.Age = value
End Property
Private Property Get IEmployee_Name() As String
IEmployee_Name = Name
End Property
Private Property Get IEmployee_Age() As Integer
IEmployee_Age = Age
End Property
Обратите внимание, что метод Create
теперь возвращает интерфейс, а интерфейс не предоставляет члены Property Let
? Теперь вызывающий код может выглядеть так:
Dim empl As IEmployee
Set empl = Employee.Create("Immutable", 42)
И поскольку клиентский код написан для интерфейса, единственные члены, которые empl
предоставляет, являются членами, определенными интерфейсом IEmployee
, что означает, что он не видит ни метод Create
, ни метод получения Self
и ни один из мутаторов Property Let
: поэтому вместо работы с "конкретным" классом Employee
остальная часть кода может работать с "абстрактным" интерфейсом IEmployee
и наслаждаться неизменным полиморфным объектом.
Ответ 4
Используя трюк
Attribute VB_PredeclaredId = True
Я нашел другой более компактный способ:
Option Explicit
Option Base 0
Option Compare Binary
Private v_cBox As ComboBox
'
' Class creaor
Public Function New_(ByRef cBox As ComboBox) As ComboBoxExt_c
If Me Is ComboBoxExt_c Then
Set New_ = New ComboBoxExt_c
Call New_.New_(cBox)
Else
Set v_cBox = cBox
End If
End Function
Как вы можете видеть, конструктор New_ вызывается как для создания, так и для установки закрытых членов класса (например, init), единственная проблема заключается в том, что при вызове в нестатическом экземпляре он будет повторно инициализировать закрытый член. но этого можно избежать, установив флаг.
Ответ 5
Другой подход
Предположим, что вы создаете класс clsBitcoinPublicKey
В модуле класса создайте ДОПОЛНИТЕЛЬНУЮ подпрограмму, которая действует так, как вы бы хотели, чтобы реальный конструктор вел себя. Ниже я назвал его ConstructorAdjunct.
Public Sub ConstructorAdjunct(ByVal ...)
...
End Sub
From the calling module, you use an additional statement
Dim loPublicKey AS clsBitcoinPublicKey
Set loPublicKey = New clsBitcoinPublicKey
Call loPublicKey.ConstructorAdjunct(...)
Единственное наказание - дополнительный вызов, но преимущество в том, что вы можете хранить все в модуле класса, а отладка становится проще.
Ответ 6
Почему бы не так:
- В модуле класса "myClass" используйте
Public Sub Init(myArguments)
вместо Private Sub Class_Initialize()
- Instancing:
Dim myInstance As New myClass: myInstance.Init myArguments