I attempted to Extract Interface on a class module yesterday called HAL_Outlook in my code library.
Here’s yesterday’s article: Refactoring in VBA with RubberDuck: Extract Interface part 1 | Access JumpStart
The dialog came up and it “worked” and created the new interface class, except it didn’t really work because it didn’t use any of my public functions or subs from the original class.
What I expect to happen is for a new empty class to be created that would have all the public functions and subs listed with no code in them.
I’m diving in for a closer look to see what I did wrong.
Looking at the structure of the class, I first notice I have a Public Enum in the class. Not something I usually do, and it’s there to be used as an option to specify the type of email format for Outlook to use: HTML, Plain, RichText, or Unspecified. Not sure if that can go in an interface or be implemented that way, so there’s item number one.
Other than that, I have a single public function in the class called Send_Outlook_Email with lots of options.
Another potential issue is that I have specified that this module be a special kind of class module with the following attributes:
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
So the class gets preloaded into the environment and you can’t create an instance of it. This is only able to be done when you import it from a text file. Otherwise only some of these attributes can be used.
I guess this example, just was not a very good one. Let me try another more traditional class that I wasn’t messing around with like this one. I’ll try this class module: HAL_RC4Cls which has these attributes (the default I believe):
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
This class has no Enums and has only 2 public functions: encrypt and decrypt. This is a really rather insecure reversible encryption algorithm that this class uses, but that doesn’t really matter for our test. Let’s see what happens if I use the RubberDuckVBA Extract Interface on this class.
Here are the defaults of the dialog:
I notice it added an I to the front of the class name, and I see the two public functions at the bottom with their return types and parameters listed. None of the private methods of the class are listed. I will go ahead and select the two members.
I will also leave the default implementation option selected “Forward Interface Member Calls to Object Members”
The other options in that drop down are as follows:
And clicking ok (after pressing the Selecting All button) I get this new class with the following code:
IHAL_RC4Cls
'@Exposed
'@Interface
Option Explicit
Public Function encrypt(sInput As String, sKey As String) As String
End Function
Public Function decrypt(sEncryption As String, sKey As String) As String
End Function
And here is what the old class now looks like. It has had a Implements line added and the encrypt and decrypt functions:
Option Compare Database
Option Explicit
'////////////////////////// CODE ADDED BY RubberDuckVBA Extract Interface
Implements IHAL_RC4Cls
Private Function IHAL_RC4Cls_encrypt(sInput As String, sKey As String) As String
IHAL_RC4Cls_encrypt = encrypt(sInput, sKey)
End Function
Private Function IHAL_RC4Cls_decrypt(sEncryption As String, sKey As String) As String
IHAL_RC4Cls_decrypt = decrypt(sEncryption, sKey)
End Function
'////////////////////////// CODE ADDED BY RubberDuckVBA Extract Interface
' ----------------------------------------------------------------
' Procedure Name: encrypt
' Purpose: Encrypt a string with RC4 and return a Hex-encoded string
' Procedure Kind: Function
' Procedure Access: Public
' Parameter sInput (String): The string to be encrypted
' Parameter sKey (String): Private key used to encrypt/decrypt
' Return Type: String
' Author: steve, from original code by Jon
' Date: 3/11/2022
' ----------------------------------------------------------------
Public Function encrypt(sInput As String, sKey As String) As String
Dim strCrypt As String
strCrypt = HCI_CryptRC4(sInput, sKey)
encrypt = HCI_ToHexDump(strCrypt)
End Function
' ----------------------------------------------------------------
' Procedure Name: decrypt
' Purpose: Decrypt values encoded with RC4 encryption
' Procedure Kind: Function
' Procedure Access: Public
' Parameter sEncryption (String): The encryption string
' Parameter sKey (String): Private key used to encrypt/decrypt
' Return Type: String
' Author: steve, from original code by Jon
' Date: 3/11/2022
' ----------------------------------------------------------------
Public Function decrypt(sEncryption As String, sKey As String) As String
Dim strCrypt As String
strCrypt = HCI_FromHexDump(sEncryption)
decrypt = HCI_CryptRC4(strCrypt, sKey)
End Function
Private Function HCI_CryptRC4(sText As String, sKey As String) As String
'...
End Function
Private Function pvCryptXor(ByVal lI As Long, ByVal lJ As Long) As Long
'...
End Function
Private Function HCI_ToHexDump(sText As String) As String
'...
End Function
Private Function HCI_FromHexDump(sText As String) As String
'...
End Function
The way this was implemented, the original class can still be instantiated as is with no changes and continue working exactly the way it did in the past.
It can also now be implemented into an object declared as the interface. So it’s a safe way to change the existing class to utilize the new features of the interface without breaking any existing code. This is definitely more similar to what I was expecting with this Refactor.
It’s also good to know that you need to have a normal class object that is creatable and does not have a predefined ID (meaning it is preloaded and ready to go as soon as Access starts running this db).