Search code examples
vb.nettypesinterfacefactory-patterngeneric-interface

Is there a way I can replace the 'Object' value type in this Visual Basic dictionary with something more precise?


I'm using a factory to select from several services that are implementing the same generic interface. I have the factory written like this:

Public Class JurisdictionServiceFactory
    Private JurisdictionTypeMapper As Dictionary(Of String, Object)

    Sub New()
        JurisdictionTypeMapper = New Dictionary(Of String, Object)
        JurisdictionTypeMapper.Add("CountryJurisdiction", ProjectGlobals.UnityContainer.Resolve(Of IJurisdictionService(Of CountryJurisdiction)))
        JurisdictionTypeMapper.Add("StateJurisdiction", ProjectGlobals.UnityContainer.Resolve(Of IJurisdictionService(Of StateJurisdiction)))
        JurisdictionTypeMapper.Add("CountyJurisdiction", ProjectGlobals.UnityContainer.Resolve(Of IJurisdictionService(Of CountyJurisdiction)))
        JurisdictionTypeMapper.Add("CityJurisdiction", ProjectGlobals.UnityContainer.Resolve(Of IJurisdictionService(Of CityJurisdiction)))
        JurisdictionTypeMapper.Add("OtherJurisdiction", ProjectGlobals.UnityContainer.Resolve(Of IJurisdictionService(Of OtherJurisdiction)))
    End Sub

    Public Function getJurisdictionService(type As String) As Object
        Return JurisdictionTypeMapper(type)
    End Function
End Class

I would like to replace 'Object' as the value type with something that would allow the compiler to know what methods exist on the object. For example, I want to be able to use autocomplete. I've tried doing this: Private JurisdictionTypeMapper As Dictionary(Of String, IJurisdictionService(Of )), but I just get a message: "Type expected".

This is IJurisdictionService:

Public Interface IJurisdictionService(Of t)
    Inherits IDisposable

    Function GetJurisdictionsByCompany(companyId As Integer) As List(Of t)

    Sub AddJurisdiction(jurisdiction As t)
End Interface

This is an example implementation:

Public Class CountryJurisdictionService
    Implements IJurisdictionService(Of CountryJurisdiction)

    Private jurisdictionRepository As IRepository(Of CountryJurisdiction)

    Public Sub New(jurisdictionRepository As IRepository(Of CountryJurisdiction))
        Me.jurisdictionRepository = jurisdictionRepository
    End Sub

    Public Sub AddJurisdiction(jurisdiction As CountryJurisdiction) Implements IJurisdictionService(Of CountryJurisdiction).AddJurisdiction
        jurisdictionRepository.Add(jurisdiction)
        jurisdictionRepository.Commit()
    End Sub

    Public Function GetJurisdictionsByCompany(companyId As Integer) As List(Of CountryJurisdiction) Implements IJurisdictionService(Of CountryJurisdiction).GetJurisdictionsByCompany
        Return jurisdictionRepository.GetMany(Function(j) j.CompanyID = companyId, False)
    End Function
End Class

Edit: This is the context that the factory will be used in:

Public Sub AddJurisdiction(jurisdiction)
        Using jurisdictionService = jurisdictionServiceFactory.getJurisdictionService(TypeName(jurisdiction))
            jurisdictionService.AddJurisdiction(jurisdiction)
        End Using
End Sub

Solution

  • Who is calling getJurisdictionService? As long as the caller knows what Type they are requesting you can do something like this.

    How about changing this:

    Public Function getJurisdictionService(type As String) As Object
        Return JurisdictionTypeMapper(type)
    End Function
    

    To this:

    Public Function getJurisdictionService(Of T)() As IJurisdictionService(Of T)
        Return DirectCast(JurisdictionTypeMapper(GetType(T)), IJurisdictionService(Of T))
    End Function
    

    And the the caller does this to get a strongly-typed service:

    service = jurisdictionServiceFactory.getJurisdictionService(Of CountryJurisdictionService)()
    

    And of course the dictionary needs to be keyed off of the Type instead of the class name:

    Private JurisdictionTypeMapper As Dictionary(Of Type, Object)
    

    Edit:

    Given the new details you have provided, in that situation I often like to create base class or parent interface to create a sibling relationship between the generic classes, even if this is just for a bit of code-based documentation:

    Public Interface IJurisdictionService
        Inherits IDisposable
    
    End Interface
    
    Public Interface IJurisdictionService(Of t) 
        Inherits IJurisdictionService
    
        Function GetJurisdictionsByCompany(companyId As Integer) As List(Of t)
    
        Sub AddJurisdiction(jurisdiction As t)
    
    End Interface
    

    And that goes for the Jurisdiction objects as well, they could use a parent Interface or base class:

    Public Interface IJurisdictionService
    End Interface
    

    Then Private JurisdictionTypeMapper As Dictionary(Of String, Object) becomes Private JurisdictionTypeMapper As Dictionary(Of String, IJurisdictionService)