2013-10-15 3 views
49

Я пытаюсь реализовать некоторые формы для инженерного проекта и абстрагировать его для некоторых общих функций, чтобы у меня была обобщенная программа.Как использовать инструменты в Excel VBA

То, что я пытаюсь сделать, это интерфейс называется 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 

Проблема заключается в том, что всякий раз, когда я запускаю тесты, он приходит со следующей ошибкой:

Compile Error:

Object module needs to implement '~' for interface '~'

+0

@ L42 почему щедрость здесь? Не могли бы вы объяснить свои требования? – dee

ответ

69

Это эзотерическая концепция ООП, и вам нужно сделать еще немного, чтобы понять и использовать пользовательскую коллекцию фигур.

Прежде всего, вы можете пройти через 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 на рабочий стол.

Make sure you are saving it with the*.cls extension and not ShapesCollection.cls.txt

Теперь открываем вам файл Excel, перейдите к VBE ALT + F11 и правой кнопкой мыши в Project Explorer.Выберите Import File в раскрывающемся меню и перейдите к файлу.

enter image description here

NB: You needed to save the code in a .cls file first and then import it because VBEditor does not allow you to use Attributes. The attributes allow you to specify the default member in the iteration and use the for each loop on custom collection classes

Смотреть еще: 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 теперь и копировать-вставить код, приведенный ниже

Module1

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 WindowCTRL + G

enter image description here


Комментарии и объяснения:

В вашем модуле класса ShapesCollection есть 2 подлодки для добавления элементов в коллекцию.

Первый способ Public Sub Add(ByVal Item As Object) просто берет экземпляр класса и добавляет его в коллекцию. Вы можете использовать его в Module1 как этот

Dim c1 As New cCircle 
shapes.Add c1 

The Public Sub AddShapes(ParamArray arr() As Variant) позволяет добавлять несколько объектов одновременно разделяя их с помощью , запятой в точно такой же образом, как и AddShapes() Sub делает.

Это отличный дизайн, чем добавление каждого объекта отдельно, но это зависит от вас, из которого вы собираетесь пойти.

Обратите внимание, как я закомментировать код в цикле

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 в соответствии с вашими потребностями или просто распечатать все объекты. Опять же, зависит от вас, как вы будете обрабатывать свои данные :)

+1

Это фантастика! Не могли бы вы немного объяснить, как «IUnknown» используется в текущем контексте? Из быстрого google я не мог легко понять это. Кроме того, это 'myCustomCollection.Remove (index)' вместо 'value' в' ShapesCollection.cls'? Кусок красоты! – Ioannis

+1

О, спасибо @ Ioannis, что было простой ошибкой, которую я исправил сейчас. 'IUnknown' является частью библиотеки типа stdole2.tlb. Вы можете использовать [OLE/COM Object Viewer] (http://msdn.microsoft.com/en-us/library/windows/desktop/ms688269 (v = vs.85) .aspx), чтобы обнаружить реализацию. Более подробные сведения с помощью «IUnknown» могут также стать новым автономным вопросом :) –

+2

Спасибо за ваш удивительно подробный ответ!Я пытаюсь моделировать кучу столбцов здания, которые имеют произвольную форму поперечного сечения. Ваш ответ ответил не только на этот вопрос, но и на справедливую часть моей следующей задачи задачи (как создать коллекцию столбцов) :) Имейте теперь написать max или min селектор! – Zigu

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 

enter image description here

10

Есть два недокументированных дополнения о VBA и заявлении «Реализует».

  1. VBA не поддерживает символ undescore '_' в имени метода унаследованного интерфейса производного класса. F.E. он не будет компилировать код с таким методом, как cShape.get_area (проверен в Excel 2007): VBA выдаст ошибку компиляции выше для любого производного класса.

  2. Если производный класс не реализует собственный метод, названный так же, как в интерфейсе, VBA успешно компилирует код, но метод будет недоступен через переменную типа производного класса.

10

Вот некоторые теоретические и практические вклады в ответы на них, если люди прибудут сюда, которые задаются вопросом, что такое инструменты/интерфейсы.

Как известно, VBA не поддерживает наследование, поэтому мы можем почти слепо использовать интерфейсы для реализации общих свойств/поведения для разных классов.
Тем не менее, я считаю, что полезно описать, что такое концептуальная разница между ними, чтобы понять, почему это имеет значение позже.

  • Наследование: определяет отношение is-a (квадрат - это форма);
  • Интерфейсы: определить обязательные отношения (типичным примером является интерфейс drawable, который предписывает, чтобы объект-получатель должен реализовать метод draw). Это означает, что классы, происходящие из разных корневых классов, могут реализовывать обычное поведение.

Наследование означает, что BaseClass (некоторый физический или концептуальный архетип) является расширен, тогда как интерфейсы реализовать набор свойств/методов, которые определяют определенное поведение .
Таким образом, можно сказать, что 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 

части, где мы получим в беде, когда мы хотим сделать каждую форму вытяжки.
К сожалению, поскольку 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 

Наш основной код будет использовать завод, чтобы создать новую площадь:

Dim square   As cSquare 

Set square = mFactory.createSquare(5, 5, 0, 0) 

Когда вы посмотрите на методы, которые у вас есть в вашем распоряжении, вы заметите, что логически получаете доступ ко всем методам, определенным в классе cSquare:

enter image description here

Позже мы увидим, почему это актуально.

Теперь вам следует задаться вопросом, что произойдет, если вы действительно хотите создать коллекцию доступных объектов. В вашем приложении могут содержаться объекты, которые не являются фигурами, но которые еще доступны. Теоретически, ничто не мешает вам иметь интерфейс 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 

Декоратор позволяет добавить некоторые удобные методы, что местные коллекции УВЫ не дают, но реальная точка здесь является то, что коллекция будет принимать только объекты, которые вытяжка (реализовать 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 

enter image description here

Мы не только уверены, что 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 (что позволяет специфическое внутреннее поведение, связанное с этим типом списка).

Если мы выполнили на интерфейс, мы можем изменить строку:

set myList = new ArrayList 

к:

set myList = new LinkedList 

и ни один из другого кода сломается как интерфейс не гарантирует, что договор выполнено, т.е. используются только общедоступные методы, определенные в IList, поэтому различные типы списков с возможностью замены с течением времени.

Заключительная вещь (возможно, менее известное поведение в 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  

Это не представляется возможным, если вы объявляете переменную как cSquare.
Я не могу сразу придумать хороший пример, когда это может быть полезно, но технически это возможно, если вы его протестируете.

0

Очень интересное сообщение, чтобы понять, почему и когда интерфейс может быть полезен! Но я думаю, что ваш последний пример о реализации по умолчанию неверен. Первый вызов метода draw метода square_1, созданного как IDrawable, правильно выводит результат, который вы даете, но второй вызов метода square_1, созданного как cSquare, является неправильным, ничего не печатается. 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 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 
Смежные вопросы