Starbucks-fueled Developer

Monday, October 03, 2005

UPDATE: DECORATing Web Controls

OK, so if it wasn't apparent in the original post, I'll confess now: it wasn't TDD'd...While I don't have the time, at least right now, to complete it with unit tests and all, I'll at least revisit it and show how it can be improved upon.

One immediate flaw with the existing code is that the PersonBuilder class relies on an implementation of IControlLocator. While this might not appear to be that big of an issue since, after all, it is a web application, the design can be improved upon by extracting the interaction between the PersonBuilder and DataGridItemControlLocator classes:


Public Interface IControlLocator
Function FindControl(controlId as String) as Control
End Interface

Public Interface IPersonDataRetriever
Function GetName() As String
Function GetAge() As Integer
End Interface

Public Class DataGridPersonDataRetriever
Implements IPersonDataRetriever

Private _dgiControlLocator as IControlLocator

Public Sub New(locator as IControlLocator)
If IsNothing(locator) Then Throw New ArgumentNullException("locator")
_dgiControlLocator = locator
End Sub

Public Function GetName() As String Implements IPersonDataRetriever.GetName()
return FindTextBox("NameField").Text
End Function

Public Function GetAge() As Integer Implements IPersonDataRetriever.GetAge()
Return Integer.Parse(FindTextBox("AgeField")).Text
End Function

Private Function FindTextBox(controlId as String) As TextBox
Retrun DirectCast(_dgiControlLocator.FindControl(controlId), TextBox)
End Function
End Class

Public Class DataGridItemControlLocator
Implements IControlLocator

private dgItem as DataGridItem
private controlPrefix as String = ""

Public Sub New(dgi as DataGridItem)
If IsNothing(dgi) Then Throw New AgrumentNullException("dgi")
dgItem = dgi
End Sub

Public Sub New(dgi as DataGridItem, prefix as String)
Me.New(dgi)
If IsNothing(prefix) OrElse String.Empty.Equals(prefix.Trim()) Then Throw New ArgumentNullException("prefix")
controlPrefix = prefix.Trim()
End Sub

Public Function FindControl(controlId as String)as Control Implements IControlLocator.FindControl
Return dgItem.FindControl(prefix + controlId)
End Function

End Class

Public Class PersonBuilder

Protected personData as IPersonDataRetriever

Public Sub New(aPersonData as IPersonDataRetriever
If IsNothing(aPersonData) Then Throw New ArgumentNullException("aPersonData")
personData = aPersonData
End Sub

Public Function MakePerson() as Person
dim thePerson as Person = New Person()
thePerson.Name = personData.GetName()
thePerson.Age = personData.GetAge()

Return thePerson
End Function

End Class


Some might not like this as it makes the code for creating the PersonBuilder more complex:


Private Sub PersonDataGrid_ItemCommand(s as object, e as DataGridEventArgs)

Dim thePerson as Person '//or Load the Person object

Select Case e.CommandName.ToLower().Trim()
Case "update"
thePerson = New PersonBuilder(New DataGridPersonDataRetriever(New DataGridItemControlLocator(e.Item, "Edit"))).MakePerson()

case "insert"
thePerson = New PersonBuilder(New DataGridPersonDataRetriever(New DataGridItemControlLocator(e.Item, "New"))).MakePerson()
end select

'//other possible logic removed
End Sub


However, if you ask me, I feel the resulting design separates the concerns from the classes in a much better fashion then the original code. The PersonBuilder class is no longer "coupled" to the IControlLocator interface, which allows the BUILDER to be used in a WinForms environment, should the app need to include rich-client support in the future.

I'm not sure, as I'm no GoF patterns expert, but this seems as close to a Model-View-Controller design as one might be able to achieve when implementing in-place-editing with the various Data-controls that are available as-of Framework 1.1.

I'd love to hear any one's thoughts!

0 Comments:

Post a Comment

<< Home