Interface implementation
Here is a small example on using interface implementation mechanism in
VBA. For this example, you need to insert three new Class Modules and one Standard
module. Follow the instructions given here below.
Insert a
new VBA Standard Module and copy-paste the following code:
Option Explicit
'
Sub tester()'
' We create an object which has a type of IInterface
Dim obj As IInterface
'
' Because
ObjectOne implements IInterface, We
' can set this
object to be ObjectOne
Set obj = New ObjectOne
obj.printMessage "Have
a nice day!"
'
' Because
ObjectTwo also implements IInterface, We
' can set this
object to be ObjectTwo
Set obj = New ObjectTwo
obj.printMessage "You
too!"
'
' We release the
object
Set obj = Nothing
End Sub
'
Insert a
new VBA Class Module (name = IInterface) and copy-paste the following code:
Option Explicit
'
' interface to be
implemented
Public Function
printMessage(ByVal s As
String)
End Function
End Function
'
Insert a
new VBA Class Module (name = ObjectOne) and copy-paste the following code:
Option Explicit
'
Implements IInterface
'
Private Function
IInterface_printMessage(ByVal s As String)
Debug.Print "ObjectOne
says " + s
End Function
'
Insert a
new VBA Class Module (name = ObjectTwo) and copy-paste the following code:
Option Explicit
'
Implements IInterface
'
Private Function
IInterface_printMessage(ByVal s As String)
Debug.Print "ObjectTwo
says " + s
End Function
'
We are
done. If you run this program, it will print out the following two strings:
ObjectOne
says Have a nice day!
ObjectTwo says You too!
What's
happening here? We create first an interface. After that, we
create two objects, both having the type IInterface because these
objects are implementing IInterface. This means, that we could do the following:
Replace
the existing VBA Standard Module and copy-paste the following code:
Option Explicit
'
Sub tester()
'
' We create an
object which has a type of IInterface
Dim obj As IInterface
'
' Because
ObjectOne implements IInterface, We
' can set this
object to be ObjectOne
Set obj = New ObjectOne
Set obj = New ObjectOne
printOut "Have
a nice day!", obj
'
' Because
ObjectTwo also implements IInterface, We
' can set this
object to be ObjectTwo
Set obj = New ObjectTwo
printOut "You
too!", obj
'
' We release the
object
Set obj = Nothing
End Sub
'
Private Function
printOut(ByVal message As
String, ByRef
obj As IInterface)
obj.printMessage message
End Function
'
If you run this new main program, it will print out exactly the same two strings as before:
ObjectOne
says Have a nice day!
ObjectTwo says You too!
However, in this new program we are witnessing
interface-based polymorphism in action. PrintOut function in the previous
code takes object having a type IInterface in its function interface. Since ObjectOne and
ObjectTwo are both IInterface implementations, function accepts the both objects as inputs.
Strategy
design pattern implementation in VBA
Before presenting the actual Strategy Pattern, I would like to present one motivational example, which can help us to understand in a more concrete way, what is the problem what we are trying to solve.
Design - before
Let us
think, that we need to build a program to price a simple forward with the following
requirement: we should be able to price a forward with or without given cashflow PV. How would
you implement this in VBA? Let me present first, how I have been implementing this,
before I got myself familiar with interface implementation.
Insert a
new VBA Standard Module and copy-paste the following code:
Option Explicit
'
Sub tester()
'
' create pricer
for forward
Dim pricer As New ForwardPricer
Debug.Print pricer.price(100, 0.02, 1.25,
ENUM_CASHFLOW_TYPE.none)
Debug.Print pricer.price(100, 0.02, 1.25,
ENUM_CASHFLOW_TYPE.discrete, 7.25)
Set pricer
= Nothing
End Sub
'
Insert a
new VBA Class (name = ForwardPricer) Module and copy-paste the following code:
Option Explicit
'
'
Public Enum
ENUM_CASHFLOW_TYPE
none = 1
discrete = 2
End Enum
'
Public Function
price(ByVal spot As
Double, ByVal
yield As Double,
ByVal maturity As
Double, _ByVal cashFlowType As ENUM_CASHFLOW_TYPE, Optional ByVal cashFlowPV As Double) As Double
'
Select Case cashFlowType
'
Case
ENUM_CASHFLOW_TYPE.none
price = spot * Exp(yield * maturity)
'
Case
ENUM_CASHFLOW_TYPE.discrete
price = (spot - cashFlowPV) * Exp(yield * maturity)
'
price = (spot - cashFlowPV) * Exp(yield * maturity)
'
End Select
End Function
'
The
previous program will print out prices for two different forwards. There is
absolutely nothing wrong in this program. However,
as soon as someone wants to add any new requirements for your pricer, you need to modify your ForwardPricer class and Enumerator. All in all, you will end up babysitting Enumerator and Select Case branches, when inserting
any new pricing algorithm, for example.
If we
think about pricing financial instrument, it's all about pricing algorithms which can vary. What if we could create a design, that could
enable the addition of any new algorithm in a such a way, that we do not need to change anything in our existing classes?
Design - after
For this
example, you need to insert four new Class Modules and two Standard modules. Follow
the instructions given here below.
Insert a
new VBA Standard Module and copy-paste the following code. This module is for Enumerator, which is going to be used for implementing parameter wrapper.
Option Explicit
'
Public Enum
Var
Spot
Yield
maturity
maturity
CashflowPV
End Enum
'
Insert a
new VBA Standard Module and copy-paste the following code. This our tester
program.
Option Explicit
'
Sub tester()
'
Dim
parameters As New
Collection
Dim p As Scripting.Dictionary
'
' create a set of
parameters
Dim i As Integer
For i = 1 To 20
'
Set p =
New Scripting.Dictionary
p.Add Var.Spot, 100
p.Add Var.CashflowPV, 7.25
p.Add Var.Yield, 0.02
p.Add Var.maturity, 0.25 + (i - 1) *
0.25
parameters.Add p
Set p =
Nothing
Next i
'
' use algorithm
to price forward with cashflow PV
Dim
algorithm As IForward: Set
algorithm = New Forward_DiscreteIncome
Dim pricer As New ForwardEngine
Dim
prices_DiscreteIncome As Collection: Set prices_DiscreteIncome = pricer.execute(algorithm,
parameters)
Set
parameters = Nothing
'
' create another
set of parameters
For i = 1 To 20
'
Set p =
New Scripting.Dictionary
p.Add Var.Spot, 100
p.Add Var.Yield, 0.02
p.Add Var.maturity, 0.25 + (i - 1) *
0.25
parameters.Add p
Set p =
Nothing
Next i
'
' use algorithm
to price forward without cashflow PV
Set
algorithm = New Forward_NoIncome
Dim
prices_noIncome As Collection: Set prices_noIncome = pricer.execute(algorithm,
parameters)
Set
parameters = Nothing
'
' place debug
point below this row and investigate your
'
prices_DiscreteIncome and prices_noIncome collections Locals window
Set p = Nothing
Set
prices_DiscreteIncome = Nothing
Set
prices_noIncome = Nothing
Set pricer
= Nothing
Set
algorithm = Nothing
End Sub
'
Insert a
new VBA Class Module (name = IForward) and copy-paste the following code. This
is the actual interface class.
Option Explicit
'
' Interface to be
implemented
Public Function
price(ByRef p As
Scripting.Dictionary) As Double
End Function
'
Insert a
new VBA Class Module (name = ForwardEngine) and copy-paste the following
code.This is the class which uses concrete implementations.
Option Explicit
'
Public Function
execute(ByRef algorithm As IForward, ByRef parameters As Collection) As
Collection
'
Dim result As New Collection
Dim i As Long
For i = 1 To parameters.Count
Dim
price As Double:
price = algorithm.price(parameters.Item(i))
result.Add price
Next i
'
Set execute
= result
End Function
'
Insert a
new VBA Class Module (name = Forward_NoIncome) and copy-paste the following
code. This is a concrete implementation of IForward interface.
Option Explicit
Implements IForward
'
Private Function
IForward_price(ByRef p As
Scripting.Dictionary) As Double
IForward_price = p.Item(Var.Spot) * Exp(p.Item(Var.Yield) *
p.Item(Var.maturity))
End Function
'
Insert a
new VBA Class Module (name = Forward_DiscreteIncome) and copy-paste the
following code. This is another concrete implementation of IForward interface.
Option Explicit
Implements IForward
'
Private Function IForward_price(ByRef p As Scripting.Dictionary) As Double
Private Function IForward_price(ByRef p As Scripting.Dictionary) As Double
'
IForward_price = (p.Item(Var.Spot) -
p.Item(Var.CashflowPV)) * Exp(p.Item(Var.Yield) * p.Item(Var.maturity))
End Function
'
The
previous program for pricing 20 forwards with two different pricing algorithms is implementing Strategy Design Pattern. The great thing in this
pattern is, that now if you need to implement any new pricing algorithm, you can just "plug-in" a new interface implementation,
without modifying any existing class. The only requirement is, that any new
implementation must implement everything what the interface has. However, you
are completely free to have any amount of private functions inside your implementations.
In our forward pricing example, any implementation must implement this public
function:
Option Explicit
'
' Interface to be
implemented
Public Function
price(ByRef p As
Scripting.Dictionary) As Double
End Function
'
Constructor problem
In my last posting http://mikejuniperhill.blogspot.fi/2013/05/handling-parameters-dynamically-with.html I was opening up my current approach for having specific input parameters wrapped inside a data structure for allowing maximum flexibility, compared to the approach where you take all specific input parameters in a function interface separately. Now, If we think about that previous forward pricing example, how could we even use that latter approach? Interface public function (price), which we must implement for every implementation, is having a very specific signature for its input parameters. AFAIK, we cannot change that signature in our implementations.
This means, that if we have the following public interface:
Public Function price(ByVal a As Double) As Double
We cannot have the following implementations:
Private Function IForward_price(ByVal a As Double, ByVal c As Double) As Double
Private Function IForward_price(ByVal a As Integer) As Double
If we think this a bit more, why do we have such a problem with this issue after all? Answer: VBA does not have real constructors.
For example, in C# you also have interface implementation and any implementation must implement everything, what has been defined in interface. However, there you have constructors for all input parameters and those constructors does not need to have any homogenous signature for input parameters. I wrote the corresponding program in C# (a bit simpler main program) just for the comparison.
In this program, instead of feeding parameters inside interface price-method, we can use class constructors in a normal way for feeding required set of parameters to objects. In our VBA implementation, we are doing the same thing by wrapping required set of parameters into a data structure (parameter wrapper) and then feeding that data structure in interface price-method.
For example, in C# you also have interface implementation and any implementation must implement everything, what has been defined in interface. However, there you have constructors for all input parameters and those constructors does not need to have any homogenous signature for input parameters. I wrote the corresponding program in C# (a bit simpler main program) just for the comparison.
In this program, instead of feeding parameters inside interface price-method, we can use class constructors in a normal way for feeding required set of parameters to objects. In our VBA implementation, we are doing the same thing by wrapping required set of parameters into a data structure (parameter wrapper) and then feeding that data structure in interface price-method.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace cApp_24052013 { class Program { static void Main(string[] args) { double f; IForward fwd = new Forward_NoIncome(100, 0.02, 1.25); ForwardPricer pricer = new ForwardPricer(); f = pricer.execute(fwd); Console.WriteLine(f); // fwd = new Forward_DiscreteIncome(100, 7.25, 0.02, 1.25); f = pricer.execute(fwd); Console.WriteLine(f); } } public class ForwardPricer { public double execute(IForward f) { return f.price(); } } public interface IForward { double price(); } public class Forward_NoIncome : IForward { private double s; private double r; private double t; // public Forward_NoIncome(double s, double r, double t) { this.s = s; this.r = r; this.t = t; } public double price() { return s * Math.Exp(r * t); } } public class Forward_DiscreteIncome : IForward { private double s; private double d; private double r; private double t; // public Forward_DiscreteIncome(double s, double d, double r, double t) { this.s = s; this.d = d; this.r = r; this.t = t; } public double price() { return (s - d) * Math.Exp(r * t); } } }
Well, that's all I wanted to share this time. Hopefully
you have got some new ideas for your own programs. Have a great weekend!
-Mike
Thank you for taking the time to write up this blog. I have been looking very hard to find decent design patterns for ms-access and this is the best insight I have found into using interfaces in VBA.
ReplyDelete