Friday, November 14, 2003

Builder Pattern


This pattern could be described as a way of building complex objects which share common elements.


Let's say we have documents that we want to provide to our clients in whatever format they require. Some clients want Word format, some want PDF, some want HTML and some want XML. We also know that in the future more formats will be required. Our documents can be broken down into elements such as document header, page header, margin width, pre-data descriptive text, data, post-data descriptive text, etc.

The builder pattern is perfect to handle this. First make sure you are familiar with the abstract factory pattern I described yesterday. Builder has many similarities to abstract factory. The main difference being that the "factories" or "toolkits" created by the abstract factory are now called "builders" and instead of providing objects for a particular domain they provide methods to build the various parts of a single complex object.

We start with an AbstractBuilder class which contains a static (shared in VB) factory method to create the builder objects that will build the documents in the various formats. It also contains abstract methods to build the various parts of the formatted document and finally a property for returning the finished document in the required format. The different document classes must use a common interface so the client code can handle them without knowing what class they are, so the property returns a type of that interface.

Public MustInherit Class AbstractDocumentBuilder
Public Shared Function GetBuilder(format As String) _
As AbstractDocumentBuilder
Select Case format.ToUpper()
Case "WORD"
Return New WordBuilder
Case "PDF"
Return New PDFBuilder
Case "XML"
Return New XMLBuilder
Case Else
Return Nothing
End Select
End Function
Public MustOverride Sub BuildDocHeader(docHeaderText As String)
Public MustOverride Sub BuildPageHeader(pageHeaderText As String)
Public MustOverride Sub BuildPreDataText(preDataText As String)
'more methods for other parts of the document ...
Public MustOverride ReadOnly Property Document() As IDocument
End Class

Note: You could give these part builder methods "do nothing" implementations so there would be no need to implement them for formats that don't have that part.

Next are the concrete builder classes for the formats:

Public Class WordBuilder : Inherits AbstractDocumentBuilder
Private _document As New WordDocument
Public Overrides ReadOnly Property Document() As IDocument
Return _document
End Get
End Property
Public Overrides Sub BuildDocHeader(docHeaderText As String)
'code here for Word format
End Sub
Public Overrides Sub BuildPageHeader(pageHeaderText As String)
'code here for Word format
End Sub
Public Overrides Sub BuildPreDataText(preDataText As String)
'code here for Word format
End Sub
End Class

In my example the IDocument interface contains a method to write the document to a file. You could give it whatever methods are appropriate for your implementation. For example you could give it a "Send()" method so it emails itself to the person requesting it.

Public Interface IDocument
Sub Write(fout As FileStream)
End Interface

Next is the class representing the formatted document:

Public Class WordDocument : Implements IDocument
Public Sub Write(ByVal fout As FileStream) Implements IDocument.Write
'code to write the document to the stream
End Sub
'other needed methods & properties
End Class

Finally the client would contain code something like this (note: "doc" represents the original unformatted document):

Public Sub WriteDocument(format As String, fout As FileStream)
Dim finalDocument As IDocument
Dim builder As AbstractDocumentBuilder
builder = AbstractDocumentBuilder.GetBuilder(format)
'Build other parts of the document ...
finalDocument = builder.Document
End Sub

To add a new format you create the builder class for it and add a 'Case "NEWDOC"' to the Select statement in the abstract builder class.

Data & Object Factory: Builder Pattern
Wikipedia - Builder Pattern
DotNetExtreme - Builder Pattern

No comments: