Search code examples
vb.netgenericsabstract-classfactory-pattern

Using abstract generic classes in software design factory pattern


I have a bit of a design issue. I created a rate calculator as follows :

Public Interface ICalculator
 Property PaymentTerm As Double
 Function Calculate() As CommissionValues
 ReadOnly Property CalculationRule As CalculationRuleEnum
End Interface

Public Interface IFlexibleRateCalculator
 Inherits ICalculator
  Property TransferRate As Decimal
End Interface

Public Interface IFixedRateCalculator
 Inherits ICalculator
  Property ContractRate As Decimal
End Interface

Public Interface IRateSettingBase
  Property RateType As RateTypeEnum
  ReadOnly Property Calculator As ICalculator
End Interface

Public MustInherit Class RateSetting
 Implements IRateSettingBase
  Public MustOverride ReadOnly Property Calculator() As ICalculator Implements IRateSettingBase.Calculator

I can do something like this:

dim ratevalues as RateValues = RateSetting().Calculator.Calculate()

Pretty simple. The problem is that each type of calculator has their own set of properties that need to be set in order for their Calculate() methods to work properly. So I end up having to implement as follows

FlexibleRateCalculator
Implements IFlexibleRateCalculator
    Private mRequestedRate As Decimal
    Public Function Calculate() As RateValues Implements ICalculator.Calculate
    
FixedRateCalculator
 Implements IFixedRateCalculator
    Private mTransferRate As Decimal
    Public Function Calculate() As RateValues Implements ICalculator.Calculate

What is the best way using generics and abstract classes to create a factory pattern that will generate a calculator of a specific type dynamically??

I need a very generic solution as many calculation rates will be added and modified all with their own parameters needed for the calculation logic. I want to be able to do this quickly and possibly control these rate calculation via db. FYI answers in C# or VB.Net are welcome :) Thanks in advance!


Solution

  • Keep only the ICalculator interface and convert the more specific interfaces to classes. I can't think of a good reason why you would create a class just to store a variable, so I'm going to get rid of the RateSetting entirely.

    Public Interface ICalculator
        Property Rate As Double
        Property PaymentTerm As Double
        Function Calculate() As CommissionValues
        ReadOnly Property CalculationRule As CalculationRuleEnum
    End Interface
    
    Public Class FlexibleRateCalculator : Implements ICalculator
    
        Public Sub New(rate As Double)
            Me.Rate = rate
        End Sub
    
        '
        ' ICalculator implementation goes here
        '
    
    End Class
    
    Public Class FixedRateCalculator : Implements ICalculator
    
        Public Sub New(rate As Double)
            Me.Rate = rate
        End Sub
    
        '
        ' ICalculator implementation goes here
        '
    
    End Class
    
    Public Enum RateType
        Flexible = 1
        Fixed = 2
    End Enum
    
    Public Class CalculatorFactory
    
        Public Shared Function GetCalculator(rate As Double, type As RateType) As ICalculator
            Select Case type
                Case RateType.Flexible
                    Return New FlexibleRateCalculator(rate)
                Case RateType.Fixed
                    Return New FixedRateCalculator(rate)
                Case Else
                    Throw New ArgumentException
            End Select
        End Function
    
    End Class
    

    You create object instances by passing a rate and a rate type to the GetCalculator method. I don't know what you mean by CalculationRule, but if it's important to the end user then you should add it as an additional parameter.

    You can easily add more calculator types that implement ICalculator, as long as you don't forget to update the select statement in the factory method.

    EDIT: of course you can also set additional properties before returning an object instance. The point of this pattern however is to make sure that the end-user does not need to know about how Calculate() is implemented. If you want to make more specific factory methods for every calculator, it kind of defeats the purpose.