66

我正在尝试为工程项目实现一些形状,并将其抽象为一些常用功能,以便我可以拥有一个通用程序。

我正在尝试做的是调用一个接口cShape并拥有cRectanglecCircle实现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

问题是每当我运行我的测试用例时,都会出现以下错误:

编译错误:

对象模块需要为接口'~'实现'~'

4

6 回答 6

93

这是一个深奥的 OOP 概念,您需要做更多的事情并了解使用自定义形状集合。

您可能首先需要对this answerVBA 中的类和接口有一个大致的了解。


请按照以下说明进行操作

首先打开记事本并复制粘贴以下代码

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. Import File从下拉菜单中选择并导航到文件。

在此处输入图像描述

注意:您需要.cls先将代码保存在文件中,然后再将其导入,因为 VBEditor 不允许您使用属性。这些属性允许您在迭代中指定默认成员并在自定义集合类上使用 for each 循环

看更多:

现在插入 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

c矩形

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

运行MainSub 并查看+中的结果Immediate Window CTRLG

在此处输入图像描述


评论和解释:

在您的ShapesCollection类模块中,有 2 个用于将项目添加到集合中的子项。

第一个方法Public Sub Add(ByVal Item As Object)简单地获取一个类实例并将其添加到集合中。你可以Module1像这样使用它

Dim c1 As New cCircle
shapes.Add c1

允许您同时添加多个对象,以与Sub完全相同的方式Public Sub AddShapes(ParamArray arr() As Variant)用逗号分隔它们。,AddShapes()

这比单独添加每个对象要好得多,但这取决于您要选择哪个对象。

注意我是如何注释掉循环中的一些代码的

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 中使用委托,但您不能,这将非常有用,所以我向您展示了仅打印一种类型的对象的另一种方法。您显然可以修改If语句以满足您的需要或简单地打印出所有对象。同样,您将如何处理数据取决于您:)

于 2013-10-15T11:05:09.850 回答
22

以下是对所给出答案的一些理论和实践贡献,以防人们来到这里想知道实现/接口是关于什么的。

众所周知,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,则这是不可能的。
当这可能有用时,我无法立即想到一个好的示例,但如果您对其进行测试,在技术上是可行的。

于 2015-10-11T00:55:53.757 回答
13

关于 VBA 和“实施”声明有两个未记录的补充。

  1. VBA 不支持在派生类的继承接口的方法名称中使用取消标记字符“_”。它不会使用 cShape.get_area 之类的方法编译代码(在 Excel 2007 下测试):VBA 将为任何派生类输出上述编译错误。

  2. 如果派生类没有实现接口中名为 as 的自己的方法,VBA 可以成功编译代码,但该方法将无法通过派生类类型的变量访问。

于 2014-10-28T09:26:30.100 回答
8

我们必须在使用它的类中实现接口的所有方法。

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

c矩形类

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

在此处输入图像描述

于 2013-10-15T04:13:59.063 回答
3

快速修复语法

如果接口ISomeInterface有:

Public Sub someMethod()
    ' Interface, no code
End Sub

然后实现需要像:

Implements ISomeInterface

Public Sub ISomeInterface_someMethod()
    '      ^^^^^^^^^^^^^^^  ' If missing: Compile Error 
    ' Code goes here
End Sub

一个不错的方法:

Implements ISomeInterface

Private Sub someMethod()
    ' Business logic goes here
End Sub

Public Sub ISomeInterface_someMethod()
    someMethod ' i.e. Business logic in 1 place: someMethod
End Sub

也就是说,其他答案非常值得一读。

于 2018-06-18T01:11:11.083 回答
2

非常有趣的帖子,可以简单地了解界面为什么以及何时有用!但我认为你关于默认实现的最后一个例子是不正确的。draw对实例化为 IDrawable 的 square_1 方法的第一次调用正确打印了您给出的结果,但对draw实例化为 cSquare 的 square_1 方法的第二次调用不正确,没有打印任何内容。实际上有 3 种不同的方法起作用:

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 IDrawable
    Set square_1 = New IDrawable
    Debug.Print "square_1 : ";
    square_1.draw

    Dim square_2 As cSquare
    Set square_2 = New cSquare
    Debug.Print "square_2 : ";
    square_2.draw 

    Dim square_3 As IDrawable
    Set square_3 = New 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
于 2017-07-29T20:45:59.053 回答