将参数传递给 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
Pass arguments to Constructor in VBA
提问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 Factory
module that contains one (or more) constructorper class which calls the Init
member of each class.
我使用一个Factory
模块,每个类包含一个(或多个)构造函数,它调用Init
每个类的成员。
For example a Point
class:
例如一个Point
类:
Class Point
Private X, Y
Sub Init(X, Y)
Me.X = X
Me.Y = Y
End Sub
A Line
class
一个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 Factory
module:
还有一个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 Init
call on each creator.
它很干净:工厂做的很少,而且它在所有对象上始终如一地执行,只是创建和Init
对每个创建者的调用。
And it's fairly object oriented: the Init
functions 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 PredeclaredId
have a "global instance" that you get for free - exactly like UserForm
modules (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:
相反,您可以利用该默认实例来实现您的工厂方法:
[Employee
class]
[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.Create
is 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 '@Description
annotations / VB_Description
attributes 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 Let
members are accessible, so your Employee
instance 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:
诀窍是让你的类实现一个只公开需要公开内容的接口:
[IEmployee
class]
[IEmployee
类]
Option Explicit
Public Property Get Name() As String : End Property
Public Property Get Age() As Integer : End Property
And now you make Employee
implementIEmployee
- the final class might look like this:
现在你Employee
实现了IEmployee
——最终的类可能是这样的:
[Employee
class]
[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 Create
method now returns the interface, and the interface doesn'texpose the Property Let
members? 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 empl
exposes are the members defined by the IEmployee
interface, which means it doesn't see the Create
method, nor the Self
getter, nor any of the Property Let
mutators: so instead of working with the "concrete" Employee
class, the rest of the code can work with the "abstract" IEmployee
interface, and enjoy an immutable, polymorphic object.
而且由于客户端代码是针对接口编写的,因此唯一empl
公开的成员是IEmployee
接口定义的成员,这意味着它看不到Create
方法、Self
getter 或任何增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:
为什么不这样:
- In a class module ?myClass? use
Public Sub Init(myArguments)
instead ofPrivate Sub Class_Initialize()
- Instancing:
Dim myInstance As New myClass: myInstance.Init myArguments
- 在类模块中?myClass?使用
Public Sub Init(myArguments)
代替Private Sub Class_Initialize()
- 实例化:
Dim myInstance As New myClass: myInstance.Init myArguments