如何构造将参数直接传递给自己的类的对象?
这样的事情:
Dim this_employee as Employee
Set this_employee = new Employee(name:="Johnny", age:=69)
无法做到这一点非常烦人,你最终会得到一些肮脏的解决方案来解决这个问题。
答案 0 :(得分:104)
这是我最近使用的一个小技巧并带来了好的结果。我想与那些经常与VBA打架的人分享。
1 .- 在每个自定义类中实现公共启动子例程。我在所有课程中称它为InitiateProperties。此方法必须接受您要发送给构造函数的参数。
2 .- 创建一个名为factory的模块,并创建一个公共函数,其单词“Create”加上与该类相同的名称,以及与构造函数需要的相同的传入参数。此函数必须实例化您的类,并调用点(1)中解释的启动子例程,传递接收的参数。最后返回实例化和启动的方法。
示例:
假设我们有自定义类Employee。如前面的示例所示,必须使用名称和年龄进行实例化。
这是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(arguments), factory.CreateClassB(other_arguments)等实例化。
正如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
哪个更好。
答案 1 :(得分:31)
我使用一个Factory
模块,每个类包含一个(或多个)构造函数,调用每个类的Init
成员。
例如Point
类:
Class Point
Private X, Y
Sub Init(X, Y)
Me.X = X
Me.Y = Y
End Sub
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
函数在对象内定义。
修改强>
我忘了补充说这允许我创建静态方法。例如,我可以做一些事情(在使参数可选后):
NewLine.DeleteAllLinesShorterThan 10
不幸的是每次都会创建一个新的对象实例,因此执行后任何静态变量都会丢失。必须在模块中定义此伪静态方法中使用的行集合和任何其他静态变量。
答案 2 :(得分:17)
导出类模块并在记事本中打开文件时,您会注意到顶部附近有一堆隐藏属性(VBE不会显示它们,也不会显示它们调整其中大部分的功能)。其中之一是VB_PredeclaredId
:
Attribute VB_PredeclaredId = False
将其设置为True
,将模块保存并重新导入VBA项目。
具有PredeclaredId
的类具有"全局实例"您可以免费获得 - 与UserForm
模块完全相同(导出用户表单,您将看到其predeclaredId属性设置为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
注释/ VB_Description
属性来记录使用情况,但如果没有Rubberduck,编辑器中的任何内容都不会在呼叫网站上显示该信息。
此外,Property Let
成员可以访问,因此您的Employee
实例可变:
empl.Name = "Booba" ' 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
接口定义的成员,这意味着它没有看到{{1} }方法,也不是Create
getter,也不是任何Self
mutator:所以不要使用"具体" Property Let
类,其余代码可以使用" abstract" Employee
接口,享受不可变的多态对象。
答案 3 :(得分:1)
使用技巧
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),唯一的问题是,如果在非静态实例上调用它将重新初始化私有成员。但这可以通过设置一个标志来避免。
答案 4 :(得分:-1)
另一种方法
假设您创建了一个类clsBitcoinPublicKey
在类模块中创建一个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(...)
唯一的惩罚是额外的调用,但优点是你可以保留类模块中的所有内容,并且调试变得更容易。
答案 5 :(得分:-2)
为什么不这样:
Public Sub Init(myArguments)
代替Private Sub Class_Initialize()
Dim myInstance As New myClass: myInstance.Init myArguments