I have a library (add-in) with a class that is used in a few small applications. I want to provide a Save
method to that class, which
will depend on the application that is running.
To solve it I am trying to use a strategy pattern(I might be misunderstanding the pattern), but my understanding of the subject is lacking. At runtime I am providing a strategy class that will handle the saving. Common class exposes a Save
method that relays it to the provided strategy class. But to keep consistency it appears to me that common class have to implement the strategy interface as well.
IRecord (Common Class) Interface:
Public Function DoSomething(): End Function
Public Function SetStrategy(ByVal Strategy As IDatabaseStrategy): End Function
Record (Common Class) implementation:
Private RecordStrategy As IDatabaseStrategy
Implements IRecord
Implements IDatabaseStrategy 'Implements this interface to have Save method
Private Function IRecord_DoSomething():
'does whatever the class is supposed to do
End Function
Private Function IRecord_SetStrategy(ByVal Strategy As IDatabaseStrategy)
Set RecordStrategy = Strategy
End Function
Private Function IDataBaseStrategy_Save()
RecordStrategy.Save
End Function
Strategy Interface and Implementation:
IDatabaseStrategy: Public Function Save():End Function
DataBaseStrategyA:
Implements IDatabaseStrategy
Private Function IDataBaseStrategy_Save()
Debug.Print "Saving to database A"
End Function
DataBaseStrategyB:
Implements IDatabaseStrategy
Private Function IDataBaseStrategy_Save()
Debug.Print "Saving to database B"
End Function
Application Module:
Option Explicit
Public Sub ApplicationA()
Dim Item As IRecord
Set Item = New Record
Dim Strategy As IDatabaseStrategy
Set Strategy = New DatabaseStrategyA
Item.SetStrategy Strategy 'this would normally be done with constructor
Dim ItemToSave As IDatabaseStrategy
Set ItemToSave = Item
ItemToSave.Save
End Sub
Public Sub ApplicationB()
Dim Item As IRecord
Set Item = New Record
Dim Strategy As IDatabaseStrategy
Set Strategy = New DatabaseStrategyB
Item.SetStrategy Strategy 'this would normally be done with constructor
Dim ItemToSave As IDatabaseStrategy
Set ItemToSave = Item
ItemToSave.Save
End Sub
With this approach I have to have Record
implement Database strategy
to have Save
method and then recast Item
from IRecord
to IDatabaseStrategy
to use it. I think I am using the pattern incorrectly, so my question is - how do I provide a DatabaseStrategy
to a Record
so that I do not have to recast the object and possibly without implementing IDatabaseStrategy
in Record
?
Alternatives that I have considered:
DatabaseStrategy
around Record
specific to the Record
and the application (DatabaseStrategy.Create(Record).Save
)DatabaseStrategy
as a member of the Record
but then it seems that application has to know that DatabaseStrategy
is a member of the record (Record.DatabaseStrategy.Save
).Class implementation defines what data representations are used to represent the attributes. Each instance of the class will populate the data representation with particular attribute values.
Frameworks are customized into specific applications by deriving classes from framework classes.
Implement required methodsFrom the main menu, select Code | Implement methods or press Ctrl+I . You can also right-click anywhere in the class file, then click Generate Alt+Insert , and select Implement methods.
One class can implement any number of interfaces, allowing a single class to have multiple behaviors.
This is a very good question on design-patterns
with VBA
.
A full description of strategy pattern
can be seen here, but to summarize, this is a UML class and sequence diagram describing it:
Basically, this strategy lets the algorithm vary independently from clients that use it.
Deferring the decision about which algorithm to use until runtime allows the calling code to be more flexible and reusable.
You proposed this two solutions and I want to explain why I didn't used them:
DatabaseStrategy
around Record specific to the Record and the application (DatabaseStrategy.Create(Record).Save
)
Using this solution would decrease cohesion, beacuse the
class
DatabaseStrategy would have to take care of instantiatingRecord
objects
(which as I show later, this can be done applyingFactory-Pattern
)
DatabaseStrategy
as a member of the Record but then it seems that application has to know that DatabaseStrategy
is a member of the record (Record.DatabaseStrategy.Save
)
This design pattern MUST know the strategy, but the innovation is that it can be changed at runtime, passing the desidered strategy object that implements the action methods. In this case I would move
DatabaseStrategy
away fromRecord
, because the last-mentioned is theobject
that needs to be saved, but it's not the agent who performs the action (which could be aDatabase
class
as I'll show later).
To solve the problem of how to instantiate this objects
with VBA class
constructors that don't accept arguments, I used another pattern: the Factory Pattern.
The code below is written just to illustrate how to use these two patterns, so many functions are omitted and there's no real implementation of the database methods. Now here's the rest of the code:
IRecord
class
(Interface):
Public Function getValue() As String: End Function
Public Function setValue(stringValue As String): End Function
Record
class
:
This class basically represent a database record. To keep it simple, we only have avalue
property, but a real implementation would be different of course.
Implements IRecord
Private value As String
Private Function IRecord_getValue() As String
IRecord_getValue = value
End Function
Private Function IRecord_setValue(stringValue As String)
value = stringValue
End Function
IConnectionStrategy
class
(Interface)
This is the Interface of Connection strategy. Little is changed from your code here.
Public Function Save(ByVal record As IRecord): End Function
ConnectionStrategyA
class
Class for Strategy A. Little is changed from your code here.
Implements IConnectionStrategy
Private connectionString As String
Private Sub Class_Initialize()
connectionString = "DRIVER=Oracle Server;SERVER=myA.server.com\DatabaseA;"
End Sub
'Implements Strayegy A
Private Function IConnectionStrategy_Save(ByVal record As IRecord)
Debug.Print "Saving to ", connectionString, "Record with value:", record.getValue
End Function
ConnectionStrategyB
class
Class for Strategy A. Little is changed from your code here.
Implements IConnectionStrategy
Private connectionString As String
Private Sub Class_Initialize()
connectionString = "DRIVER=SQL Server;SERVER=myB.server.com\DatabaseB;"
End Sub
'Implements Strategy B
Private Function IConnectionStrategy_Save(ByVal record As IRecord)
Debug.Print "Saving to ", connectionString, "Record with value:", record.getValue
End Function
Database
class
This is the class that handles the connection with a database. A connection strategy defines how to connect to the database.
Note that you can change the strategy without having to recast the object, just invokesetConnectionStrategy(...)
Private connection As IConnectionStrategy
Private Sub Class_Initialize()
'Initialize everything but strategy.
End Sub
'Funzione che verrà richiamata per inizializzare le propietà della classe
Public Sub InitiateProperties(ByVal connectionStrategy As IConnectionStrategy)
Set connection = connectionStrategy
End Sub
Public Sub saveRecord(ByVal record As IRecord)
connection.Save record
End Sub
Public Sub setConnectionStrategy(ByVal strategy As IConnectionStrategy)
connection = strategy
End Sub
Private Sub Class_Terminate()
'close connections
End Sub
DatabaseFactory
Module
(VBA doesn't have static class, so use a Module instead)
This class is responsible for the instantiation of yourDatabase Objects
'Instantiate a Database with given Strategy
Public Function createDatabaseWithStrategy(strategy As IConnectionStrategy) As Database
Set createDatabaseWithStrategy = New Database
CreateFoglioIdro.InitiateProperties connectionStrategy:=strategy
End Function
'Instantiate a Database with Strategy A
Public Function createDatabaseWithStrategyA() As Database
Set createDatabaseWithStrategyA = New Database
createDatabaseWithStrategyA.InitiateProperties connectionStrategy:=New ConnectionStrategyA
End Function
'Instantiate a Database with Strategy B
Public Function createDatabaseWithStrategyB() As Database
Set createDatabaseWithStrategyB = New Database
createDatabaseWithStrategyB.InitiateProperties connectionStrategy:=New ConnectionStrategyB
End Function
And finally an example of application (App Module
):
Option Explicit
Public Sub ApplicationA()
Dim record As IRecord
Set record = New record
record.setValue ("This is a value")
Dim db As Database
Set db = DatabaseFactory.createDatabaseWithStrategyA
db.saveRecord record
'Prints-> Saving to DRIVER=Oracle Server;SERVER=myA.server.com\DatabaseA; Record with value: This is a value
End Sub
Public Sub ApplicationB()
Dim record As IRecord
Set record = New record
record.setValue ("This is a value")
Dim db As Database
Set db = DatabaseFactory.createDatabaseWithStrategyB
db.saveRecord record
'Prints-> Saving to DRIVER=SQL Server;SERVER=myB.server.com\DatabaseB; Record with value: This is a value
End Sub
As you can see, strategy-pattern
combined with Factory-Pattern
, helps you eliminating most of the repetitive code to initialize and set a strategy for your Database class
.
Hope this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With