I'm currently working on a windows form application. We've tried to follow good OO principles in designing our business domain classes; encapsulating the business rules, only allowing valid instances of a domain class to be created.
We also really like windows form databinding. It's so much easier than writing lots of code to shunt your object's properties back and forth to the form's controls.
So, what's the problem. Well, say we have a person class with a name property. The business rules say that the name property can't be an empty string, but how do we create a new person if we can't first bind a blank name to a text box?
I've got a nice solution with the use of the 'Builder' pattern. Each domain class has a nested Builder class that is bound to the form instead of an instance of the class itself. The builder allows empty values for required properties so that initially the form has blank fields to be filled in. When the user clicks 'OK', the builder's CreatePerson method is called that returns an instance of the domain class.
Because the builder is a nested type it has access to private shared methods that can contain business logic in the domain class, this enforces encapsulation. Validation of the entered fields can be done on the CreateInstance method.
Here's the Person class with the nested Builder. Note that the DateOfBirth and Age properties are related via a business rule and that the business rule is also used in the Builder:
Public Class Person
Private m_name As String
Private m_age As Integer
Private m_dateOfBirth As DateTime
Public Sub New(ByVal name As String, ByVal dateOfBirth As DateTime)
Me.Name = name
Me.DateOfBirth = dateOfBirth
End Sub
Public Property Name() As String
Get
Return m_name
End Get
Set(ByVal Value As String)
If Value = String.Empty Then
Throw New ValidationException("Name cannot be an empty string")
End If
m_name = Value
End Set
End Property
Public Property DateOfBirth() As DateTime
Get
Return m_dateOfBirth
End Get
Set(ByVal Value As DateTime)
m_dateOfBirth = Value
m_age = CalculateAgeFromDob(m_dateOfBirth)
End Set
End Property
Private Shared Function CalculateAgeFromDob(ByVal dateOfBirth As DateTime) As Integer
Return ((DateTime.Now().Subtract(dateOfBirth).TotalDays) / 360) - 1
End Function
Private Shared Function CalculateDobFromAge(ByVal age As Integer, ByVal dateOfBirth As DateTime) As DateTime
Return New DateTime(DateTime.Now.Year - age, dateOfBirth.Month, dateOfBirth.Day)
End Function
Public Property Age() As Integer
Get
Return m_age
End Get
Set(ByVal Value As Integer)
m_age = Value
m_dateOfBirth = CalculateDobFromAge(m_age, m_dateOfBirth)
End Set
End Property
Public Overrides Function ToString() As String
Return m_name & " " & m_age.ToString() & " " & m_dateOfBirth.ToShortDateString()
End Function
Public Class Builder
Private m_name As String
Private m_age As Integer
Private m_dateOfBirth As DateTime
Public Sub New()
'
' initialise to starting values
'
m_name = ""
m_age = 0
m_dateOfBirth = DateTime.Now
End Sub
Public Function GetPerson() As Person
Return New Person(m_name, m_dateOfBirth)
End Function
Public Property Name() As String
Get
Return m_name
End Get
Set(ByVal Value As String)
m_name = Value
End Set
End Property
Public Property DateOfBirth() As DateTime
Get
Return m_dateOfBirth
End Get
Set(ByVal Value As DateTime)
m_dateOfBirth = Value
m_age = CalculateAgeFromDob(m_dateOfBirth)
End Set
End Property
Public Property Age() As Integer
Get
Return m_age
End Get
Set(ByVal Value As Integer)
m_age = Value
m_dateOfBirth = CalculateDobFromAge(m_age, m_dateOfBirth)
End Set
End Property
End Class
End Class
And here's the interesting bits of the form...
Public Class MainForm
Inherits System.Windows.Forms.Form
Private m_person As Person
Private m_personBuilder As Person.Builder
Private Sub BindPerson(ByVal person As Person)
m_person = person
Me.m_nameTextBox.DataBindings.Add("Text", m_person, "Name")
Me.m_dateOfBirthTextBox.DataBindings.Add("Text", m_person, "DateOfBirth")
Me.m_ageTextBox.DataBindings.Add("Text", m_person, "Age")
End Sub
Private Sub BindPersonBuilder(ByVal personBuilder As Person.Builder)
m_personBuilder = personBuilder
Me.m_nameTextBox.DataBindings.Add("Text", m_personBuilder, "Name")
Me.m_dateOfBirthTextBox.DataBindings.Add("Text", m_personBuilder, "DateOfBirth")
Me.m_ageTextBox.DataBindings.Add("Text", m_personBuilder, "Age")
End Sub
#Region " Windows Form Designer generated code "
Public Sub New(ByVal personBuilder As Person.Builder)
MyBase.New()
InitializeComponent()
BindPersonBuilder(personBuilder)
End Sub
Public Sub New(ByVal person As Person)
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
BindPerson(person)
End Sub
....
#End Region
Private Sub m_showPersonButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles m_showPersonButton.Click
Try
m_person = m_personBuilder.GetPerson()
MessageBox.Show(m_person.ToString())
Catch ex As ValidationException
MessageBox.Show(ex.Message)
End Try
End Sub
End Class
No comments:
Post a Comment