Правильно обрабатывать события рабочего листа, когда его код реализован в интерфейсе «прокси» + класс - PullRequest
2 голосов
/ 15 апреля 2019

Я пытаюсь в дальнейшем опираться на этот отличный пример , который уже реализует лучшие практики, обсуждаемые в этих очень проницательных статьях RubberduckVBA.com:

  1. Абстрагирование от рабочей книги / листа Excel через прокси-классы ;
  2. Использование элемента управления UserForm без вмешательства в состояние его экземпляра по умолчанию;
  3. Добавление логики «Применить» к # 2 .

Я хотел бы добавить к существующему примеру обработчик событий, который (для простоты) сообщает значение верхней левой ячейки "измененного" диапазона Sheet2 в ячейке "A1" Sheet1, вдоль со временем изменения в «А2». Я бы обычно делал это в коде Sheet2 следующим образом:

Private Sub Worksheet_Change(ByVal Target As Range)
    Sheet1.Cells(1, 1).Value2 = Target.Cells(1, 1).Value2
    Sheet1.Cells(1, 2).Value2 = CStr(Now)
End Sub

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

Мне удалось понять, как обработка событий реализована в удивительном учебном пособии по броненосцам , но его дизайн несколько отличается:

  1. «Морской бой» следует шаблону проектирования MVC, в то время как я хотел бы придерживаться MVP, как в примере;
  2. "Морской бой" абстрагируется от своих рабочих листов через класс "Просмотр", в то время как я хотел бы иметь отдельный прокси-интерфейс + класс для каждого листа;
  3. «Морской бой» развертывает шаблон адаптера , пока я в порядке, когда мои представления и прокси-листы реализуются вместе с презентатором (если это возможно в отношении обработки событий).

Имея это в виду, я бы очень хотел увидеть пример кода, который добавляет событие «Worksheet_Change», которое я описал выше, к базовому проекту , в котором уже реализованы прокси Workbook и Worksheet и следует шаблону MVP.

Даже без примера кода было бы очень полезно, если бы я выяснил эти вопросы:

  1. Подсказывает ли Proxy подход к рабочему листу , что должен быть абсолютно нулевой код позади? Будет ли это шагом в неправильном направлении, если я начну реализацию события «Worksheet_Change» внутри Sheet2 (а не его прокси) следующим образом:
Public Event SheetChanged(ByVal changedRange As Range)

Private Sub Worksheet_Change(ByVal Target As Range)
    RaiseEvent SheetChanged(Target)
End Sub
  1. Если не обязательно использовать Pattern Adapter для обработки событий, все же рекомендуется иметь интерфейсы "IViewCommands" и "IViewEvents" для перечисления всех команд, отправленных от докладчика Просмотр и события, поднятые из просмотра и отправленные на докладчика соответственно?
  2. Полагаю, мне понадобится Ленивый объект / Слабая ссылка , чтобы иметь возможность выставлять события. Если это так, и если я могу выполнить работу без Адаптера (см. # 2 выше), означает ли это, что мой класс «Sheet2Proxy» должен будет содержать слабую ссылку на Presenter через его «IViewEvents» (снова см. # 2 выше) интерфейс?

1 Ответ

2 голосов
/ 15 апреля 2019

Вы абстрагируете Worksheet за "прокси" классом;по определению, он связан с рабочим листом, и вам нужно убедиться, что абстракция герметична, чтобы вы не смотрели на негерметичную абстракцию и в итоге не связали другой код с Excel.Worksheettype, который побеждает всю цель.

Для остальной части проекта прокси-класс рабочего листа действует как фасад , который манипулирует и понимает все, что нужно знать о конкретном Excel.Worksheet: следствием этого является то, что теперь вы можете использовать два модуля для абстрагирования элементов рабочего листа - самого рабочего листа и прокси-класса:

  • Код для рабочего листа может абстрагировать такие вещи, как ListObject / tablesименованные диапазоны и т.д .;используя Property Get членов, которые может использовать прокси.
  • Класс прокси-таблицы Worksheet абстрагирует манипуляции с таблицами от остальной части кода.

Действительно, этот подход не оставляет много места /потребность в фактическом выделении кода на листе: я бы начал кодировать все в прокси-классе, и если этот модуль становится слишком многословным, или если я считаю, что его уровень абстракции должен стать немного выше, то я бы переместил нижнийвыравнивать материал по самому коду рабочего листа. Модули

Workheet и другие модули документов не должны реализовывать интерфейсы - создание рабочего листа, реализующего интерфейс, является хорошим способом запутать и вывести VBA из строя: не делайте этого,Так что это может быть ваш программный код:

Option Explicit

Public Property Get SomeSpecificRange() As Range
    Set SomeSpecificRange = Me.Names("SomeSpecificRange").RefersToRange
End Property

Тогда прокси-класс может сделать это:

Option Explicit
Private sheetUI As Sheet1
Private WithEvents sheet As Worksheet

Private Sub Class_Initialize()
    Set sheet = Sheet1
    Set sheetUI = Sheet1
End Sub

Private Sub sheet_Change(ByVal Target As Range)
    If Intersect(Target, sheetUI.SomeSpecificRange) Then
        '...
    End If
End Sub

Таким образом, прокси-класс может отлично обрабатывать события рабочей таблицы без подключения всего адаптера.,Он также может обрабатывать команды, поступающие от вашего докладчика, через его открытых членов Public.

Но прокси-класс, также называемый «абстрактный лист», не является подходящим местом для ответа на события: это докладчик, который должензапустить шоу.

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

Option Explicit
Public Event SomeSpecificRangeChanged()
Private sheetUI As Sheet1
Private WithEvents sheet As Worksheet

Private Sub Class_Initialize()
    Set sheet = Sheet1
    Set sheetUI = Sheet1
End Sub

Private Sub sheet_Change(ByVal Target As Range)
    If Intersect(Target, sheetUI.SomeSpecificRange) Then
        RaiseEvent SomeSpecificRangeChanged
    End If
End Sub

Затем докладчик может обработать SomeSpecificRangeChanged off прокси-класс - вызовите некоторую UserForm, запустите некоторый запрос к базе данных, независимо от требований:

Private WithEvents proxy As Sheet1Proxy

Private Sub Class_Initialize()
    Set proxy = New Sheet1Proxy
End Sub

Private Sub proxy_SomeSpecificRangeChanged()
    'business logic to run when SomeSpecificRange is changed
End Sub

Проблема в том, что прокси-класс связан с рабочим листом, и теперь докладчикв сочетании с прокси-сервером: мы абстрагировали много вещей, но по-прежнему нет способа поменять зависимость рабочего листа / прокси-сервера на что-то другое и проверить логику презентатора без использования рабочего листа.

Итак, мы создаем интерфейсотделить докладчика от прокси - скажем, ISheet1Proxy ... и теперь мы застряли, потому что мы не можем разоблачитьСобытия в интерфейсе.

Именно здесь в игру вступает шаблон адаптера, позволяющий нам формализовать интерфейсы для «команд» (презентатор -> просмотр) и «событий» (просмотр -> докладчик).

С помощью адаптера рабочий лист / прокси-сервер и презентатор теперь полностью отделены, и теперь вы можете реализовать логику презентатора без знания каких-либо Excel.Worksheet и в идеале любых Excel.Range или Excel.*: каждыйВзаимодействие с рабочим листом формализуется в виде некоторой «команды», отправляемой представлению / рабочему листу / прокси-серверу, или некоторого «события», отправляемого докладчику, точно так же, как в проекте «Морской бой».

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


Очевидно, это много работы.Это отличная практика для принципов ООП и обучения написанию разъединенного кода, который можно тестировать модульно ... но для небольшого VBA-проекта это сильно излишнее ИМО.


Все это рассматривает Excel.* классы как конкретные типы, что, как и VBA, вполне может иметь место.Тем не менее, все типы взаимодействия Excel являются интерфейсами в отношении .NET, поэтому Rubberduck собирается значительно упростить все , предоставив API-оболочку для Moq , чрезвычайно популярного.NET mocking Framework:

Rubberduck mocking framework coming soon-ish

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

Public Sub DoSomething(ByVal target As Range)
    target.Value = 42
End Sub

За это:

Public Sub DoSomething()
    Dim target As Range
    Set target = Sheet1.Range("A1")
    target.Value = 42
End Sub
...