Как использовать инструменты в 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 

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

Ошибка компиляции:

Объектный модуль должен реализовать '~' для интерфейса '~'

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

Сначала вы можете пройти 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 . В раскрывающемся меню выберите « Import File и перейдите к файлу.

введите описание изображения здесь

NB: .cls вам нужно сохранить код в файле .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 сейчас и скопировать-вставить код ниже

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 Window CTRL + G

введите описание изображения здесь


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

В ShapesCollection класса 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.

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

Обратите внимание, как я прокомментировал некоторый код в цикле

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

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

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

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

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

Класс 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 Class

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

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

Наследование означает, что базовый класс (некоторый физический или концептуальный архетип) расширен , тогда как интерфейсы реализуют набор свойств / методов, которые определяют определенное поведение .
Как таковой, можно сказать, что Shape – это базовый класс, из которого наследуются все другие формы, которые могут реализовать интерфейс с возможностью drawable чтобы сделать все фигуры доступными. Этот интерфейс был бы контрактом, который гарантирует, что каждый Shape имеет метод draw , определяющий, как / где должна быть нарисована фигура: круг может или не может быть выполнен иначе, чем квадрат.

class 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, мы не можем реализовать интерфейс drawable в базовом классе. Похоже, что 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:

введите описание изображения здесь

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

Теперь вы должны задаться вопросом, что произойдет, если вы действительно хотите создать коллекцию доступных объектов. В вашем приложении могут содержаться объекты, которые не являются фигурами, но которые еще доступны. Теоретически, ничто не мешает вам иметь интерфейс IComputer, который можно нарисовать (может быть, какой-то клипарт или что-то еще).
Причина, по которой вам может понадобиться коллекция доступных объектов, заключается в том, что вы можете отображать их в цикле в определенный момент жизненного цикла приложения.

В этом случае я напишу класс декоратора, который обертывает коллекцию (мы увидим, почему). class 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). Если мы попытаемся добавить объект, который не является допустимым, будет выбрано несоответствие типа (разрешены только допустимые объекты!).

Таким образом, мы можем запрограммировать цикл создания объектов с возможностью рисования для их рендеринга. Разрешить непривлекательный объект в коллекции приведет к ошибке. Контур рендеринга может выглядеть так:

Вариант Явный

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

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

 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.
Я не могу сразу придумать хороший пример, когда это может быть полезно, но технически это возможно, если вы его протестируете.

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

Автоматизация фильтров WHERE в SQL-запросе

Как вывести содержимое определенной строки из файла CSV?

Как создать двоичный вывод, если значение найдено в отдельном списке excel

Макрос для Excel: если столбец B имеет «X», затем скопируйте всю строку и вставьте в лист «Column B»,

C #: Excel: задайте выбранное задание для открытия следующей книги

Может ли Excel идентифицировать ячейки, которые образуют периметр вокруг ячеек определенного значения?

vlookup, но имя ячейки, которая вернет не значение в ячейке

Как скопировать данные в последнюю строку и скопировать в последнюю строку таблицы на том же рабочем листе автоматически, используя excel vba?

Как сохранить тысячи разделителей (,) десятичных чисел, скрывая ненужные нули в excel

Excel для добавления содержимого ячеек в конец текстовых файлов. Как?

SendKeys в поле ввода Excel (VBA)

Производить случайные числа с определенным распределением с Excel

Тесты на Visual Studio: запуск из Excel

Заменить значение Loop PHPExcel

Как создать на основе шаблона пользовательские настраиваемые отчеты в Excel

Давайте будем гением компьютера.