将参数传递给 VBA 中的构造函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15224113/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 15:04:59  来源:igfitidea点击:

Pass arguments to Constructor in VBA

vbaclassoopconstructorfactory

提问by bgusach

How can you construct objects passing arguments directly to your own classes?

如何构造直接将参数传递给自己的类的对象?

Something like this:

像这样的东西:

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

Not being able to do this is very annoying, and you end up with dirty solutions to work this around.

无法做到这一点非常烦人,最终你会得到肮脏的解决方案来解决这个问题。

回答by bgusach

Here's a little trick I'm using lately and brings good results. I would like to share with those who have to fight often with VBA.

这是我最近使用的一个小技巧,并带来了不错的效果。我想与那些经常与 VBA 斗争的人分享。

1.-Implement a public initiation subroutine in each of your custom classes. I call it InitiateProperties throughout all my classes. This method has to accept the arguments you would like to send to the constructor.

1.-在每个自定义类中实现一个公共启动子程序。我在所有课程中都将其称为 InitiateProperties。此方法必须接受您要发送给构造函数的参数。

2.-Create a module called factory, and create a public function with the word "Create" plus the same name as the class, and the same incoming arguments as the constructor needs. This function has to instantiate your class, and call the initiation subroutine explained in point (1), passing the received arguments. Finally returned the instantiated and initiated method.

2.-创建一个名为 factory 的模块,并创建一个带有“Create”一词的公共函数,加上与类相同的名称,以及与构造函数所需的相同传入参数。该函数必须实例化您的类,并调用第 (1) 点中解释的启动子例程,传递接收到的参数。最后返回实例化和启动的方法。

Example:

例子:

Let's say we have the custom class Employee. As the previous example, is has to be instantiated with name and age.

假设我们有自定义类 Employee。正如前面的例子, is 必须用 name 和 age 实例化。

This is the InitiateProperties method. m_name and m_age are our private properties to be set.

这是 InitiateProperties 方法。m_name 和 m_age 是我们要设置的私有属性。

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

    m_name = name
    m_age = age

End Sub

And now in the factory module:

现在在工厂模块中:

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

And finally when you want to instantiate an employee

最后当你想实例化一个员工时

Dim this_employee as Employee
Set this_employee = factory.CreateEmployee(name:="Johnny", age:=89)

Especially useful when you have several classes. Just place a function for each in the module factory and instantiate just by calling factory.CreateClassA(arguments), factory.CreateClassB(other_arguments), etc.

当您有多个类时特别有用。只需在模块工厂中为每个函数放置一个函数,并通过调用factory.CreateClassA(arguments)factory.CreateClassB(other_arguments)等来实例化。

EDIT

编辑

As stenci pointed out, you can do the same thing with a terser syntax by avoiding to create a local variable in the constructor functions. For instance the CreateEmployee function could be written like this:

正如 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

Which is nicer.

哪个更好。

回答by stenci

I use one Factorymodule that contains one (or more) constructorper class which calls the Initmember of each class.

我使用一个Factory模块,每个类包含一个(或多个)构造函数,它调用Init每个类的成员。

For example a Pointclass:

例如一个Point类:

Class Point
Private X, Y
Sub Init(X, Y)
  Me.X = X
  Me.Y = Y
End Sub

A Lineclass

一个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

And a Factorymodule:

还有一个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

One nice aspect of this approach is that makes it easy to use the factory functions inside expressions. For example it is possible to do something like:

这种方法的一个好处是可以轻松地在表达式中使用工厂函数。例如,可以执行以下操作:

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

or:

或者:

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

It's clean: the factory does very little and it does it consistently across all objects, just the creation and one Initcall on each creator.

它很干净:工厂做的很少,而且它在所有对象上始终如一地执行,只是创建和Init对每个创建者的调用。

And it's fairly object oriented: the Initfunctions are defined inside the objects.

它是相当面向对象的:Init函数是在对象内部定义的。

EDIT

编辑

I forgot to add that this allows me to create static methods. For example I can do something like (after making the parameters optional):

我忘了补充一点,这允许我创建静态方法。例如,我可以执行以下操作(在将参数设为可选之后):

NewLine.DeleteAllLinesShorterThan 10

Unfortunately a new instance of the object is created every time, so any static variable will be lost after the execution. The collection of lines and any other static variable used in this pseudo-static method must be defined in a module.

不幸的是,每次都会创建一个新的对象实例,因此执行后任何静态变量都会丢失。此伪静态方法中使用的行和任何其他静态变量的集合必须在模块中定义。

回答by Mathieu Guindon

When you export a class module and open the file in Notepad, you'll notice, near the top, a bunch of hidden attributes (the VBE doesn't display them, and doesn't expose functionality to tweak most of them either). One of them is VB_PredeclaredId:

当您导出一个类模块并在记事本中打开该文件时,您会注意到靠近顶部的一堆隐藏属性(VBE 不显示它们,也不公开调整其中大部分的功能)。其中之一是VB_PredeclaredId

Attribute VB_PredeclaredId = False

Set it to True, save, and re-import the module into your VBA project.

将其设置为True,保存并将模块重新导入到您的 VBA 项目中。

Classes with a PredeclaredIdhave a "global instance" that you get for free - exactly like UserFormmodules (export a user form, you'll see its predeclaredId attribute is set to true).

带有 a 的类PredeclaredId有一个“全局实例”,你可以免费获得——就像UserForm模块一样(导出用户表单,你会看到它的 predeclaredId 属性设置为 true)。

A lot of people just happily use the predeclared instance to store state. That's wrong - it's like storing instance state in a static class!

很多人只是高兴地使用预先声明的实例来存储状态。这是错误的 - 这就像在静态类中存储实例状态!

Instead, you leverage that default instance to implement your factory method:

相反,您可以利用该默认实例来实现您的工厂方法:

[Employeeclass]

[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

With that, you can do this:

有了这个,你可以这样做:

Dim empl As Employee
Set empl = Employee.Create("Johnny", 69)

Employee.Createis working off the default instance, i.e. it's considered a member of the type, and invoked from the default instance only.

Employee.Create正在处理默认实例,即它被视为类型的成员,并且仅从默认实例调用。

Problem is, this is also perfectly legal:

问题是,这也是完全合法的:

Dim emplFactory As New Employee
Dim empl As Employee
Set empl = emplFactory.Create("Johnny", 69)

And that sucks, because now you have a confusing API. You could use '@Descriptionannotations / VB_Descriptionattributes to document usage, but without Rubberduck there's nothing in the editor that shows you that information at the call sites.

这很糟糕,因为现在你有一个令人困惑的 API。您可以使用'@Description注释/VB_Description属性来记录使用情况,但如果没有 Rubberduck,编辑器中将无法在呼叫站点显示该信息。

Besides, the Property Letmembers are accessible, so your Employeeinstance is mutable:

此外,Property Let成员是可访问的,因此您的Employee实例是可变的

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

The trick is to make your class implement an interfacethat only exposes what needs to be exposed:

诀窍是让你的类实现一个只公开需要公开内容的接口

[IEmployeeclass]

[IEmployee类]

Option Explicit

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

And now you make EmployeeimplementIEmployee- the final class might look like this:

现在你Employee实现了IEmployee——最终的类可能是这样的:

[Employeeclass]

[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

Notice the Createmethod now returns the interface, and the interface doesn'texpose the Property Letmembers? Now calling code can look like this:

请注意该Create方法现在返回interface,并且该接口公开Property Let成员?现在调用代码可以是这样的:

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

And since the client code is written against the interface, the only members emplexposes are the members defined by the IEmployeeinterface, which means it doesn't see the Createmethod, nor the Selfgetter, nor any of the Property Letmutators: so instead of working with the "concrete" Employeeclass, the rest of the code can work with the "abstract" IEmployeeinterface, and enjoy an immutable, polymorphic object.

而且由于客户端代码是针对接口编写的,因此唯一empl公开的成员是IEmployee接口定义的成员,这意味着它看不到Create方法、Selfgetter 或任何增Property Let变器:因此,不要使用“具体”Employee类,其余代码可以与“抽象”IEmployee接口一起工作,并享受不可变的多态对象。

回答by Tomasz

Using the trick

使用技巧

Attribute VB_PredeclaredId = True

I found another more compact way:

我找到了另一种更紧凑的方式:

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

As you can see the New_ constructor is called to both create and set the private members of the class (like init) only problem is, if called on the non-static instance it will re-initialize the private member. but that can be avoided by setting a flag.

正如您所看到的,调用 New_ 构造函数来创建和设置类的私有成员(如 init),唯一的问题是,如果在非静态实例上调用,它将重新初始化私有成员。但这可以通过设置标志来避免。

回答by Larry Kavounas

Another approach

另一种方法

Say you create a class clsBitcoinPublicKey

假设您创建了一个类 clsBitcoinPublicKey

In the class module create an ADDITIONAL subroutine, that acts as you would want the real constructor to behave. Below I have named it ConstructorAdjunct.

在类模块中创建一个 ADDITIONAL 子例程,它的行为就像您希望真正的构造函数一样。下面我将其命名为 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(...)

The only penalty is the extra call, but the advantage is that you can keep everything in the class module, and debugging becomes easier.

唯一的惩罚是额外的调用,但好处是可以将所有内容都保留在类模块中,调试变得更加容易。

回答by Loook

Why not this way:

为什么不这样:

  1. In a class module ?myClass? use Public Sub Init(myArguments)instead of Private Sub Class_Initialize()
  2. Instancing: Dim myInstance As New myClass: myInstance.Init myArguments
  1. 在类模块中?myClass?使用Public Sub Init(myArguments)代替Private Sub Class_Initialize()
  2. 实例化: Dim myInstance As New myClass: myInstance.Init myArguments