Подтвердить что ты не робот

Передача аргументов конструктору в VBA

Как вы можете конструировать объекты, передающие аргументы непосредственно вашим собственным классам?

Что-то вроде этого:

Dim this_employee as Employee
Set this_employee = new Employee(name:="Johnny", age:=69)

Невозможность сделать это очень раздражает, и вы оказываетесь в грязных решениях для работы над этим.

4b9b3361

Ответ 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

Почему бы не так:

  1. В модуле класса "myClass" используйте Public Sub Init(myArguments) вместо Private Sub Class_Initialize()
  2. Instancing: Dim myInstance As New myClass: myInstance.Init myArguments