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 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ファイルをデスクトップに保存します。

拡張子で保存していないことを確認してください*.clsShapesCollection.cls.txt

Excel ファイルを開き、VBE ALT+に移動してF11を右クリックしますProject ExplorerImport Fileドロップダウン メニューから選択し、ファイルに移動します。

ここに画像の説明を入力

注: .clsVBEditor では属性を使用できないため、最初にコードをファイルに保存してからインポートする必要がありました。属性を使用すると、反復で既定のメンバーを指定し、カスタム コレクション クラスで 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 でデリゲートを使用できる場合に非常に便利ですが、使用できないため、1 種類のオブジェクトのみを出力する別の方法を示しました。もちろん、必要に応じてステートメントを変更したり、Ifすべてのオブジェクトを単純に出力したりできます。繰り返しますが、データをどのように処理するかはあなた次第です:)

于 2013-10-15T11:05:09.850 に答える
22

実装/インターフェースとは何か疑問に思っている人がここに到着した場合に備えて、与えられた答えに対する理論的および実際的な貢献を以下に示します。

ご存知のように、VBA は継承をサポートしていないため、ほとんど盲目的にインターフェイスを使用して、異なるクラス間で共通のプロパティ/動作を実装することがあります。
それでも、後でなぜ重要なのかを理解するために、両者の概念的な違いを説明することは有益だと思います。

  • 継承: is-a 関係 (正方形は形) を定義します。
  • インターフェイス: 必須の関係を定義します (典型的な例は、drawableドローアブル オブジェクトがメソッドを実装する必要があることを規定するインターフェイスですdraw)。これは、異なるルート クラスに由来するクラスが共通の動作を実装できることを意味します。

継承とは、基本クラス (何らかの物理的または概念的なアーキタイプ) が拡張されることを意味しますが、インターフェイスは特定の動作を定義する一連のプロパティ/メソッドを実装します。 そのため、これは他のすべての図形が継承する基本クラスであり、すべての図形を描画可能にするインターフェイスを実装できるクラスであると言えます。このインターフェイスは、すべての Shape にメソッドがあり、図形を描画する方法/場所を指定することを保証するコントラクトです。円は、正方形とは異なる方法で描画される場合と、描画されない場合があります。
Shapedrawabledraw

クラス IDrawable:

'IDrawable interface, defining what methods drawable objects have access to
Public Function draw()
End Function

VBA は継承をサポートしていないため、特定のプロパティ/動作が一般的な形状 (正方形、円など) によって実装されることを保証するインターフェイス IShape を作成することを自動的に選択する必要があります。拡張できます。

クラス IShape:

'Get the area of a shape
Public Function getArea() As Double
End Function

問題になるのは、すべての Shape を drawable にしたいときです。
残念ながら、IShape はインターフェイスであり、VBA の基底クラスではないため、ドローアブル インターフェイスを基底クラスに実装することはできません。VBA では、あるインターフェイスに別のインターフェイスを実装させることはできないようです。これをテストした後、コンパイラは望ましい動作を提供していないようです。言い換えれば、IDrawable を IShape 内に実装することはできません。このため、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 にメソッドがあることを確認するだけでなく、IDrawable によって定義されたメソッドのみdrawが公開されることも確認します。 正方形の場合、これの利点はすぐには明らかではないかもしれませんが、より明確な 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 クラスで明確に定義されているパブリック メソッドにアクセスできます。これが望ましい場合もありますが、多くの場合、クラス固有の public メソッドによって定義されたのではなく、内部クラスの動作を利用したいだけです。
この ArrayList をコードで 50 回以上使用すると、利点が明らかになり、LinkedList (このタイプのリストに関連する特定の内部動作を可能にする) を使用する方が良いことが突然わかります。

インターフェイスに準拠していれば、次の行を変更できます。

set myList = new ArrayList

に:

set myList = new LinkedList 

インターフェイスがコントラクトが満たされていることを確認するため、他のコードは壊れません。IList で定義されたパブリック メソッドのみが使用されるため、さまざまな種類のリストを時間の経過とともに交換できます。

最後のこと (おそらく VBA ではあまり知られていない動作) は、インターフェイスに既定の実装を与えることができるということです。

次の方法でインターフェイスを定義できます。

IDrawable:

Public Function draw()
    Debug.Print "Draw interface method"
End Function

draw メソッドも実装するクラス:

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    

変数を cSquare として宣言する場合、これは不可能です。
これが役立つ良い例をすぐに思いつくことはできませんが、テストすれば技術的に可能です。

于 2015-10-11T00:55:53.757 に答える
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

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

ここに画像の説明を入力

于 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

インターフェイスが便利な理由とタイミングを簡単に理解するための非常に興味深い投稿です。しかし、デフォルトの実装に関する最後の例は間違っていると思います。IDrawable としてインスタンス化された square_1のメソッドへの最初の呼び出しは、draw与えられた結果を正しく出力しますが、drawcSquare としてインスタンス化された square_1 のメソッドへの 2 回目の呼び出しは正しくなく、何も出力されません。3 つの異なる方法が実際に作用します。

IDrawable.cl:

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 に答える