以下是对所给出答案的一些理论和实践贡献,以防人们来到这里想知道实现/接口是关于什么的。
众所周知,VBA 不支持继承,因此我们可能几乎盲目地使用接口来实现跨不同类的公共属性/行为。
尽管如此,我认为描述两者之间的概念差异是有用的,以便稍后了解它的重要性。
- 继承:定义了一种is-a关系(正方形is-a shape);
- 接口:定义一个必须做的关系(一个典型的例子是
drawable
规定可绘制对象必须实现方法的接口draw
)。这意味着源自不同根类的类可以实现共同的行为。
继承意味着基类(一些物理或概念原型)被扩展,而接口实现了一组定义特定行为的属性/方法。
因此,有人会说这Shape
是所有其他形状都继承自的基类,它可以实现drawable
接口以使所有形状都可绘制。这个接口将是一个契约,保证每个 Shape 都有一个draw
方法,指定应该如何/在哪里绘制一个形状:一个圆形可能 - 或可能不 - 以不同于正方形的方式绘制。
类IDrawable:
'IDrawable interface, defining what methods drawable objects have access to
Public Function draw()
End Function
由于 VBA 不支持继承,我们自动被迫选择创建一个接口 IShape,以保证某些属性/行为由通用形状(正方形、圆形等)实现,而不是创建一个抽象的 Shape 基类,我们从中可以延长。
IShape 类:
'Get the area of a shape
Public Function getArea() As Double
End Function
我们遇到麻烦的部分是当我们想让每个 Shape 都可绘制时。
不幸的是,由于 IShape 是一个接口而不是 VBA 中的基类,我们无法在基类中实现可绘制接口。似乎 VBA 不允许我们让一个接口实现另一个接口。对此进行测试后,编译器似乎没有提供所需的行为。换句话说,我们不能在 IShape 中实现 IDrawable,并期望 IShape 的实例因此而被迫实现 IDrawable 方法。
我们被迫为每个实现 IShape 接口的通用形状类实现这个接口,幸运的是 VBA 允许实现多个接口。
cSquare 类:
Option Explicit
Implements iShape
Implements IDrawable
Private pWidth As Double
Private pHeight As Double
Private pPositionX As Double
Private pPositionY As Double
Public Function iShape_getArea() As Double
getArea = pWidth * pHeight
End Function
Public Function IDrawable_draw()
debug.print "Draw square method"
End Function
'Getters and setters
接下来的部分是接口的典型用途/好处发挥作用的地方。
让我们通过编写一个返回一个新正方形的工厂来开始我们的代码。(这只是我们无法直接向构造函数发送参数的一种解决方法):
模块 mFactory:
Public Function createSquare(width, height, x, y) As cSquare
Dim square As New cSquare
square.width = width
square.height = height
square.positionX = x
square.positionY = y
Set createSquare = square
End Function
我们的主要代码将使用工厂创建一个新的 Square:
Dim square As cSquare
Set square = mFactory.createSquare(5, 5, 0, 0)
当您查看您可以使用的方法时,您会注意到您在逻辑上可以访问 cSquare 类上定义的所有方法:
我们稍后会看到为什么这是相关的。
现在你应该想知道如果你真的想创建一个可绘制对象的集合会发生什么。您的应用程序可能碰巧包含不是形状但仍可绘制的对象。从理论上讲,没有什么能阻止您拥有可以绘制的 IComputer 界面(可能是一些剪贴画或其他)。
您可能希望拥有一组可绘制对象的原因是,您可能希望在应用程序生命周期的某个点循环呈现它们。
在这种情况下,我将编写一个包装集合的装饰器类(我们将了解原因)。类collDrawables:
Option Explicit
Private pSize As Integer
Private pDrawables As Collection
'constructor
Public Sub class_initialize()
Set pDrawables = New Collection
End Sub
'Adds a drawable to the collection
Public Sub add(cDrawable As IDrawable)
pDrawables.add cDrawable
'Increase collection size
pSize = pSize + 1
End Sub
装饰器允许您添加一些原生 vba 集合不提供的便捷方法,但这里的实际要点是该集合将只接受可绘制的对象(实现 IDrawable 接口)。如果我们尝试添加一个不可绘制的对象,则会引发类型不匹配(只允许绘制可绘制对象!)。
所以我们可能想要循环一个可绘制对象的集合来渲染它们。允许不可绘制对象进入集合会导致错误。渲染循环可能如下所示:
Option Explicit
Public Sub app()
Dim obj As IDrawable
Dim square_1 As IDrawable
Dim square_2 As IDrawable
Dim computer As IDrawable
Dim person as cPerson 'Not drawable(!)
Dim collRender As New collDrawables
Set square_1 = mFactory.createSquare(5, 5, 0, 0)
Set square_2 = mFactory.createSquare(10, 5, 0, 0)
Set computer = mFactory.createComputer(20, 20)
collRender.add square_1
collRender.add square_2
collRender.add computer
'This is the loop, we are sure that all objects are drawable!
For Each obj In collRender.getDrawables
obj.draw
Next obj
End Sub
请注意,上面的代码增加了很多透明度:我们将对象声明为 IDrawable,这使得循环永远不会失败是透明的,因为 draw 方法可用于集合中的所有对象。
如果我们尝试将 Person 添加到集合中,如果此 Person 类未实现可绘制接口,则会引发类型不匹配。
但也许将对象声明为接口很重要的最相关原因是因为我们只想公开定义在接口中的方法,而不是像我们之前看到的那样在单个类上定义的那些公共方法.
Dim square_1 As IDrawable
我们不仅可以确定 square_1 有一个draw
方法,而且还可以确保只有IDrawable 定义的方法才会被公开。
对于正方形,这样做的好处可能不会立即清楚,但让我们看一下 Java 集合框架的一个更清晰的类比。
想象一下,您有一个名为的通用接口IList
,它定义了一组适用于不同类型列表的方法。每种类型的列表都是实现 IList 接口的特定类,定义它们自己的行为,并可能在顶部添加更多自己的方法。
我们将列表声明如下:
dim myList as IList 'Declare as the interface!
set myList = new ArrayList 'Implements the interface of IList only, ArrayList allows random (index-based) access
在上面的代码中,将列表声明为 IList 可确保您不会使用特定于 ArrayList 的方法,而只会使用接口规定的方法。想象一下,您将列表声明如下:
dim myList as ArrayList 'We don't want this
您将有权访问在 ArrayList 类上专门定义的公共方法。有时这可能是需要的,但通常我们只想利用内部类行为,而不是由类特定的公共方法定义。
如果我们在代码中再使用这个 ArrayList 50 次,好处就变得很明显了,突然我们发现我们最好使用 LinkedList(它允许与这种类型的 List 相关的特定内部行为)。
如果我们遵守接口,我们可以改变这一行:
set myList = new ArrayList
至:
set myList = new LinkedList
并且其他代码都不会中断,因为接口确保合同得到履行,即。仅使用在 IList 上定义的公共方法,因此不同类型的列表可以随时间交换。
最后一件事(可能是 VBA 中鲜为人知的行为)是您可以为接口提供默认实现
我们可以通过以下方式定义接口:
可绘制:
Public Function draw()
Debug.Print "Draw interface method"
End Function
以及一个实现 draw 方法的类:
c方:
implements IDrawable
Public Function draw()
Debug.Print "Draw square method"
End Function
我们可以通过以下方式在实现之间切换:
Dim square_1 As IDrawable
Set square_1 = New IDrawable
square_1.draw 'Draw interface method
Set square_1 = New cSquare
square_1.draw 'Draw square method
如果您将变量声明为 cSquare,则这是不可能的。
当这可能有用时,我无法立即想到一个好的示例,但如果您对其进行测试,在技术上是可行的。