I mostly develop AddIns for Office. In the example of MS Word, I have an AddIn which allows to review drafting style, another AddIn which allows to monitor cross-references, another Addin which allows to import graphics from the web, etc. What the AddIns do is irrelevant here, but you should now they all work independently from each other.
I now would like to centralise all these AddIn 'Extensions' into one executable, so that the end users only deal with only one setup for Word, instead of 3. Due to some internal restriction rules, some users may not be allowed to use some AddIn extensions. Hence the need for some verifications at runTime.
At the moment I came up with the following arquitecture, but I am not 100% satisfied. I set below a listing of strenghts and weaknesses. Feel free to suggest aditionnal weaknesses, and/or fixes.
Strengths
- The flow between the UI (button callbacks) and the execution is centralized and standardized.
- The feature availability validation is centralised and easy to update if needs be.
- Dealing with an new AddIn Extension is "easy": build a new
class
whichinherits AddInExtension
, populate theAddInExtensions Collection
, populate the newly builtClass
with relevant callback names in theOverrides Sub FinallyExecute()
, and add their corresponding features (Subs, Classes) into the sameClass
. - Encapsulation is total and there is no risk for interference between two extensions.
Weaknesses
- I am worried I will have to include the entire code of an AddIn Extension into a single .vb file (the AddInExtensionABCServices
Class
). - I am also worried I will have to nest all AddIn Extension Sub-Classes into the Main-Class. I feel this can get messy.
- Architecture is not 100% Hard Typed, in the sens that the callback
Sub
uses text strings twice to run the relevant Sub, whilst I would have loved to be able to do something likeMyExtensions.ABCServices.GetChocolate
.
Re point 7, my intuition tells me this could be fixed through (i) Shared Class
or (ii) a combination of Module
and Namespace
. But (i) a MustInherit Class
cannot be Shared
, and (ii) Modules are hard to keep Private
(default is Public
, hence prone to errors).
Module Module1
Private AddInExtensions As New Collections.Hashtable()
Sub Main()
AddInExtensions.Add("OnlineServices", New AddInExtensionOnlineServices)
AddInExtensions.Add("LocalServices", New AddInExtensionLocalServices)
AddInExtensions.Add("SpaceServices", New AddInExtensionSpaceServices)
'... rest of initialization code
End Sub
'Example of a CallBack
Sub MyCallBackButtonClicked()
Dim MyExtension As IAddInExtension = AddInExtensions("LocalServices")
MyExtension.TryExecute("GetTea")
End Sub
End Module
'# Interface required for Intelisense when calling AddInExtensions As Collections.Hashtable()
Interface IAddInExtension
Sub TryExecute(ByVal ActionName As String)
End Interface
'# Base Class from which will derive each AddIn Extension class
Friend MustInherit Class AddInExtension
Implements IAddInExtension
Protected Property Name As String
Protected Property StatusIsOk As Boolean
'This sub verifies the services are available
Public Sub TryExecute(ByVal ActionName As String) Implements IAddInExtension.TryExecute
If StatusIsOk Then
Me.FinallyExecute(ActionName)
Else
MsgBox("Sorry, you cannot use this service. Please contact your IT department.", vbOKOnly)
End If
End Sub
'The overriden version of this Sub will contain all the features related to a given Extension
Protected MustOverride Sub FinallyExecute(ByVal ActionName As String)
End Class
'# AddIn Extension Class for ONLINE SERVICES
Friend Class AddInExtensionOnlineServices
Inherits AddInExtension
Public Sub New()
MyBase.Name = "OnlineServices"
MyBase.StatusIsOk = True 'Let's say this AddIn Extension is PERMITED (typically would run a function here)
End Sub
'Actions available through the Button Callback for this AddIn Extension
Protected Overrides Sub FinallyExecute(ActionName As String)
Select Case ActionName
Case "GetTime"
Call Me.GetTime()
Case "GetWeather"
Call Me.GetWeather()
End Select
End Sub
Private Sub GetTime()
'...
End Sub
Private Sub GetWeather()
'...
End Sub
End Class
'# AddIn Extension Class for LOCAL SERVICES
Friend Class AddInExtensionLocalServices
Inherits AddInExtension
Public Sub New()
MyBase.Name = "LocalServices"
MyBase.StatusIsOk = True 'Let's say this AddIn Extension is PERMITED (typically would run a function here)
End Sub
'Actions available through the Button Callback for this AddIn Extension
Protected Overrides Sub FinallyExecute(ActionName As String)
Select Case ActionName
Case "GetCoffee"
Call Me.GetCoffee()
Case "GetTea"
Call Me.GetTea()
End Select
End Sub
Private Sub GetCoffee()
'...
End Sub
Private Sub GetTea()
'...
End Sub
End Class
'# AddIn Extension Class for SPACE SERVICES
Friend Class AddInExtensionSpaceServices
Inherits AddInExtension
Public Sub New()
MyBase.Name = "SpaceServices"
MyBase.StatusIsOk = False 'Let's say this AddIn Extension is FORBIDDEN (typically would run a function here)
End Sub
'Actions available through the Button Callback for this AddIn Extension
Protected Overrides Sub FinallyExecute(ActionName As String)
Select Case ActionName
Case "GetOxygen"
Call Me.GetOxygen()
Case "GetSun"
Call Me.GetSun()
End Select
End Sub
Private Sub GetOxygen()
'...
End Sub
Private Sub GetSun()
'...
End Sub
End Class
Also one question aside: I would like to change
Dim MyExtension As IAddInExtension = AddInExtensions("LocalServices")
MyExtension.Execute("GetTea")
by
AddInExtensions("LocalServices").Execute("GetTea")
but Intelisense does not pick up the type so .Execute
does not show in the dropwodn list. Other than minorly inconvenient, would that cause any problem?
I was thinking of typing the Hashtable with something in the like of Private AddInExtensions As New Collections.Hashtable(Of IAddInExtension)
but the compiler does not like it.