VBA设计模式

VBA设计模式之工厂方法

对象之间有4种关系:

  1. 聚合关系
    在这里插入图片描述
    A has a B,例如飞机场 has a 飞机。

  2. 组合关系
    在这里插入图片描述
     A是由B组成,A包含B,B是A的一部分,则表示为A has B,例如:飞机 has a 发动机。

  3. 继承关系
    在这里插入图片描述
     A派生了B,B是A的一种,A是B的泛化,则表示B is a A。例如: 波音777 is a 飞机。

  4. 依赖关系
    在这里插入图片描述
     A依赖B,A使用B,则表示为A use a B。例如:飞机 use a 飞行员。

VBA面向对象的机制

 在VBA中,没有继承机制,但是没有关系,因为可以使用组合来弥补。面向对象编程的关键是类(Class),因类是对象的蓝图,这是OOP的关键。

标题面向对象的四个特性:(OOP)

  1. Abstraction 抽象性
     编写代码其实就是一个抽象的过程,函数抽象出一系列可执行操的作。一个模块抽象了一组相关的操作,类抽象了一组数据和相关操作,甚至变量也是一种抽象。
     结构清晰的代码在单一抽象层面操作,并调用其它封装好的功能,在更高层面上我们不关心底层的实现细节。
     类是一个重要的抽象:类定义了对象的模板,对象封装数据并公布对数据进行操作的方法。
  2. Encapsulation 封装性
     与抽象性类似,封装实现细节,只公布接口给外面使用。全局变量与封装相反,如果在类模块中有一个公共字段,你不是在封装你的数据。在编程中不需要暴露字段,而是要暴露属性。属性访问器中可以包含逻辑代码,这就是封装的美妙之处。
  3. Polymorphism 多态性
     如果你以前从未使用过接口,很难理解多态性。接口使VBA打开了面向对象编程的大门。对象具有多种形式的能力,称为多态。
  4. Inheritance 继承性
     VBA不具备继承的机制,一个类从另一个类继承成员的能力,当两个类以 is-a方式相关联时,继承就起作用了。继承是OPP的四大支柱之一,而组合不是。VBA接口是一个带有空成员的类。

组合

 在VBA中,类之间的关系不是 is-a,而是说这个类 has-a某个东西。当一个对象封装了其他对象的实例时,它利用了组。可以封装外部对象并公开则可以完全模拟类的继承。

封装的目的

 默认情况VBA Classes 是私有的(Private),只能在定义它们的项目中使用。类模块的属性也可以设置为“PublicNotCreatable“,并在其他项目中使用,其他项目将添加这个VBA项目作为引用(就像引用脚本库一样,现在引用的是另一个.xlsm或.xlam)。在引用VBA项目中,可以看到并使用其他引用项目中的类,但不能创建他们的实例。需要一种方法将功能公开给引用的VBA项目,以返回公共类的实例。

Singleton 单列模式

 在OOP设计模式中,工厂方法通常与单列模式结合在一起使用,因工厂类只需要有一个实例。假如客户端不能使用New关键字创建类实例,将VB_PredeclaredId属性设置为True本质上使该类的默认实例成为有效的单例。

VBA中的默认实例

 首先导出一个VBA类模块,然后用记事本打开,会看到下面的属性:

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "类1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True

 在VBA的窗体模块中,有一个默认实例,因窗体模块中将该属性设置为True,这个VBA该属性自动创建一个全局作用域对象。用同样的方式导出窗体模块然后用记事本打开。

VERSION 5.00
Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} UserForm1 
   Caption         =   "UserForm1"
   ClientHeight    =   3015
   ClientLeft      =   120
   ClientTop       =   465
   ClientWidth     =   4560
   OleObjectBlob   =   "UserForm1.frx":0000
   StartUpPosition =   1  '所有者中心
End
Attribute VB_Name = "UserForm1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True '该属性是打开VBA面向对象编程的关键
Attribute VB_Exposed = False

 在显示窗体的时候可以直接使用UserForm1.Show,没有显示创建UserForm1 的实例,使用一个已经存在的实例。因其自动创建一个以类为名称的默认全局对象,就像静态类一样。
 在VBA中使用默认实例作为单列模式,这个属性是打开VBA OOP编程的关键。有了这属性,我们可以在VBA中拥有一个有效只读的对象。

例子

 假如你有一个 Car 类,类里面有 Make、Model和Manufacturer三个属性即制造时间、车型、制造商。

'@Folder("VBAProject")
'@Class Car
Option Explicit
Private Type TCar
    Make As Long
    Model As String
    Manufacturer As String
End Type

Private this As TCar

Public Property Get Make() As Long
    Make = this.Make
End Property

Friend Property Let Make(ByVal Value As Long)
    this.Make = Value
End Property

Public Property Get Model() As String
    Model = this.Model
End Property

Friend Property Let Model(ByVal Value As String)
    this.Model = Value
End Property

Public Property Get Manufacturer() As String
    Manufacturer = this.Manufacturer
End Property

Friend Property Let Manufacturer(ByVal Value As String)
    this.Manufacturer = Value
End Property

 Friend关键字对于外部引用项目,该属性变为只读的。在同一项目定义的CarFactory Class可以访问Friend修饰的数据。

'@Exposed
'@Folder("VBAProject")
'@PredeclaredId=True
'@ClassName CarFactory
Option Explicit
Public Function Create(ByVal carMake As Long, ByVal carModel As String, ByVal carManufacturer As String) As Car
    Dim result As Car
    Set result = New Car
    With New result
        .Make = carMake
        .Model = carModel
        .Manufacturer = carManufacturer
    End With
    Set Create = result
End Function

 CarFactory的PredeclaredId特性设置为True,VBA客户代码可以这样调用该类。

Public Sub DoSomething()
    Dim myCar  As Car
    Set myCar = CarFactory.Create(2020, "Civic", "Honda")
Debug.Print "We hav a " & myCar.Make & " " & myCar.Manufacturer & " " & myCar.Model & " here."
End Sub

VBA中的接口

 在VBA中,类模块的公共成员定义该类实例的默认接口。而任何其他类理论上都可以实现任何其他类的默认接口。在实践中,用类模块来定义正式的抽象接口(只有方法的存根,没有实现),称这样特殊的类为接口。
 例如添加一个类模块,将其作为接口使用命名为ICar。

'@Interface
'@Folder("VBAProject")
'@ClassName ICar
Option Explicit
Public Property Get Make() As Long
End Property
 
Public Property Get Model() As String
End Property
 
Public Property Get Manufacturer() As String
End Property

VBA中实现接口

 在VBA中,在类模块的声明部分使用Implements关键字来告诉编译器类可以与特定的接口一起使用。但必须实现接口的每个成员,不实现他们编译时将错误。
 因此,上面代码拥有一个ICar抽象接口。表示任何实现该接口的对象都具有只读属性Make,Model和Manufacture。

'@Folder("VBAProject")
'@PredeclaredId
'@ClassName ReadOnlyCar
Option Explicit

Implements ICar

Private Type TCar
    Make As Long
    Model As String
    Manufacture As String
End Type
Private this As TCar

Private Property Get ICar_Make() As Long
   ICar_Make = this.Make
End Property

Private Property Get ICar_Model() As String
    ICar_Model = this.Model
End Property

Private Property Get ICar_Manufacturer() As String
    ICar_Manufacturer = this.Manufacture
End Property

 注意实现代码总是私有的,这是不能改变的。这些方法在类实例上是不公开的。只能通过ICar来访问这些方法。
 我把工厂类的方法Create移动到ReadOnlyCar类本身并且函数返回ICar接口

Public Function Create(ByVal carMake As Long, ByVal carModel As String, ByVal carManufacturer As String) As ICar
    With this
        .Make = carMake
        .Model = carModel
        .Manufacturer = carManufacturer
    End With
    Set Create = Me
End Function

 因类的VB_PredeclaredId属性设置为True,将得到一个自由,全局的默认实例。客户端代码可以这样使用。

Dim myCar as ICar
set myCar=ReadOnlyCar.Create(2020,"Honda","Fit")
'在这里mycar只能访问ICar的成员。

 每个实现类都提供上面的方法是非常麻烦的。重新定义一个ICarFactory抽象工厂接口。

'@Folder("VBAProject")
'@Interface
Option Explicit

Public Function Create(ByVal carMake As Long, ByVal carModel As String, ByVal carManufacturer) As ICar

End Function

 ICar实现该接口,代码得到简化,并删除原来的Create方法。代码如下:

'@Folder("VBAProject")
'@Class Car
Option Explicit
Implements ICar
Implements IFactory
Private Type TCar
    Make As Long
    Model As String
    Manufacturer As String
End Type

Private this As TCar

Public Property Get Self() As IFactory
	Set Self = Me
End Property


Private Property Get ICar_Make() As Long
    'TODO implement interface member
    ICar_Make = this.Make
End Property

Private Property Get ICar_Model() As String
    'TODO implement interface member
    ICar_Model = this.Model
End Property

Private Property Get ICar_Manufacturer() As String
    'TODO implement interface member
    ICar_Manufacturer = this.Manufacturer
End Property

Private Function IFactory_Create(ByVal carMake As Long, ByVal carModel As String, ByVal carManufacturer As Variant) As ICar
   With this
        .Make = carMake
        .Model = carModel
        .Manufacturer = carManufacturer
    End With
    Set IFactory_Create = Me
End Function

 客户端代码实现如下,变得非常简洁。

Public Sub main()
    Dim myCar As ICar
    Set myCar = ReadOnlyCar.Self.Create(2020, "丰田", "上汽")
    Debug.Print myCar.Make, myCar.Manufacturer, myCar.Model
End Sub

 上面便是VBA中实现的工厂方法,该方法可以实现VBA对象初始化操作,模拟高级语言的构造函数。

工厂方法的定义

 定义一个创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
工厂方法模式的通用类图

在这里插入图片描述
 工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最高抽象的定义;Creator为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreateor完成。
 用女娲造人的故事来举例说明:
在这里插入图片描述
根据皮肤颜色来,来创建不同护色的人。
第一步:
 按照工厂方法的语义,先找到人类的共性,人类的肤色和说话的音节不同将其抽象出来作为接口对外公布。

'@Interface IHuman
Publci Sub getColor()
'皮肤颜色不同
End sub
public Sub talk()
'说法方式不同
End sub
'@不同护色的人实现该接口
'@YelloHuman
Implements IHuman
Private Sub IHuman_getColor()
    Debug.Print "黄色人种的皮肤颜色是黄色的!"
End Sub

Private Sub IHuman_talk()
    Debug.Print "黄色人种会说话,一般说的都是双字节。"
End Sub
'@WhiteHuman
Private Sub IHuman_getColor()
    Debug.Print "白色人种的皮肤颜色是白色的!"
End Sub

Private Sub IHuman_talk()
    Debug.Print "白色人种会说话,一般都是单字节。"
End Sub
'@BlackHuman
Private Sub IHuman_getColor()
	Debug.Print "黑色人种的皮肤颜色是黑色的!"
End Sub

Private Sub IHuman_talk()
	Debug.Print "黑人会说话,一般听不懂"
End Sub

 上面VBA代码按照工厂方法的语义已经完成了对事物最抽象部分的定义。

'@Interface IAbstractFactory
public Function CreateObject(Value as IHuman) as IHuman
'通过接口来延迟到子类实例化对象
End Function
'AbstactFactory
Implements AbstactHumanFactory

Public Property Get Self() As IAbstactFactory
    Set Self = Me
End Property

Private Function AbstactFactory_CreateOject(Value As IHuman) As IHuman
    Set AbstactFactory_CreateOject = Value
End Function

猜你喜欢

转载自blog.csdn.net/qq_25686631/article/details/109712071
vba