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



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



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



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


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

843   5  

5 ответов:

вот небольшой трюк, который я использую в последнее время и приносит хорошие результаты. Я хотел бы поделиться с теми, кому приходится часто бороться с VBA.

1.- реализовать общедоступную подпрограмму инициализации в каждом из ваших пользовательских классов. Я называю это InitiateProperties во всех моих классах. Этот метод должен принимать аргументы, которые вы хотите отправить конструктору.

2.- создайте модуль под названием factory и создайте публичную функцию с помощью слово "Create" плюс то же имя, что и класс, и те же входящие аргументы, что и конструктор. Эта функция должна создать экземпляр класса и вызвать подпрограмму инициализации, описанную в пункте (1), передав полученные аргументы. Наконец, возвращается экземпляр и инициированный метод.

пример:

допустим, у нас есть сотрудник пользовательского класса. Как и в предыдущем примере, is должен быть создан с именем и возрастом.

это Метод InitiateProperties. m_name и m_age - это наши частные свойства, которые должны быть установлены.

Public Sub InitiateProperties(name as String, age as Integer)

    m_name = name
    m_age = age

End Sub

а теперь в Заводском модуле:

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 и создайте экземпляр, просто вызвав фабрики.CreateClassA(аргументы),фабрики.CreateClassB (other_arguments), так далее.

EDIT

как указал stenci, вы можете сделать то же самое с синтаксисом terser, избегая создавать локальную переменную в функциях конструктора. Например, функция CreateEmployee может быть написана следующим образом:

Public Function CreateEmployee(name as String, age as Integer) as Employee

    Set CreateEmployee = new Employee
    CreateEmployee.InitiateProperties name:=name, age:=age

End Function

что приятнее.

я использую один Factory модуль, который содержит один (или более) конструктор за класс, который вызывает Init члены каждого класса.

например a 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

одним из приятных аспектов этого подхода является то, что позволяет легко использовать заводские функции внутри выражений. Например можно что-то сделать например:

D = Distance(NewPoint(10, 10), NewPoint(20, 20)

или:

D = NewPoint(10, 10).Distance(NewPoint(20, 20))

это чисто: завод делает очень мало, и он делает это последовательно по всем объектам, только создание и один Init призываем каждого создатель.

и это довольно объектно-ориентированный:Init функции определяются внутри объектов.

EDIT

Я забыл добавить, что это позволяет мне создать статические методы. Например, я могу сделать что-то подобное (сделав параметры необязательными):

NewLine.DeleteAllLinesShorterThan 10

к сожалению, каждый раз создается новый экземпляр объекта, поэтому любая статическая переменная будет потеряна после выполнения. Набор строк и любая другая статическая переменная, используемая в этом псевдостатическом методе, должны быть определены в модуле.

когда вы экспортируете модуль класса и открываете файл в блокноте, вы заметите, что в верхней части есть куча скрытых атрибутов (VBE не отображает их и не предоставляет функциональность для настройки большинства из них). Один из них -VB_PredeclaredId:

Attribute VB_PredeclaredId = False

установить True сохраните и повторно импортировать модуль в проект VBA.

классы с a PredeclaredId есть "глобальный экземпляр", который вы получаете бесплатно - точно так же, как UserForm модули (экспорт формы пользователя, вы увидите, что его атрибут preforlaredid установлен в true).

многие люди просто счастливо используют экземпляр predeclared для хранения состояния. Это неправильно - это как хранить состояние экземпляра в статическом классе!

вместо этого вы используете этот экземпляр по умолчанию для реализации своего заводского метода:

[Employee класса]

'@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 аннотации / VB_Description атрибуты для использования документа, но без Rubberduck нет ничего в редакторе, который показывает вам эту информацию на сайтах вызовов.

кроме того,Property Let члены доступны, так что ваш Employee экземпляр mutable:

empl.Name = "Booba" ' Johnny no more!

фокус в том, чтобы сделать свой класс реализовать интерфейс это только выставляет то, что должно быть выставлено:

[IEmployee класса]

Option Explicit

Public Property Get Name() As String : End Property
Public Property Get Age() As Integer : End Property

и EmployeeреализоватьIEmployee - последний класс может выглядеть так:

[Employee класса]

'@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

обратите внимание на способ возвращает интерфейс, и интерфейс не разоблачение Property Let члены? Теперь вызывающий код может выглядеть так:

Dim empl As IEmployee
Set empl = Employee.Create("Immutable", 42)

а так как клиентский код написан против интерфейса, то только члены empl exposes-это члены, определенные IEmployee интерфейс, что означает, что он не видит Create метод, ни Self геттер, ни Property Let мутаторы: так вместо работы с "бетоном"Employee класс, остальная часть кода может работать с "абстрактным"IEmployee интерфейс, и наслаждаться неизменным, полиморфным объектом.

используя хитрость

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

другой подход

скажем, вы создаете класс clsBitcoinPublicKey

в модуле class создайте дополнительную подпрограмму, которая действует так, как вы хотите, чтобы действительный конструктор вел себя. Ниже я назвал его 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(...)

единственным наказанием является дополнительный вызов, но преимущество в том, что вы можете сохранить все в модуле класса, и отладка становится проще.

Comments

    Ничего не найдено.