The big buzzy words are just a name given to what’s happening when we identify a procedure’s dependencies and decide to inject them. Like any procedure that needs to invoke Workbook.Worksheets.Add
must depend on a given specific Workbook
object. If the workbook we mean to work with is the document that’s hosting our VBA project, then that workbook is ThisWorkbook
. Otherwise, you might have been writing something like this in a standard module:
Public Sub DoSomething()
Dim sheet As Worksheet
Set sheet = Worksheets.Add
'...
End Sub
The problem is the implicit dependency in ActiveWorkbook
. Indeed, if we don’t qualify a Worksheets
call, then we’re implicitly writing [Global].Worksheets.Add
, i.e. Application.Worksheets
, …which means ActiveWorkbook.Worksheets
– that is, whatever workbook happens to be active at that time. While that can be useful in certain specific situations, most of the time you will rather want to be working with one very specific Workbook
object. The hidden, implicit dependency in the above snippet, is a Workbook
; with dependency injection, you inject that Workbook
object instead:
Public Sub DoSomething(ByVal wb As Workbook)
Dim sheet As Worksheet
Set sheet = wb.Worksheets.Add
'...
End Sub
As a result, procedures explicitly tell their callers what their dependencies are. If a procedure starts needing many parameters, it speaks volumes about the code and its refactoring opportunities! Maybe two or more parameters are closely related and should really become a class in its own right, with its data and its methods; maybe the procedure is simply doing too many things though – having too many dependencies is easily a tell-tale sign.
Dependencies can be hard to find. Other times they’re plain obvious:
Public Sub DoSomething()
Dim thing As ISomething
Set thing = New Something
thing.DoStuff
'...
End Sub
In any case, correctly identifying all the dependencies of a procedure is definitely the hardest part of DI. The actual injection technique used makes for interesting decision-making though. If you’ve been passing parameters between procedures for any amount of time, congrats, you already master method injection.
Method Injection
We use method injection when we pass dependencies around as parameters to a method of an object.
Public Sub DoSomething(ByVal thing As ISomething)
thing.DoStuff
'...
End Sub
You would inject a parameter that way if no other method in that class would love to share that dependency – in which case you would preferably inject the dependency at the class level, and have one less parameter to every method that would otherwise need it.
Property Injection
Using a public Property Set
member, we allow code written against the class’ default interface to inject a dependency at the class/instance level.
Public Property Get Thing() As ISomething
Thing = this.Thing
End Property
Public Property Set Thing(ByVal value As ISomething)
Set this.Thing = value
End Property
Public Sub DoSomething()
this.Thing.DoStuff
'...
End Sub
Property injection is nice, but the downside is that the point of injection isn’t as tight as with method injection: now we need to deal with temporal coupling, and make sure DoSomething
can’t run if Thing
isn’t set. Debug.Assert
statements are perfect for this, since that kind of bug should be caught early on:
Debug.Assert Not this.Thing Is Nothing 'execution stops if expression is false
this.Thing.DoStuff '<~ this.Thing is safe to invoke members against
Alternatively, raise a custom error that explains that the Thing
property needs to be assigned before DoSomething
can be invoked.
But that won’t prevent other code from tampering with the assigned reference, since it’s Public
. Remember when I said it allows code written against the default interface to invoke the setter? If we consider VBA classes’ default interface as the “concrete implementation”, and make it explicitly implement another interface, we can expose the Property Get
member and leave the Property Set
only accessible from the default interface – and since the “D” of SOLID says we shall be coding against interfaces, then very little code needs to know about the default interface: only the code that’s New
-ing up the object does, in fact.
Implements IFoo
Public Property Get Thing() As ISomething
Thing = this.Thing
End Property
Public Property Set Thing(ByVal value As ISomething)
Set this.Thing = value
End Property
Private Property Get IFoo_Thing() As ISomething
Set IFoo_Thing = this.Thing
End Property
Private Sub IFoo_DoSomething()
this.Thing.DoStuff
'...
End Sub
Any Public
members of a class, are members of that class’ default interface. If this class module is Foo
, then Foo.Thing
can be read and assigned from a Foo
variable. Since the class implements the IFoo
interface and that this interface doesn’t expose a Property Set
member for the Thing
property, code written against IFoo
will only be able to access the Property Get
member and the DoSomething
method: whatever code is responsible for injecting the Thing
dependency, is the only code that needs to know about Foo
and its Property Set Thing
member.
Dim t As Foo
Set t = New Foo
Set t.Thing = New Something
'...
If you’ve read about factories in VBA, then you’ve already seen this in action; the Battleship project demonstrates it as well.
Where are all things created?
Since we’re injecting dependencies all the way down, this New
-ing up necessarily happens near the entry point of the macro: ideally all dependencies are resolved and injected in one single place, known as the composition root. See, in the above snippet, imagine the Something
dependency injected into foo.Thing
itself had its own dependencies, which might themselves have their own dependencies: the dependency graph of a simple application should be relatively manageable, but larger applications configure a DI/IoC Container and let that object be responsible for automatically injecting all dependencies everywhere; Rubberduck uses Castle Windsor, and used Ninject before that. Unfortunately VBA does not have any offering of IoC containers at the moment, and until we’re able to create a VBA class instance from C# code, Rubberduck can’t do it either.
But, honestly, a VBA project shouldn’t become so huge as to really need an IoC container to resolve a dependency graph: poor man’s DI is perfectly fine! Here is one of the entry points of the Battleship code showing how each component is New
‘d up and injected into other components – a WorksheetView
is used for creating a GridViewAdapter
through property injection (via a factory method), injected along with a GameRandomizer
into a GameController
through method injection in the NewGame
method:
Public Sub PlayWorksheetInterface()
Dim view As WorksheetView
Set view = New WorksheetView
Dim randomizer As IRandomizer
Set randomizer = New GameRandomizer
Set controller = New GameController
controller.NewGame GridViewAdapter.Create(view), randomizer
End Sub
The controller has other dependencies that should be injected as well. One good example can be found in the viewAdapter_OnCreatePlayer
handler:
Dim player As IPlayer
Select Case pt
Case HumanControlled
Set player = HumanPlayer.Create(grid)
Case ComputerControlled
Select Case difficulty
Case AIDifficulty.RandomAI
Set player = AIPlayer.Create(grid, RandomShotStrategy.Create(rng))
Case AIDifficulty.FairplayAI
Set player = AIPlayer.Create(grid, FairPlayStrategy.Create(rng))
Case AIDifficulty.MercilessAI
Set player = AIPlayer.Create(grid, MercilessStrategy.Create(rng))
End Select
End Select
If we injected the IPlayer
implementations from the start, we would be creating the players before the game even knows on which grid each player is playing, or whether a human player is even involved. So in this handler the GameController
class is being coupled with HumanPlayer
and AIPlayer
classes, and this coupling isn’t ideal at all, because if the controller is coupled with a HumanPlayer
object, then there’s no way we can write any unit tests for any of the controller logic. Surely there’s a better way to do this!
When you can’t create a dependency at the entry point
Sometimes you just can’t create the dependency until much later during the execution of a macro, so it’s not possible to inject it anywhere. For example you might need an ADODB.Connection
, but the SQL authentication requires you to prompt the user for credentials – it would be clunky to prompt the user for database credentials at the start of the macro, before they even click any button to do something with a database. So instead of injecting the ADODB.Connection
dependency directly, instead we inject an abstract factory, and since the role of a factory is precisely to create an instance of something, we’re not breaking any rules by New
-ing up the connection object in there:
Implements IConnectionFactory
Private Function IConnectionFactory_Create(ByVal user As String, ByVal pwd As String) As ADODB.Connection
Dim result As ADODB.Connection
Set result = New ADODB.Connection
result.ConnectionString = "..." & user & "..." & pwd & "..."
result.Open
Set IConnectionFactory_Create = result
End Function
And now whatever class needs a database connection can have an IConnectionFactory
object injected as a dependency, and own a new connection object by invoking its Create
method.
If we injected an abstract factory into Battleship’s GameController
, say, IPlayerFactory
, we would remove the coupling between the controller and the concrete IPlayer
implementations: the controller wouldn’t need to care for HumanPlayer
or AIPlayer
, only that there’s a factory it can give parameters to, and get an IPlayer
object back. That would greatly simplify the entire logic for the viewAdapter_OnCreatePlayer
handler:
Dim player As IPlayer
Set player = factory.Create(grid, difficulty)
If the difficulty
is AIDifficulty.Unspecified
, the factory yields a HumanPlayer
; otherwise, we get an AIPlayer
– and by doing that, we’ve effectively removed a responsibility from the controller: now the concern of creating player objects belongs to a PlayerFactory
class that can be injected into the controller at the entry point, as an IPlayerFactory
dependency; the factory itself is coupled with the various IGameStrategy
implementations, but that coupling isn’t hindering any testing, and so injecting some GameStrategyFactory
would be possible, but it would also be over-abstracting/over-engineering, since IGameStrategy
is only really relevant for an IPlayer
, so a factory that’s creating players needs to know about the game strategies.
So now we can write tests for the factory to prove it returns the correct expected IPlayer
implementations given different AIDifficulty
arguments, and we could inject a fake implementation of the IPlayerFactory
into the controller, and then write a series of tests that prove the GameController
invokes the IPlayerFactory.Create
method with the appropriate arguments in response to the GridViewAdapter.OnCreatePlayer
event: given gridId=1
, the handler instructs the factory it needs a player in grid 1; given pt=PlayerType.HumanControlled
, the handler instructs the factory to create a player with AIDifficulty.Unspecified
; given difficulty=AIDifficulty.MercilessAI
, the handler instructs the factory to create a player with that specified difficulty
parameter. We could also test that after two players have been created, the controller invokes the OnBeginShipPosition
method against the view adapter, and so on.
Dependency injection promotes decoupling of dependencies, and testable procedures with fewer responsibilities.