Вот некоторые теоретические и практические вклады в ответы на них, если люди прибудут сюда, которые задаются вопросом, что такое инструменты/интерфейсы.
Как известно, 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:
Позже мы увидим, почему это актуально.
Теперь вам следует задаться вопросом, что произойдет, если вы действительно хотите создать коллекцию доступных объектов. В вашем приложении могут содержаться объекты, которые не являются фигурами, но которые еще доступны. Теоретически, ничто не мешает вам иметь интерфейс 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
Мы не только уверены, что 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.
Я не могу сразу придумать хороший пример, когда это может быть полезно, но технически это возможно, если вы его протестируете.
@ L42 почему щедрость здесь? Не могли бы вы объяснить свои требования? – dee