bottom prev next

Builder

An acoustic guitar manufacturer runs an apprentice shop where a person orchestrating the learning process issues the following directives to the future masters of the craft:

  • add acoustic guitar body (resonance chamber)
  • add acoustic guitar neck
  • add acoustic guitar truss rod
  • add acoustic guitar fretboard
  • add acoustic guitar nut
  • add acoustic guitar bridge
  • add acoustic guitar tuning keys
  • add acoustic guitar strings

and the like.

Later on, after endeavoring to make banjos, the manufacturer faces a choice - hire a new person to run the banjo-making apprentice shop or keep the same person but change his/her and all the participants' behavior in two ways by:

1) removing the name of the concrete string instrument from the overseer's directives when he/she issues them

2) adding the name of "their" instrument to the directive by the students when they hear it

In the latter distribution of responsibilities a directive:

  • add (string instrument's) neck

will result in an acoustic guitar maker adding an acoustic guitar-specific neck, a banjo maker adding a banjo-specific neck, a violin maker adding a violin-specific neck, a zither maker consuming the directive but not acting if a neckless variety of the instrument is produced and so on.

In the above example:

- necks, fretboards, bodies, nuts, bridges, scrolls, strings, etc., are generic parts while their instrument-specific equivalents are concrete parts

- a string instrument is a generic or abstract composite data type called a generic product

- acoustic guitars, banjos, violins, etc., are concrete composite data types called concrete products

- a collection of concrete parts of a concrete product forms its internal layout

- apprentices are concrete builders

- the entire apprentice shop is a builder

We observe that even though all the concrete products possess enough commonality to be classed as string instruments their corresponding parts - and hence the exact steps required to assemble them - differ from one instrument type to another.

However, the reason just one overseer in our apprentice shop can orchestrate the production of multiple types of string instruments is because our arrangement - the Builder pattern, while capturing and exposing the commonality of, encapsulates the construction differences among the parts across these instruments.

Formally, then, Builder may be used to encapsulate the internal layout of a variable of a composite data type.

All the patterns that we have looked at so far create their variables in, essentially, one step.

In contrast, Builder creates a variable of a composite data type in a series of steps via a set of externally-driven addPart()/removePart() functions or their equivalents. Thus Builder encapsulates the internal layout of a variable of a composite data type by formalizing and granulating its assembly process.

Parts of a product may be:

  • fixed in number but of different data types
  • of the same data type but different in number which may be not known until run time
  • of different data types and different in number (which may be not known until run time)

Builder is a versatile arrangement with a wide area of application: it may be used to form TCP/IP packets, for example. Each such packet - a product - is represented by a variable of a composite data type consisting of a fixed number - four - of parts:

  • application (FTP, Telnet, SMTP)
  • transport (TCP, UDP)
  • internet (IP, ICMP, IGMP, IPSec)
  • network access (ARP, RARP, Ethernet, Token Ring)

where each type of packet corresponds to what is known as layer.

The type of a concrete packet is determined by the mix (internal layout) of the above protocols. Before bits and bytes on a local host become electrical signals on a wire they undergo a uniform assembly process - each consequent layer treats the previous layer's data as a body or payload, attaches its own header to it and passes the built up header/body pair as data on to the next layer.

Builder may be used in client-server communications on the client side to package different types of concrete requests into a generic whole before sending it off to a server for further processing.

The printf() family of the standard C library functions is another example of Builder - these functions encapsulate the process of a composite string creation via an implicit set of add() functions impersonated by the set of conversion specifications - %d, %i, %o, %u, etc.

The Builder pattern consequences:

  • Encapsulation of the internal layout of a variable of a composite data type

  • Ability to create variables of different underlying composite data types via a uniform assembly process


Sample Problem

An ASCII text-based document, doc_t ADT, supports "plain" and "html" text formats and consists of a header and a body.

Implement this document's builder.


Generic Solution Description

1) Identify all the parts of a product and model them with an appropriate data type - native C or custom.

2) Implement this product's Builder as Factory driven by the chosen key, see Factory chapter for details.

3) For each part of a product add one set of addPart/removePart() functions or their equivalents to Builder's public interface.

4) Implement each concrete builder to carry out the part and type-specific tasks.


Sample Solution

Step 1

Our product, which we will implement as ADT, consists of two distinct parts - a header and a body - which we will model with null-terminated C strings.

Builders will set the contents of a document via its setContent() interface while applications will print the contents of a document via its print() interface (see ADT chapter for details).

libdoc.h:

#ifndef __DOC_HEADER__ #define __DOC_HEADER__ #include <sys/types.h> typedef struct doc { void ( *setContent )( struct doc*, const char* ); void ( *print )( struct doc* ); } doc_t; extern doc_t* docNew(); extern void docDelete( doc_t** ); extern doc_t* docConstruct( void* ); extern void docDestruct( doc_t* ); extern size_t docSizeOf(); extern void docSetContent( doc_t*, const char* ); extern void docPrint( doc_t* ); #endif

libdoc.c:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include "libdoc.h" typedef struct { doc_t docadt; /* Both - header and body - are combined in one state variable, buf. */ char buf[ 1024 + 1 ]; } docimpl_t; static void docSetContentImpl( doc_t*, const char* ); static void docPrintImpl( doc_t* ); static doc_t docAdt = { docSetContentImpl, docPrintImpl }; extern size_t docSizeOf() { return sizeof( docimpl_t ); } extern doc_t* docNew() { void* docimpl = calloc( 1, docSizeOf() ); doc_t* doc = docConstruct( docimpl ); return doc; } extern doc_t* docConstruct( void* docmem ) { docimpl_t* docimpl = ( docimpl_t* )docmem; docimpl->docadt = docAdt; docimpl->buf[ 0 ] = '\0'; return &docimpl->docadt; } extern void docDestruct( doc_t* doc ) { docimpl_t* docimpl; if ( !doc ) { return; } docimpl = ( docimpl_t* )doc; memset( docimpl, 0, sizeof( docimpl_t ) ); } extern void docDelete( doc_t** doc ) { if ( !doc && !*doc ) { return; } docDestruct( *doc ); free( *doc ); *doc = ( doc_t* )NULL; } extern void docSetContent( doc_t* doc, const char* cntnt ) { doc->setContent( doc, cntnt ); } extern void docPrint( doc_t* doc ) { doc->print( doc ); } static void docSetContentImpl( doc_t* doc, const char* cntnt ) { docimpl_t* docimpl = ( docimpl_t* )doc; memset( docimpl->buf, 0, sizeof( docimpl->buf ) ); if ( cntnt && *cntnt ) { snprintf( docimpl->buf, sizeof( docimpl->buf ), "%s", cntnt ); } } static void docPrintImpl( doc_t* doc ) { docimpl_t* docimpl = ( docimpl_t* )doc; printf( "%s", docimpl->buf ); }


Step 2

Our Builder will be driven by human-readable null-terminated C strings: "plain" will select the plain text document builder while "html" will select the HTML document builder (see Factory chapter for details):

  • libdocbldr.h, Builder's public header

  • libdocbldr.c, Builder proper

  • libdocbldrplain.c, plain text builder

  • libdocbldrhtml.c, HTML builder


Step 3

Add one set of add()/remove() functions to Builder's public interface.

By giving our document builder an ability to attach and detach a document, the same builder variable may be used to assemble multiple like products.

libdocbldr.h:

#include <sys/types.h> #include "libdoc.h" typedef struct docbldr { void ( *addHdr )( struct docbldr*, const char* ); void ( *rmHdr )( struct docbldr* ); void ( *addBody )( struct docbldr*, const char* ); void ( *rmBody )( struct docbldr* ); void ( *attachDoc )( struct docbldr*, doc_t* ); doc_t* ( *detachDoc )( struct docbldr* ); void ( *destruct )( struct docbldr* ); } docbldr_t; extern docbldr_t* docbldrNew( const char* ); extern void docbldrDelete( docbldr_t** ); extern docbldr_t* docbldrConstruct( void*, const char* ); extern void docbldrDestruct( docbldr_t* ); extern size_t docbldrSizeOf( const char* ); extern void docbldrAddHdr( docbldr_t*, const char* ); extern void docbldrRmHdr( docbldr_t* ); extern void docbldrAddBody( docbldr_t*, const char* ); extern void docbldrRmBody( docbldr_t* ); extern void docbldrAttachDoc( docbldr_t*, doc_t* ); extern doc_t* docbldrDetachDoc( docbldr_t* );

By analogy with the naming convention adopted in Factory chapter we have used the MLRTB token to designate the Multi-Library Run Time Builder in the code below.

libdocbldr.c:

#include <stdio.h> #include <stdlib.h> #include <dlfcn.h> #include "libdocbldr.h" extern size_t docbldrSizeOf( const char* docbldrnm ) { size_t rv; size_t ( *docbldrsizeof )(); void* handle = RTLD_DEFAULT; char nm[ 1024 + 1 ] = { 0 }; size_t nmsz = sizeof( nm ); #ifdef DOC_MLRTB snprintf( nm, nmsz, "libdocbldr%s.so", docbldrnm ); handle = dlopen( nm, RTLD_LAZY | RTLD_GLOBAL ); #endif snprintf( nm, nmsz, "docbldrSizeOf%s", docbldrnm ); docbldrsizeof = ( size_t ( * )() )dlsym( handle, nm ); rv = docbldrsizeof(); return rv; } extern docbldr_t* docbldrConstruct( void* mem, const char* docbldrnm ) { docbldr_t* docbldr; docbldr_t* ( *docbldrcnstrct )( void* ); char fnm[ 1024 + 1 ] = { 0 }; size_t fnmsz = sizeof( fnm ); snprintf( fnm, fnmsz, "docbldrConstruct%s", docbldrnm ); docbldrcnstrct = ( docbldr_t* ( * )( void* ) )dlsym( RTLD_DEFAULT, fnm ); docbldr = docbldrcnstrct( mem ); return docbldr; } extern docbldr_t* docbldrNew( const char* docbldrnm ) { docbldr_t* docbldr; size_t n = docbldrSizeOf( docbldrnm ); void* mem = calloc( 1, n ); docbldr = docbldrConstruct( mem, docbldrnm ); return docbldr; } extern void docbldrDestruct( docbldr_t* docbldr ) { docbldr->destruct( docbldr ); } extern void docbldrDelete( docbldr_t** docbldr ) { if ( !docbldr || !*docbldr ) { return; } docbldrDestruct( *docbldr ); free( *docbldr ); *docbldr = ( docbldr_t* )NULL; } extern void docbldrAddHdr( docbldr_t* docbldr, const char* txt ) { docbldr->addHdr( docbldr, txt ); } extern void docbldrRmHdr( docbldr_t* docbldr ) { docbldr->rmHdr( docbldr ); } extern void docbldrAddBody( docbldr_t* docbldr, const char* txt ) { docbldr->addBody( docbldr, txt ); } extern void docbldrRmBody( docbldr_t* docbldr ) { docbldr->rmBody( docbldr ); } extern void docbldrAttachDoc( docbldr_t* docbldr, doc_t* doc ) { docSetContent( doc, NULL ); docbldr->attachDoc( docbldr, doc ); } extern doc_t* docbldrDetachDoc( docbldr_t* docbldr ) { doc_t* doc; doc = docbldr->detachDoc( docbldr ); return doc; }


Step 4

Implement all the concrete builders.

Below is a sample plain text builder implementation.

Observe that the corresponding add() functions perform only format and, more importantly, part-specific tasks:

- docbldrAddHdrPlain() creates a plain text version of a header only

- docbldrAddBodyPlain() creates a plain text version of a body only

While the above functions are granular or atomic docbldrDetachDoc() is where final document assembly takes place. We observe in passing that while the name of this function comes in many guises: getProduct, getResult, getWhole, etc., its purpose is to provide an explicit final construction step.

libdocbldrplain.c:

#include <stdio.h> #include "libdocbldr.h" typedef struct { docbldr_t docbldradt; doc_t* doc; char hdr[ 1024 + 1 ]; char body[ 1024 + 1 ]; } docbldrplain_t; extern size_t docbldrSizeOfplain(); extern docbldr_t* docbldrConstructplain( void* ); static void docbldrAddHdrPlain( docbldr_t*, const char* ); static void docbldrRmHdrPlain( docbldr_t* ); static void docbldrAddBodyPlain( docbldr_t*, const char* ); static void docbldrRmBodyPlain( docbldr_t* ); static void docbldrAttachDocPlain( docbldr_t*, doc_t* ); static doc_t* docbldrDetachDocPlain( docbldr_t* ); static void docbldrDestructPlain( docbldr_t* ); static docbldr_t docbldrAdtPlain = { docbldrAddHdrPlain, docbldrRmHdrPlain, docbldrAddBodyPlain, docbldrRmBodyPlain, docbldrAttachDocPlain, docbldrDetachDocPlain, docbldrDestructPlain }; extern size_t docbldrSizeOfplain() { size_t rv; rv = sizeof( docbldrplain_t ); return rv; } extern docbldr_t* docbldrConstructplain( void* mem ) { docbldr_t* rv; docbldrplain_t* plain = ( docbldrplain_t* )mem; plain->docbldradt = docbldrAdtPlain; plain->doc = ( doc_t* )NULL; rv = &plain->docbldradt; return rv; } static void docbldrDestructPlain( docbldr_t* docbldr ) { docbldrplain_t* plain = ( docbldrplain_t* )docbldr; plain->doc = ( doc_t* )NULL; plain->hdr[ 0 ] = '\0'; plain->body[ 0 ] = '\0'; } static void docbldrAddHdrPlain( docbldr_t* docbldr, const char* txt ) { docbldrplain_t* plain = ( docbldrplain_t* )docbldr; snprintf( plain->hdr, sizeof( plain->hdr ), "[%s]", txt ); } static void docbldrRmHdrPlain( docbldr_t* docbldr ) { docbldrplain_t* plain = ( docbldrplain_t* )docbldr; plain->hdr[ 0 ] = '\0'; } static void docbldrAddBodyPlain( docbldr_t* docbldr, const char* txt ) { docbldrplain_t* plain = ( docbldrplain_t* )docbldr; snprintf( plain->body, sizeof( plain->body ), "[%s]", txt ); } static void docbldrRmBodyPlain( docbldr_t* docbldr ) { docbldrplain_t* plain = ( docbldrplain_t* )docbldr; plain->body[ 0 ] = '\0'; } static void docbldrAttachDocPlain( docbldr_t* docbldr, doc_t* doc ) { docbldrplain_t* plain = ( docbldrplain_t* )docbldr; plain->doc = doc; } static doc_t* docbldrDetachDocPlain( docbldr_t* docbldr ) { doc_t* rv; char cntnt[ 1024 + 1 ] = { 0 }; docbldrplain_t* plain = ( docbldrplain_t* )docbldr; rv = plain->doc; snprintf( cntnt, sizeof( cntnt ), "%s\n%s\n", plain->hdr, plain->body ); docSetContent( rv, cntnt ); plain->doc = ( doc_t* )NULL; return rv; }

Type in the remaining builder implementation by analogy.


Step 5

Build the document ADT:

gcc -g -c -fPIC -I . libdoc.c gcc -g -shared -o libdoc.so libdoc.o


Step 6

Build the document builder.

SLBTB:

gcc -D_GNU_SOURCE -g -c -fPIC -I . libdocbldr.c gcc -g -c -fPIC -I . libdocbldrplain.c gcc -g -c -fPIC -I . libdocbldrhtml.c gcc -g -shared -o libdocbldr.so libdocbldr.o \ libdoclnbldrplain.o \ libdoclnbldrhtml.o -ldl


MLBTB:

gcc -D_GNU_SOURCE -g -c -fPIC -I . libdocbldr.c gcc -g -shared -o libdocbldr.so libdocbldr.o -ldl gcc -g -c -fPIC -I . libdocbldrplain.c gcc -g -shared -o libdocbldrplain.so libdocbldrplain.o gcc -g -c -fPIC -I . libdocbldrhtml.c gcc -g -shared -o libdocbldrhtml.so libdocbldrhtml.o


MLRTB:

Add -DDOC_MLRTB option to MLBTB's libdocbldr.c build line above:

gcc -D_GNU_SOURCE -DDOC_MLRTB -g -c -fPIC -I . libdocbldr.c


Step 7

Write a sample application to exercise the Builder pattern. Our version below should be self-explanatory.

docbldr.c:

#include <stdio.h> #include "libdoc.h" #include "libdocbldr.h" extern int main( int argc, char* argv[] ) { doc_t* doc; docbldr_t* db; if ( argc < 2 ) { return -1; } db = docbldrNew( argv[ 1 ] ); doc = docNew(); docbldrAttachDoc( db, doc ); docbldrAddHdr( db, "Hello" ); docbldrAddBody( db, "world." ); doc = docbldrDetachDoc( db ); docPrint( doc ); docbldrDelete( &db ); docDelete( &doc ); return 0; }


Step 8

Build the sample application.

SLBTB and MLRTB:

gcc -g -c -I . docbldr.c gcc -g -L . -o docbldr docbldr.o -ldoc -ldocbldr


MLBTB:

gcc -g -c -I . docbldr.c gcc -g -L . -o docbldr docbldr.o -ldoc -ldocbldr -ldocbldrplain -ldocbldrhtml


Step 9

Run the sample program with various inputs. Study the output observing how the same assembly process created two different types of products:

./docbldr plain [Hello] [world.] ./docbldr html <html> <head> <title>Hello</title> </head> <body> <p>world.</p> </body> </html>


Exercises

1) Carry out the following modifications and analyze their cost:

- add a new part, a footer perhaps, to a product keeping the number of the existing document formats constant

- add a new document format, PDF perhaps, keeping the number of parts of a product constant

2) A new requirement demands the ability to create products (documents) at varying degrees of completion. For example, we should be able to create plain text or HTML (or PDF) documents with:

  • header only (and no body)
  • body only (and no header)
  • header and footer only
  • footer only

and so on. The mix of parts that should be present in such a document is arbitrary and is not known until run time.

Implement this requirement.


Files

libdoc.h libdoc.c

libdocbldr.h libdocbldr.c libdocbldrplain.c libdocbldrhtml.c

mklib.sh

docbldr.c mkapp.sh

\(\blacksquare\)

top prev next