I am looking for a design pattern that allows me to composite multiple functions/variables into a single object, with some specific requirements:
- The entire object definition should be de-/serializable to XML.
- Based on this initial definition, instances of an object with the specified variables/functions should be created. These instances should be serializable and de-/serializable to XML as well, but it doesn't have to be the same format as the definition.
- The system should be easy to extend in the future, if more functions are added.
- Performance should be at an acceptable level. At runtime, there will be approximately 2000 objects that each have their own collection of these compositable objects, and each collection could hold 10-20 composite instances on average (some of which may be created and deleted frequently while others stay around permanently). These lists will be iterated rather frequently to look for composite objects that provide a specific functionality for the given situation.
I'll illustrate it with an example. This approach defines an interface for each function that I want to provide in the composition and then defines individual classes that implement one or more interfaces into composite objects. This assumes that the serialization will be done automatically with an XMLSerializer
, hence why some of the properties are declared as public despite having a getter.
'This is an abstract base class, so all composite objects can be held in the same list
'(as well as sharing some common properties)
Public MustInherit Class ModifierBase
Public Property Name As String
End Class
'This is a function that should be provided.
Public Interface IMod_MinBound
Function GetMin() As Double
End Interface
'Another function.
Public Interface IMod_MaxBound
Function GetMax() As Double
End Interface
Next up are the implementations of these functions.
'Implements only one function.
Public Class MinBoundImpl
Inherits ModifierBase
Implements IMod_MinBound
Public Property MinValue As Double
Public Sub New()
'Empty ctor for serialization
End Sub
Public Sub New(name As String, min As Double)
Me.Name = name
MinValue = min
End Sub
Public Function GetMin() As Double Implements IMod_MinBound.GetMin
Return MinValue
End Function
End Class
'Implements the other function.
Public Class MaxBoundImpl
Inherits ModifierBase
Implements IMod_MaxBound
Public Property MaxValue As Double
Public Sub New()
'Empty ctor for serialization
End Sub
Public Sub New(name As String, max As Double)
Me.Name = name
MaxValue = max
End Sub
Public Function GetMax() As Double Implements IMod_MaxBound.GetMax
Return MaxValue
End Function
End Class
'Implements both functions.
Public Class ClampBoundImpl
Inherits ModifierBase
Implements IMod_MaxBound, IMod_MinBound
Public Property MaxValue As Double
Public Property MinValue As Double
Public Sub New()
'Empty ctor for serialization
End Sub
Public Sub New(name As String, min As Double, max As Double)
Me.Name = name
MinValue = min
MaxValue = max
End Sub
Public Function GetMax() As Double Implements IMod_MaxBound.GetMax
Return MaxValue
End Function
Public Function GetMin() As Double Implements IMod_MinBound.GetMin
Return MinValue
End Function
End Class
And last but not least some test code that would put it to use. This is just an example. There are other usages in praxis, where more than just one of the modifiers may apply at a time.
Public Sub ModifierTest()
Dim modList As New List(Of ModifierBase)
'Usually the workflow would be, that the original definitions
'come from an XML file and get stored with a unique name in a hash map.
'Then instances would be created as copies of those original definitions.
modList.Add(New MinBoundImpl("Test 1", 10))
modList.Add(New MinBoundImpl("Test 2", 5))
modList.Add(New MaxBoundImpl("Test 3", 80))
modList.Add(New ClampBoundImpl("Test 4", 8, 75))
GetModifiedValue(0, modList) 'Should return 5
GetModifiedValue(100, modList) 'Should return 80
End Sub
'This is just one of many possible applications.
Private Function GetModifiedValue(val As Double, modList As List(Of ModifierBase)) As Double
Dim min = Double.MaxValue
Dim max = Double.MinValue
'Iterate through all possible modifiers and only consider the functionality
'that is relevant to us in the current scenario.
For Each m In modList
If TypeOf m Is IMod_MinBound Then
min = Math.Min(min, DirectCast(m, IMod_MinBound).GetMin())
End If
If TypeOf m Is IMod_MaxBound Then
max = Math.Max(max, DirectCast(m, IMod_MaxBound).GetMax())
End If
Next
Return Clamp(val, min, max)
End Function
The drawback of this approach is that, as the number of interfaces increases, I'd have to provide an exponential amount of implementations if I wanted to cover all the possible permutations of functionality that the user could wish to define in the XML.
Another (perhaps better) approach might be to let ModifierBase
hold a hash map of actual implementations (rather than interfaces), and then retrieve the implementation for a desired key (which might as well just be the class name) instead of checking whether a specific interface is being implemented. Not sure how this compares performance-wise, though, since there may be more object instances this way. Is GC pressure a thing in .NET?
Or is there another design pattern that might be suitable here and which I am completely overlooking?
Aucun commentaire:
Enregistrer un commentaire