I was excited yesterday to discover that I wanted an interface and how this might help with my TDD. I refactored the FormListener to have an interface.
Here’s why I’m so excited.
Now I can have a live version of the Listener AND a test version of the listener that are different, but have to meet the requirements of the interface and are interchangeable in terms of the functionality I want any particular FormListener to work.
So, again, the interface for the FormListener (saved with the name IFormListener) is currrently VERY simple:
'@Interface
Option Explicit
Public Sub Setup(theForm As Access.Form)
End Sub
This simply means that any class I create that uses the line will not compile unless it includes the elements listed in the interface:
Implements IFormListener
Currently, the only thing this means is that a listener must have a public Setup routine that accepts an Access Form object. But, I can add things to that interface as I continue and this will require all classes based on the interface to have those elements. For example, I will add a Public Event at some point in the future which will require all the classes based on the interface to expose that event.
As long as each interface will expose the things I want the coder to ask it to do, this means I can create other versions of the listener that have different internal code.
At this point I can now create a new Test class for the FormListener based on IFormListener and add new functions for the testing which won’t be required for another class for the FormListener that will be used in the live environment.
So, I will rename the current class that I originally called FormListener to: TestFormListener. Again, here is the code of the now newly named TestFormListener:
Option Compare Database
Option Explicit
Implements IFormListener
Private WithEvents frm As Access.Form
Private m_bBeforeUpdateTriggered As Boolean
Public Sub Setup(theForm As Access.Form)
Set frm = theForm
frm.BeforeUpdate = "[Event Procedure]"
End Sub
Public Property Get BeforeUpdateTriggered() As Boolean: BeforeUpdateTriggered = m_bBeforeUpdateTriggered: End Property
Public Property Let BeforeUpdateTriggered(ByVal bNewValue As Boolean): m_bBeforeUpdateTriggered = bNewValue: End Property
Private Sub frm_BeforeUpdate(Cancel As Integer)
Me.BeforeUpdateTriggered = True
End Sub
Public Function TimesFieldChanged(theFieldName As String) As Long
Dim retVal As Long
retVal = 0
TimesFieldChanged = 0
End Function
Private Sub IFormListener_Setup(theForm As Access.Form)
Setup theForm
End Sub
So, the only thing the interface requires is the Private Sub IFormListener_Setup(theForm As Access.Form)
The interface doesn’t care what’s in the routine, just prevents it from compiling if it’s not there. This also will be publicly available and be what is called when I do this in a test module:
' This will only show "Setup" as a method in Intellisense
' When this Setup method is called, it will actually call the "IFormListener_Setup" sub
Dim NewListener As IFormListener
Set NewListener = New TestFormListener
NewListener.Setup NewForm
Note here that I am defining a NewListener as an IFormListener. The Intellisense in VBA will use the IFormListener methods and show those only, not the other public properties and methods I have added like “TimesFieldChanged” or “BeforeUpdateTriggered”
Alternatively I could Dim NewListener As TestFormListener and then the Intellisense would show the public subs and properties of TestFormListener instead of the Interface public sub.
However, either way, all the public elements that aren’t named the same as in the Interface are available either way. I will test this when I do the next TDD segment though to make sure I am correct and explain the difference a little futher.