I have two objects that need to interact with each other one is called Collateral
the other is called Model
. Model
is an abstract Class is implemented by Model_A
, Model_B
, Model_AB
. Each Collateral object has a collection of models
as one of its properties. In order to initialize each Model
I will need to use information from Collateral
(and still another object lets call it User_Input
), that information will vary with implementation of Model
.
My question is it possible to use a constructor that will be aware of what object is creating it(in this case Model
Constructor that knows what Collateral
instantiated it)?
If not I assume that someone will suggest for me to use abstract factory pattern, if so is so how would it look like(I'm afraid I'm still green when it comes to OOP)?
For Simplicity's sake assume following:
Collateral
has properties A, B, C , Models_CollectionCollateral
Calls procedure Run
for Each of Models
it created( has in Models_Collection)Model
has a public Sub called Run
which is implemented in all classes bellowRun
Manipulates Collateral
Model_A
requires property A to initializeModel_B
requires property B to initializeModel_AB
requires property A, B to initializeHere is a Simplified Code of how I assume this should look like:
Collateral
Dim A, B, C as Variant
Dim Model_Collection as Collection
Sub New_Model( Model_Type as String)
Model_Collection.Add(Model_Implementation)
End Sub
Sub Execute_Models()
For Each Model in Model_Collection
Model.Run(Me)
Next Model
End Sub
Model
Sub Run()
End
Model_A
Implements Model
Sub Class_Initialize()
'Some code that takes property A from Collateral that Created this object
Sub Run(Collateral as Collateral)
'Some Code
End Sub
Model_B
Implements Model
Sub Class_Initialize()
'Some code that takes property B from Collateral that Created this object
Sub Run(Collateral as Collateral)
'Some Code
End Sub
Model_AB
Implements Model
Sub Class_Initialize()
'Some code that takes property A, and B from Collateral that Created this object
Sub Run(Collateral as Collateral)
'Some Code
End Sub
First, lets answer your question. How can you dynamically create instances of different class that all implement the same interface? As was pointed out, VBA doesn't have any constructors, so you're correct. A Factory Pattern is called for here.
How I tend to go about this is define a public enum in the Interface class that keeps track of what classes have been implemented. Any time you implement a new one, you'll need to add it to your enum and Factory. It's a bit more maintenance then I like, but without proper reflection, there's not much we can do about that.
So, the IModel
interface:
Public Enum EModel
ModelA
ModelB
ModelC
End Enum
Public Sub Run
End Sub
Your models themselves remain unchanged. Then back in your Collateral
implement your New_Model
like this.
private models as Collection
Public Sub New_Model(ByVal type As EModel) As IModel
dim model As IModel
Select Case type
Case EModel.ModelA: Set model = New ModelA
Case EModel.ModelB: Set model = New ModelB
Case EModel.ModelC: Set model = New ModelC
End Select
models.Add model
End Sub
Note that it's better to use the enum than a string as in your example so it gets compile time checked for errors instead of runtime. (This removes the chances of misspelling something.)
If it was me implementing this, I would create an actual separate class ModelFactory
. Then Collateral
would call on the model factory to get what it needs. It makes a nice separation of concerns I think.
An implementation would look something like this, based on your requirements.
Public Function CreateModel(Optional A As Variant, Optional B As Variant, Optional C As Variant)
If Not A Is Nothing Then
If B Is Nothing Then
Set CreateModel = New ModelA
Exit Function
Else
Set CreateModel = New ModelC
Exit Function
End If
End If
If Not B Is Nothing Then
Set CreateModel = New ModelB
Exit Function
End If
End Function
Note that this entirely does away with the enum and the need to specify the type. The factory knows what to create based on which arguments are available to it.
Then your Collateral
class simply calls on the factory and gives it whatever it has.
Private A,B,C
Private models As Collection
Private factory As ModelFactory
Private Sub Class_Initialize()
Set factory = New ModelFactory
End Sub
Public Sub New_Model()
models.Add factory.CreateModel(A,B,C)
End Sub
Now, I'm going to pre-emptively answer your next question, because I feel like you're on the verge of asking it already.
How can I tell exactly what type of model I have?
Well, for that you have a few options that are detailed a bit in this code review Q & A. It depends on your use case, but here they are.
TypeName(arg)
- Returns the string name of the object. For example:
Dim model As IModel
Set model = New ModelA
Debug.Print TypeName(model) '=> "ModelA"
TypeOf
and Is
- Checks the type of a variable a bit more strongly. Details are in the question I linked to, but here is an example.
Dim model as IModel
Set model = SomeFunctionThatReturnsAnIModel()
If TypeOf model Is ModelA Then
' take some specific action for ModelA types
Else If TypeOf model Is ModelB Then
' ModelB type specific action
Else If ...