在VBA中将参数传递给Constructor

bgu*_*ach 75 oop vba constructor factory class

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

像这样的东西:

Dim this_employee as Employee
Set this_employee = new Employee(name:="Johnny", age:=69)
Run Code Online (Sandbox Code Playgroud)

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

bgu*_*ach 109

这是我最近使用的一个小技巧并带来了好的结果.我想与那些经常与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
Run Code Online (Sandbox Code Playgroud)

现在在工厂模块中:

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
Run Code Online (Sandbox Code Playgroud)

最后,当您想要实例化员工时

Dim this_employee as Employee
Set this_employee = factory.CreateEmployee(name:="Johnny", age:=89)
Run Code Online (Sandbox Code Playgroud)

当你有几个班级时特别有用.只需在模块工厂中为每个函数放置一个函数,并通过调用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
Run Code Online (Sandbox Code Playgroud)

哪个更好.

  • 嗨,我看到了一些好处.可能是它们有些重叠.首先,您可以像标准方式一样使用构造函数,就像使用任何普通的OOP语言一样,这样可以提高清晰度.然后,每次实例化一个对象时,你保存该行来启动你的对象,这会让你少写,然后你不能忘记初始化对象,最后你的程序中有一个概念较少,这降低了复杂性. (3认同)
  • 很抱歉从死里唤醒它,但最后一段是错误的,那是*不是*“更好”的代码。将函数的返回机制(分配给标识符)视为声明的局部变量是误导和混淆的*充其量*,而不是“更好”(*看起来*像递归调用,不是吗?)。如果您想要更简洁的语法,请利用我的回答中所示的模块属性。作为奖励,你失去了负责创建几乎所有东西及其母亲的实例的*单一责任原则需要一个殴打*“工厂包”模块,并且不可避免地在任何体面的项目中变得一团糟。 (3认同)
  • 不错的解决方案!虽然我可能会将其重命名为“ factory.CreateEmployee”以减少歧义... (2认同)
  • 工厂模块相对于每个类中的 Construct 方法有什么好处?因此,您可以调用“Set employee_obj = New Employee”,然后调用“employee_obj.Construct "Johnny", 89”,并且构造内容发生在类内部。只是好奇。 (2认同)
  • 您可以使用私有变量跟踪它.你可以定义`Class_Initialize`,然后定义一个变量`m_initialized = false`.当你输入`InitiateProperties`时,检查`m_initialized`,如果是,则继续,最后将其设置为true.如果是,则引发错误或不执行任何操作.如果再次调用InitiateProperties方法,它将为true,并且不会更改对象的状态. (2认同)

ste*_*nci 33

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

例如一个Point类:

Class Point
Private X, Y
Sub Init(X, Y)
  Me.X = X
  Me.Y = Y
End Sub
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

还有一个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
Run Code Online (Sandbox Code Playgroud)

这种方法的一个很好的方面是可以很容易地在表达式中使用工厂函数.例如,可以执行以下操作:

D = Distance(NewPoint(10, 10), NewPoint(20, 20)
Run Code Online (Sandbox Code Playgroud)

要么:

D = NewPoint(10, 10).Distance(NewPoint(20, 20))
Run Code Online (Sandbox Code Playgroud)

它很干净:工厂做的很少,它在所有对象中都是一致的,只是创建和Init每个创建者的一次调用.

并且它是面向对象的:Init函数在对象内定义.

编辑

我忘了补充说这允许我创建静态方法.例如,我可以做一些事情(在使参数可选后):

NewLine.DeleteAllLinesShorterThan 10
Run Code Online (Sandbox Code Playgroud)

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

  • 这比选定的答案更清晰. (2认同)
  • 是的,组织是完全相同的,但我同意你的方式更简洁。这意味着相同,但是每个构造函数可以节省两行,这很好。如果您不介意,我将使用您的语法更新我的代码。 (2认同)

Mat*_*don 25

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

Attribute VB_PredeclaredId = False
Run Code Online (Sandbox Code Playgroud)

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

具有PredeclaredId免费获得的"全局实例"的类- 与UserForm模块完全相同(导出用户表单,您将看到其predeclaredId属性设置为true).

很多人只是愉快地使用预先声明的实例来存储状态.那是错的 - 就像在静态类中存储实例状态一样!

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

[ 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
Run Code Online (Sandbox Code Playgroud)

有了它,你可以这样做:

Dim empl As Employee
Set empl = Employee.Create("Johnny", 69)
Run Code Online (Sandbox Code Playgroud)

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

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

Dim emplFactory As New Employee
Dim empl As Employee
Set empl = emplFactory.Create("Johnny", 69)
Run Code Online (Sandbox Code Playgroud)

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

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

empl.Name = "Jane" ' Johnny no more!
Run Code Online (Sandbox Code Playgroud)

诀窍是让你的类实现一个只暴露需要暴露的东西的接口:

[ IEmployee班级]

Option Explicit

Public Property Get Name() As String : End Property
Public Property Get Age() As Integer : End Property
Run Code Online (Sandbox Code Playgroud)

现在你制作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
Run Code Online (Sandbox Code Playgroud)

请注意,该Create方法现在返回接口,并且接口公开Property Let成员?现在调用代码可能如下所示:

Dim empl As IEmployee
Set empl = Employee.Create("Immutable", 42)
Run Code Online (Sandbox Code Playgroud)

并且由于客户端代码是针对接口编写的,因此empl公开的唯一成员是IEmployee接口定义的成员,这意味着它不会看到Create方法,也不会看到Selfgetter,也不会看到任何Property Letmutator:所以不要使用"具体的" Employee类",其余代码可以使用"抽象" IEmployee接口,并享受不可变的多态对象.

  • @Matheiu Guindon - 并不是想发垃圾邮件,但近 3 个月后我又重新审视了这篇文章。从那时起,我就 OOP 一遍又一遍地阅读你的 ruby​​duck 博客,现在这个答案对我来说完全有意义。我不敢相信我在上面的评论中提出的问题。 (4认同)