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
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.
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.
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
Public members of a class, are members of that class’ default interface. If this class module is
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 '...
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
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
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
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
If we injected an abstract factory into Battleship’s
IPlayerFactory, we would remove the coupling between the controller and the concrete
IPlayer implementations: the controller wouldn’t need to care for
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
Dim player As IPlayer Set player = factory.Create(grid, difficulty)
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
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.