如何在Excel VBA中使用实现
我试图为一个工程项目实现一些形状,并将其抽象为一些常见的function,以便我可以有一个通用的程序。
我想要做的是有一个名为cShape
的接口,并有cRectangle
和cCircle
实现cShape
我的代码如下:
cShape
界面
Option Explicit Public Function getArea() End Function Public Function getInertiaX() End Function Public Function getInertiaY() End Function Public Function toString() End Function
cRectangle
类
Option Explicit Implements cShape Public myLength As Double ''going to treat length as d Public myWidth As Double ''going to treat width as b Public Function getArea() getArea = myLength * myWidth End Function Public Function getInertiaX() getInertiaX = (myWidth) * (myLength ^ 3) End Function Public Function getInertiaY() getInertiaY = (myLength) * (myWidth ^ 3) End Function Public Function toString() toString = "This is a " & myWidth & " by " & myLength & " rectangle." End Function
cCircle
类
Option Explicit Implements cShape Public myRadius As Double Public Function getDiameter() getDiameter = 2 * myRadius End Function Public Function getArea() getArea = Application.WorksheetFunction.Pi() * (myRadius ^ 2) End Function ''Inertia around the X axis Public Function getInertiaX() getInertiaX = Application.WorksheetFunction.Pi() / 4 * (myRadius ^ 4) End Function ''Inertia around the Y axis ''Ix = Iy in a circle, technically should use same function Public Function getInertiaY() getInertiaY = Application.WorksheetFunction.Pi() / 4 * (myRadius ^ 4) End Function Public Function toString() toString = "This is a radius " & myRadius & " circle." End Function
问题是,每当我运行testing用例时,都会出现以下错误:
编译错误:
对象模块需要为接口“〜”实现“〜”
这是一个深奥的面向对象的概念,还有一点你需要做和理解使用自定义的形状集合。
您可能首先想要通过this answer
来了解VBA中的类和接口。
按照下面的说明
首先打开记事本并复制粘贴下面的代码
VERSION 1.0 CLASS BEGIN MultiUse = -1 END Attribute VB_Name = "ShapesCollection" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = False Attribute VB_PredeclaredId = False Attribute VB_Exposed = False Option Explicit Dim myCustomCollection As Collection Private Sub Class_Initialize() Set myCustomCollection = New Collection End Sub Public Sub Class_Terminate() Set myCustomCollection = Nothing End Sub Public Sub Add(ByVal Item As Object) myCustomCollection.Add Item End Sub Public Sub AddShapes(ParamArray arr() As Variant) Dim v As Variant For Each v In arr myCustomCollection.Add v Next End Sub Public Sub Remove(index As Variant) myCustomCollection.Remove (index) End Sub Public Property Get Item(index As Long) As cShape Set Item = myCustomCollection.Item(index) End Property Public Property Get Count() As Long Count = myCustomCollection.Count End Property Public Property Get NewEnum() As IUnknown Attribute NewEnum.VB_UserMemId = -4 Attribute NewEnum.VB_MemberFlags = "40" Set NewEnum = myCustomCollection.[_NewEnum] End Property
将文件保存为ShapesCollection.cls
到您的桌面。
确保你保存了
*.cls
扩展名而不是ShapesCollection.cls.txt
现在打开你的Excel文件,进入VBE的ALT + F11,并右键单击Project Explorer
。 从下拉菜单中selectImport File
并导航到该文件。
注意:您需要首先将代码保存在
.cls
文件中,然后将其导入,因为VBEditor不允许使用属性。 这些属性允许您在迭代中指定默认成员,并使用自定义集合类中的每个循环
查看更多: 1,2,3,4
现在插入3个类模块。 重命名并复制粘贴代码
cShape 这是你的接口
Public Function GetArea() As Double End Function Public Function GetInertiaX() As Double End Function Public Function GetInertiaY() As Double End Function Public Function ToString() As String End Function
cCircle
Option Explicit Implements cShape Public Radius As Double Public Function GetDiameter() As Double GetDiameter = 2 * Radius End Function Public Function GetArea() As Double GetArea = Application.WorksheetFunction.Pi() * (Radius ^ 2) End Function ''Inertia around the X axis Public Function GetInertiaX() As Double GetInertiaX = Application.WorksheetFunction.Pi() / 4 * (Radius ^ 4) End Function ''Inertia around the Y axis ''Ix = Iy in a circle, technically should use same function Public Function GetInertiaY() As Double GetInertiaY = Application.WorksheetFunction.Pi() / 4 * (Radius ^ 4) End Function Public Function ToString() As String ToString = "This is a radius " & Radius & " circle." End Function 'interface functions Private Function cShape_getArea() As Double cShape_getArea = GetArea End Function Private Function cShape_getInertiaX() As Double cShape_getInertiaX = GetInertiaX End Function Private Function cShape_getInertiaY() As Double cShape_getInertiaY = GetInertiaY End Function Private Function cShape_toString() As String cShape_toString = ToString End Function
因为CRectangle
Option Explicit Implements cShape Public Length As Double ''going to treat length as d Public Width As Double ''going to treat width as b Public Function GetArea() As Double GetArea = Length * Width End Function Public Function GetInertiaX() As Double GetInertiaX = (Width) * (Length ^ 3) End Function Public Function GetInertiaY() As Double GetInertiaY = (Length) * (Width ^ 3) End Function Public Function ToString() As String ToString = "This is a " & Width & " by " & Length & " rectangle." End Function ' interface properties Private Function cShape_getArea() As Double cShape_getArea = GetArea End Function Private Function cShape_getInertiaX() As Double cShape_getInertiaX = GetInertiaX End Function Private Function cShape_getInertiaY() As Double cShape_getInertiaY = GetInertiaY End Function Private Function cShape_toString() As String cShape_toString = ToString End Function
您需要现在Insert
一个标准Module
并复制粘贴下面的代码
模块1
Option Explicit Sub Main() Dim shapes As ShapesCollection Set shapes = New ShapesCollection AddShapesTo shapes Dim iShape As cShape For Each iShape In shapes 'If TypeOf iShape Is cCircle Then Debug.Print iShape.ToString, "Area: " & iShape.GetArea, "InertiaX: " & iShape.GetInertiaX, "InertiaY:" & iShape.GetInertiaY 'End If Next End Sub Private Sub AddShapesTo(ByRef shapes As ShapesCollection) Dim c1 As New cCircle c1.Radius = 10.5 Dim c2 As New cCircle c2.Radius = 78.265 Dim r1 As New cRectangle r1.Length = 80.87 r1.Width = 20.6 Dim r2 As New cRectangle r2.Length = 12.14 r2.Width = 40.74 shapes.AddShapes c1, c2, r1, r2 End Sub
运行Main
Sub,并在Immediate Window
CTRL + G中查看结果
意见和解释:
在您的ShapesCollection
类模块中,有2 ShapesCollection
项用于将项目添加到集合中。
第一种方法Public Sub Add(ByVal Item As Object)
只需要一个类实例并将其添加到集合中。 你可以像这样在Module1
使用它
Dim c1 As New cCircle shapes.Add c1
Public Sub AddShapes(ParamArray arr() As Variant)
允许您在添加多个对象的同时,
逗号分隔它们,
方法与AddShapes()
Sub相同。
这是一个比单独添加每个对象更好的devise,但取决于你要去哪个对象。
注意我是如何在循环中注释掉一些代码的
Dim iShape As cShape For Each iShape In shapes 'If TypeOf iShape Is cCircle Then Debug.Print iShape.ToString, "Area: " & iShape.GetArea, "InertiaX: " & iShape.GetInertiaX, "InertiaY:" & iShape.GetInertiaY 'End If Next
如果从'If
和'End If
行中删除注释,则只能打印cCircle
对象。 如果你可以在VBA中使用委托,这将是非常有用的,但是你不能这么做,所以我已经向你展示了只打印一种types的对象的另一种方法。 你显然可以修改If
语句来适应你的需要,或者只是打印出所有的对象。 再次,这取决于你如何处理你的数据:)
有两个关于VBA和“实现”声明的无证添加。
-
VBA在派生类的inheritance接口的方法名称中不支持非内核字符'_'。 Fe它不会用cShape.get_area(在Excel 2007中testing)的方法编译代码:VBA将输出上面的任何派生类的编译错误。
-
如果一个派生类没有实现在接口中命名的自己的方法,VBA成功地编译了一个代码,但是这个方法将无法通过派生类types的variables来实现。
我们必须在所使用的类中实现接口的所有方法。
cCircle类
Option Explicit Implements cShape Public myRadius As Double Public Function getDiameter() getDiameter = 2 * myRadius End Function Public Function getArea() getArea = Application.WorksheetFunction.Pi() * (myRadius ^ 2) End Function ''Inertia around the X axis Public Function getInertiaX() getInertiaX = Application.WorksheetFunction.Pi() / 4 * (myRadius ^ 4) End Function ''Inertia around the Y axis ''Ix = Iy in a circle, technically should use same function Public Function getIntertiaY() getIntertiaY = Application.WorksheetFunction.Pi() / 4 * (myRadius ^ 4) End Function Public Function toString() toString = "This is a radius " & myRadius & " circle." End Function Private Function cShape_getArea() As Variant End Function Private Function cShape_getInertiaX() As Variant End Function Private Function cShape_getIntertiaY() As Variant End Function Private Function cShape_toString() As Variant End Function
cRectangle类
Option Explicit Implements cShape Public myLength As Double ''going to treat length as d Public myWidth As Double ''going to treat width as b Private getIntertiaX As Double Public Function getArea() getArea = myLength * myWidth End Function Public Function getInertiaX() getIntertiaX = (myWidth) * (myLength ^ 3) End Function Public Function getIntertiaY() getIntertiaY = (myLength) * (myWidth ^ 3) End Function Public Function toString() toString = "This is a " & myWidth & " by " & myLength & " rectangle." End Function Private Function cShape_getArea() As Variant End Function Private Function cShape_getInertiaX() As Variant End Function Private Function cShape_getIntertiaY() As Variant End Function Private Function cShape_toString() As Variant End Function
cShape类
Option Explicit Public Function getArea() End Function Public Function getInertiaX() End Function Public Function getIntertiaY() End Function Public Function toString() End Function
以下是给出的答案的一些理论和实践的贡献,以防人们到达这里,想知道什么是实现/接口。
我们知道,VBA不支持inheritance,因此我们可能几乎盲目地使用接口来实现跨不同类的公共属性/行为。
不过,我认为描述两者之间在概念上的区别是很有用的,以了解它为什么在以后重要。
- inheritance:定义一个is-a关系(一个正方形是一个形状);
- 接口:定义必须执行的关系(一个典型的例子是
drawable
接口,规定可绘制对象必须实现方法draw
)。 这意味着来自不同根类的类可以实现常见的行为。
inheritance意味着基类(一些物理或概念原型)被扩展 ,而接口实现了一组定义特定行为的属性/方法。
因此,人们会说, Shape
是所有其他形状inheritance的基类,其中一个可以实现drawable
接口,使所有的形状都是可绘制的。 这个接口将是一个合约,保证每个Shape都有一个draw
方法,指定应该如何/在哪里绘制一个形状:一个圆形可以 – 也可以不 – 与正方形绘制不同。
class IDrawable:
'IDrawable interface, defining what methods drawable objects have access to Public Function draw() End Function
由于VBA不支持inheritance,所以我们自动地被迫select创build一个IShape接口来保证某些属性/行为能够被普通形状(正方形,圆形等)实现,而不是创build一个抽象的Shape基类,可以延长。
class IShape:
'Get the area of a shape Public Function getArea() As Double End Function
我们遇到困难的部分是当我们想要使每一个形状可绘制。
不幸的是,因为IShape是VBA中的一个接口而不是基类,所以我们不能在基类中实现可绘制的接口。 看来,VBA不允许我们有另一个接口工具; 经过testing后,编译器似乎不提供所需的行为。 换句话说,我们不能在IShape中实现IDrawable,并且期望IShape的实例因此被迫实现IDrawable方法。
我们被迫将这个接口实现为实现IShape接口的每个通用形状类,幸运的是VBA允许实现多个接口。
class 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
我们的主要代码将使用工厂创build一个新的广场:
Dim square As cSquare Set square = mFactory.createSquare(5, 5, 0, 0)
当您查看您掌握的方法时,您会注意到您可以通过逻辑访问cSquare类中定义的所有方法:
我们稍后会看到为什么这是相关的。
现在你应该想知道如果你真的想创build一个可绘制对象的集合将会发生什么。 您的应用程序可能碰巧包含的对象不是形状,但仍然是可绘制的。 理论上来说,没有任何东西可以阻止你有一个可以绘制的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
装饰器允许你添加一些native vba集合不提供的便利方法,但是这里的实际要点是集合将只接受可绘制的对象(实现IDrawable接口)。 如果我们尝试添加一个不可绘制的对象,则会引发types不匹配(只允许绘制对象!)。
所以我们可能想要遍历一系列可绘制的对象来渲染它们。 允许一个不可绘制的对象进入集合会导致一个错误。 渲染循环可能如下所示:
选项显式
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类没有实现可绘制的接口,就会抛出一个types不匹配。
但是,为什么将对象声明为接口也许是最重要的原因,因为我们只想暴露在接口中定义的方法 ,而不是我们之前看到的在单个类上定义的公共方法。
Dim square_1 As IDrawable
我们不仅确定square_1具有draw
方法,而且还确保只有由IDrawable定义的方法才会被暴露。
对于一个正方形来说,这个好处可能不会立即清楚,但是让我们来看看Java集合框架中的一个比较清晰的类比。
想象一下,你有一个通用的IList
接口,它定义了适用于不同types列表的一组方法。 每种types的列表都是一个特定的类,它实现了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(允许与这种Listtypes相关的特定内部行为)。
如果我们遵守界面,我们可以改变这一行:
set myList = new ArrayList
至:
set myList = new LinkedList
而其他的代码都不会因为接口确保合同履行而中断。 只使用在IList上定义的公共方法,因此不同types的列表随时间可交换。
最后一件事(VBA中可能不太为人所知的行为)是可以给接口一个默认实现
我们可以通过以下方式定义一个接口:
IDrawable:
Public Function draw() Debug.Print "Draw interface method" End Function
还有一个实现绘制方法的类:
的CSquare:
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
如果将variables声明为cSquare,则这是不可能的。
当这可能是有用的时候,我不能立即想到一个好例子,但是如果您testing它,在技术上是可能的。
非常有趣的文章,了解简单的理解为什么和什么时候一个接口是有用 但我认为你最后一个关于默认实现的例子是不正确的。 第一次调用square_1的draw
方法,实例化为IDrawable正确的打印结果,但第二次调用square方法实例化为cSquare的draw
方法不正确,不会打印任何内容。 实际上有三种不同的方法:
IDrawable.cls:
Public Function draw() Debug.Print "Interface Draw method" End Function
cSquare.cls:
Implements IDrawable Public Function draw() Debug.Print "Class Draw method" End Function Public Function IDrawable_draw() Debug.Print "Interfaced Draw method" End Function
标准模块:
Sub Main() Dim square_1 As Class6_Methods_IDrawable Set square_1 = New Class6_Methods_IDrawable Debug.Print "square_1 : "; square_1.draw Dim square_2 As Class6_Methods_cSquare Set square_2 = New Class6_Methods_cSquare Debug.Print "square_2 : "; square_2.draw Dim square_3 As Class6_Methods_IDrawable Set square_3 = New Class6_Methods_cSquare Debug.Print "square_3 : "; square_3.draw End Sub
结果是:
square_1 : Interface Draw method square_2 : Class Draw method square_3 : Interfaced Draw method