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)
哪个更好.
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)
不幸的是,每次都会创建一个新的对象实例,因此执行后任何静态变量都将丢失.必须在模块中定义此伪静态方法中使用的行集合和任何其他静态变量.
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接口,并享受不可变的多态对象.