8 июн. 2010 г.

vba 6.0 + правильное программирование

   Волею судеб сложилось так, что пришлось писать на Visual Basic For Applications версии 6.0, нужно было провести испытания разработанного модуля в сторонней ИС, под рукой оказалась ИС, спроектированная в Excel. Конечно то, что мне дали, с большим натягом можно назвать ИС, скорее это набор несвязных таблиц, содержащих стохастически сгенерированную информацию, забитую в совершенно непонятном порядке :D Но не в этом суть, в общем-то. Как бы "это" противно не выглядело, нужно было выполянть свою работу, таким образом двигаясь дальше на пути испытания разработанного мною метода автоматизации в выданной мне ИС. Решил я сделать так, как надо, а не так как требуют того обстоятельства данного начатого проекта. И начал, вот что из этого получилось.


   Суть моего модуля состоит в автоматизации составления отчетов в различных ИС.  Итак, мне нужен инструмент для манипуляции информацией (нечто, которое смогло бы мне предоставить что-то наподобие языка запросов к содержащимся на листах данных), инструментарий для отображения этой информации. Пока большего не хотелось, поскольку на полную интеграцию моего модуля нужно было дописывать имеющуюся ИС (ну не нравится она мне и все тут!), мне нужно было только провести испытания согласно моему ТЗ.
   Возможности языка VBA6 достаточно ограничены, однако не настолько, чтобы на нем нельзя было реализовать стандартные паттерны программирования. Касательно ООП в VBA6, то могу отметить следующие пункты, оказавшие мне существенные неудобства в процессе адаптации модуля моего проекта:
  • отсутствие наследования (полное, как ни странно);
  • сомнительный полиморфизм;
  • неполноценная инкапсуляция;
  • ну и так далее, насколько Вы можете понять, если Вам все же приходилось разрабатывать на человеческих языках программирования с полной поддержкой ООП (хотя, спешу заметить, что ООП - совсем не панацея). 
   Было принято решение оформить класс для работы с данными в форме итератора (что, в общем-то и логично, данные получать надо множеством, передвигаясь по нему от записи к записи). Поразмышляв над логикой этого класса и поискав на просторах интернета реализации данного шаблона, получил следующий код для модуля класса CTableIterator:

' количество строк в запросе
Private FCount As Integer
' массив, хранящий в себе значения итерируемых строк
Private Items() As Integer
' метод-получатель количества строк
Public Property Get Count() As Integer
Count = FCount
End Property
' метод-установщик количества строк
Private Property Let Count(value As Integer)
FCount = value
End Property
' метод, возвращающий текущий элемент
Public Function Item(index As Integer)
Item = Items(index)
End Function

   Во время инициализации класса, конструктору объекта передается лист, по которому осуществляется итерация, начальная строка таблицы, начальный столбец таблицы (чтобы обеспечить возможность итерации по листам, где таблицы начинаются в разных местах):
' начальная строка итерируемой таблицы
Private iRow As Integer
' начальный столбец итерируемой таблицы
Private iCol As Integer
' имя листа, по которому осуществляется итерация
Private sheetName As String

' дополнительная инициализация объекта класса
Public Sub Init(initSheetName As String, initRow As Integer, initCol As Integer)
iRow = initRow ' начальная строка
iCol = initCol ' начальный столбец
sheetName = initSheetName ' имя листа
Sheets(sheetName).Select ' выбор активного листа
End Sub
   Далее нужны функции для непосредственной работы с данными. Это функции выборки, вставки, обновления, удаления строк. Опишу лишь прототипы этих функций, не вдаваясь в подробности:
' выборка - указывается индекс столбца и значение, по которому нужно фильтровать
Public Sub Select(colindex As Integer, value As String)
' обновление - индекс строки и массив значений для ее обновления
Public Sub Update(index As Integer, values() As String)
' удаление - индекс удаляемой строки
Public Sub Delete(index As Integer)
' вставка - массив вставляемых значений
Public Sub Add(values() As String)
   При работе с этим классом встает проблема целостности таблиц, к которым мы имеем доступ. Поэтому нужно определить ряд правил для поддержания целостности данных (конечно, здесь не приходится говорить о полноценном ее варианте, однако целостную структуру таблицы, для начала, мы можем обеспечить). Под целой таблицей понимать будем такую таблицу, которая не имеет в себе пустых строк, поскольку пустую строку мы используем в качестве флага конца таблицы на листе excel (это наиболее простой вариант, можно придумать нечто более изощренное :) ). Поэтому пишем функцию, которая будет "сдвигать" таблицу таким образом, чтобы она не содержала пустых строк:
' функция-дефрагментатор таблицы
Private Sub Defragment(index As Integer)
   Используем данную функцию после операции удаления (очистки) строки таблицы на листе. Она сдвигает ту часть таблицы, которая ниже удаленной строки вверх. Все просто и логично, на мой взгляд.
   Итак, класс-итератор для передвижения по таблицам excel закончен, теперь можно приступить к написанию визуальной части проекта для отображения данных таблиц. Об этом в следующий раз. Пасип за внимание ;)
  PS Да, совсем забыл пример работы с классом:
Dim ti As New CTableIterator
ti.Init "mysheet", 1, 1
ti.Select 1, "value"
Dim i As Integer
Dim arr() As Integer
For i = 1 To ti.count
Redim arr( UBound( ti.Item(i) ) )
MsgBox arr(2)
Next i
  Конечно же, у вас должен существовать лист с названием "mysheet" и таблица на нем. Вот теперь все. :)


Словарик:
ИС - информационная система;
VBA - Visual Basic For Applications - встроенное в ms excel средство программирования;
Паттерны проектирования (шаблоны проектирования, шаблоны программирования) - набор удачных конструкций для реализации программных систем;
ООП - объектно-ориентированное программирование.