Home » excel » excel – Application specific implementation of a class method

excel – Application specific implementation of a class method

Posted by: admin March 9, 2020 Leave a comment

Questions:

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 IDatabaseStrategyto 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:

  • Wrapping DatabaseStrategy around Record specific to the Record and the application (DatabaseStrategy.Create(Record).Save)
  • Exposing 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).
How to&Answers:

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:

enter image description here

  • Summary

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:

  • Wrapping 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 instantiating Record objects (which as I show later, this can be done applying Factory-Pattern)

  • Exposing 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 from Record, because the last-mentioned is the object that needs to be saved, but it’s not the agent who performs the action (which could be a Database 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.

enter image description here

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:

  • 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 a value 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 invoke setConnectionStrategy(...)

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 your Database 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.