From 11bb583f210f113876b8a487c138c8492759b4e7 Mon Sep 17 00:00:00 2001 From: James Holder Date: Fri, 3 Jun 2011 22:52:12 -0400 Subject: [PATCH 01/12] Add the complete list of GoF design patterns to the corresponding section of Wanted Recipes (excluding the recipe added below). Add Design Patterns/Builder Pattern recipe. Add name to Authors file. --- authors.textile | 1 + chapters/design_patterns/builder.textile | 54 ++++++++++++++++++++++++ chapters/design_patterns/index.textile | 17 ++++++++ chapters/index.textile | 1 + wanted-recipes.textile | 30 ++++++++++++- 5 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 chapters/design_patterns/builder.textile create mode 100644 chapters/design_patterns/index.textile diff --git a/authors.textile b/authors.textile index 6a0895c..f6e75aa 100644 --- a/authors.textile +++ b/authors.textile @@ -13,6 +13,7 @@ _The following people are totally rad and awesome because they have contributed * David Moulton _dave@themoultons.net_ * Sebastian Slomski _sebastian@simple-systems.org_ * Aaron Weinberger _aw9994@cs.ship.edu_ +* James C. Holder _cs_cookbook@thirdtruck.org_ * ...You! What are you waiting for? Check out the contributing section and get cracking! diff --git a/chapters/design_patterns/builder.textile b/chapters/design_patterns/builder.textile new file mode 100644 index 0000000..d1536ac --- /dev/null +++ b/chapters/design_patterns/builder.textile @@ -0,0 +1,54 @@ +--- +layout: recipe +title: Builder Pattern +chapter: Design Patterns +--- + +h2. Problem + +You need to prepare a complicated, multi-part object, more than once or with varying configurations. + +h2. Solution + +Create a Builder to encapsulate the production process. +{% highlight coffeescript %} +TodoTxtBuilder = (defaultParameters={ }) -> + date: new Date(defaultParameters.date) or new Date + contexts: defaultParameters.contexts or [ ] + projects: defaultParameters.projects or [ ] + priority: defaultParameters.priority or undefined + + newTodo: (description, parameters={ }) -> + date = (parameters.date and new Date(parameters.date)) or this.date + contexts = this.contexts.concat(parameters.contexts or [ ]) + projects = this.projects.concat(parameters.projects or [ ]) + priorityLevel = parameters.priority or this.priority + + createdAt = [date.getFullYear(), date.getMonth()+1, date.getDate()].join("-") + contextNames = ("@" + context for context in contexts).join(" ") + projectNames = ("+" + project for project in projects).join(" ") + priority = if priorityLevel then "(" + priorityLevel + ")" else "" + + [priority, createdAt, description, contextNames, projectNames].reduce (whole, part) -> + if part then (whole and whole + " ") + part else whole + +builder = new TodoTxtBuilder({date: "10/13/2011"}) + +builder.newTodo "Wash laundry" + +# => 2011-10-13 Wash laundry + +builder = new TodoTxtBuilder({date: "10/13/2011", contexts: ["work"]}) + +builder.newTodo "Show the new design pattern to Sean", {contexts: ["desk", "xpSession"]} + +# => '2011-10-13 Show the new design pattern to Sean @work @desk @xpSession' + +builder.newTodo "Remind Lucy about the failing unit tests", {contexts: ["meeting"], projects: ["compilerRefactor"], priority: 'A'} + +# => '(A) 2011-10-13 Remind Lucy about the failing unit tests @work @meeting +compilerRefactor' +{% endhighlight %} + +h2. Discussion +Based on the Todo.txt format, the TodoTxtBuilder class takes care of all the heavy lifting of text generation and lets the programmer focus on the unique elements of each todo item. A command line tool tool or GUI could plug into this code and retain support for later, more advanced versions of the format with ease. + diff --git a/chapters/design_patterns/index.textile b/chapters/design_patterns/index.textile new file mode 100644 index 0000000..dc44fbd --- /dev/null +++ b/chapters/design_patterns/index.textile @@ -0,0 +1,17 @@ +--- +layout: chapter +title: Design Patterns +chapter: Design Patterns +--- + +{% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} +{% capture indexurl %}{{ url }}/index.html{% endcapture %} + +{% for page in site.pages %} + {% if page.url contains url %} + {% unless page.url == indexurl %} + * {{ page.title }} + {% endunless %} + {% endif %} +{% endfor %} + diff --git a/chapters/index.textile b/chapters/index.textile index d26d135..bd759b5 100644 --- a/chapters/index.textile +++ b/chapters/index.textile @@ -13,6 +13,7 @@ chapters: - jQuery - Regular Expressions - AJAX +- Design Patterns --- diff --git a/wanted-recipes.textile b/wanted-recipes.textile index a0c37de..f370475 100644 --- a/wanted-recipes.textile +++ b/wanted-recipes.textile @@ -123,4 +123,32 @@ h2. AJAX h2. Design patterns -* Singleton pattern +* Creational Patterns +** Abstract Factory +** Factory Method +** Prototype +** Singleton + +* Structural Patterns +** Adapter +** Bridge +** Composite +** Decorator +** Facade +** Flyweight +** Proxy + +* Behavioral Patterns +** Chain of Responsibility +** Command +** Interpreter +** Iterator +** Mediator +** Memento +** Observer +** State +** Strategy +** Template Method +** Visitor + + From 35610919e40688791fe3fd700254dd127b5c5a35 Mon Sep 17 00:00:00 2001 From: James Holder Date: Fri, 3 Jun 2011 23:27:20 -0400 Subject: [PATCH 02/12] Adjust spacing to avoid code format issue. --- chapters/design_patterns/builder.textile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/chapters/design_patterns/builder.textile b/chapters/design_patterns/builder.textile index d1536ac..e83dfcc 100644 --- a/chapters/design_patterns/builder.textile +++ b/chapters/design_patterns/builder.textile @@ -11,24 +11,22 @@ You need to prepare a complicated, multi-part object, more than once or with var h2. Solution Create a Builder to encapsulate the production process. + {% highlight coffeescript %} TodoTxtBuilder = (defaultParameters={ }) -> date: new Date(defaultParameters.date) or new Date contexts: defaultParameters.contexts or [ ] projects: defaultParameters.projects or [ ] priority: defaultParameters.priority or undefined - newTodo: (description, parameters={ }) -> date = (parameters.date and new Date(parameters.date)) or this.date contexts = this.contexts.concat(parameters.contexts or [ ]) projects = this.projects.concat(parameters.projects or [ ]) priorityLevel = parameters.priority or this.priority - createdAt = [date.getFullYear(), date.getMonth()+1, date.getDate()].join("-") contextNames = ("@" + context for context in contexts).join(" ") projectNames = ("+" + project for project in projects).join(" ") priority = if priorityLevel then "(" + priorityLevel + ")" else "" - [priority, createdAt, description, contextNames, projectNames].reduce (whole, part) -> if part then (whole and whole + " ") + part else whole @@ -47,8 +45,9 @@ builder.newTodo "Show the new design pattern to Sean", {contexts: ["desk", "xpSe builder.newTodo "Remind Lucy about the failing unit tests", {contexts: ["meeting"], projects: ["compilerRefactor"], priority: 'A'} # => '(A) 2011-10-13 Remind Lucy about the failing unit tests @work @meeting +compilerRefactor' + {% endhighlight %} h2. Discussion -Based on the Todo.txt format, the TodoTxtBuilder class takes care of all the heavy lifting of text generation and lets the programmer focus on the unique elements of each todo item. A command line tool tool or GUI could plug into this code and retain support for later, more advanced versions of the format with ease. +Based on the Todo.txt format, the TodoTxtBuilder class takes care of all the heavy lifting of text generation and lets the programmer focus on the unique elements of each todo item. A command line tool tool or GUI could plug into this code and retain support for later, more advanced versions of the format with ease. From 119e099c98e21721776e2be613f119f81411a4f5 Mon Sep 17 00:00:00 2001 From: James Holder Date: Sat, 4 Jun 2011 10:07:30 -0400 Subject: [PATCH 03/12] Remove extraneous curly brackets. --- chapters/design_patterns/builder.textile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chapters/design_patterns/builder.textile b/chapters/design_patterns/builder.textile index e83dfcc..1b51d84 100644 --- a/chapters/design_patterns/builder.textile +++ b/chapters/design_patterns/builder.textile @@ -30,19 +30,19 @@ TodoTxtBuilder = (defaultParameters={ }) -> [priority, createdAt, description, contextNames, projectNames].reduce (whole, part) -> if part then (whole and whole + " ") + part else whole -builder = new TodoTxtBuilder({date: "10/13/2011"}) +builder = new TodoTxtBuilder(date: "10/13/2011") builder.newTodo "Wash laundry" # => 2011-10-13 Wash laundry -builder = new TodoTxtBuilder({date: "10/13/2011", contexts: ["work"]}) +builder = new TodoTxtBuilder(date: "10/13/2011", contexts: ["work"]) -builder.newTodo "Show the new design pattern to Sean", {contexts: ["desk", "xpSession"]} +builder.newTodo "Show the new design pattern to Sean", contexts: ["desk", "xpSession"] # => '2011-10-13 Show the new design pattern to Sean @work @desk @xpSession' -builder.newTodo "Remind Lucy about the failing unit tests", {contexts: ["meeting"], projects: ["compilerRefactor"], priority: 'A'} +builder.newTodo "Remind Lucy about the failing unit tests", contexts: ["meeting"], projects: ["compilerRefactor"], priority: 'A' # => '(A) 2011-10-13 Remind Lucy about the failing unit tests @work @meeting +compilerRefactor' From f56ea921f43f1c9e030eda4b6e63053fa25d88be Mon Sep 17 00:00:00 2001 From: James Holder Date: Sat, 4 Jun 2011 10:41:54 -0400 Subject: [PATCH 04/12] Add "Pre-Construction" section to the Discussion to demonstrate more of the pattern's value. Replace the variable re-initialization in the Solution with a new variable to avoid confusion. --- chapters/design_patterns/builder.textile | 37 ++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/chapters/design_patterns/builder.textile b/chapters/design_patterns/builder.textile index 1b51d84..f3d5598 100644 --- a/chapters/design_patterns/builder.textile +++ b/chapters/design_patterns/builder.textile @@ -36,13 +36,13 @@ builder.newTodo "Wash laundry" # => 2011-10-13 Wash laundry -builder = new TodoTxtBuilder(date: "10/13/2011", contexts: ["work"]) +workBuilder = new TodoTxtBuilder(date: "10/13/2011", contexts: ["work"]) -builder.newTodo "Show the new design pattern to Sean", contexts: ["desk", "xpSession"] +workBuilder.newTodo "Show the new design pattern to Sean", contexts: ["desk", "xpSession"] # => '2011-10-13 Show the new design pattern to Sean @work @desk @xpSession' -builder.newTodo "Remind Lucy about the failing unit tests", contexts: ["meeting"], projects: ["compilerRefactor"], priority: 'A' +workBuilder.newTodo "Remind Lucy about the failing unit tests", contexts: ["meeting"], projects: ["compilerRefactor"], priority: 'A' # => '(A) 2011-10-13 Remind Lucy about the failing unit tests @work @meeting +compilerRefactor' @@ -51,3 +51,34 @@ builder.newTodo "Remind Lucy about the failing unit tests", contexts: ["meeting" h2. Discussion Based on the Todo.txt format, the TodoTxtBuilder class takes care of all the heavy lifting of text generation and lets the programmer focus on the unique elements of each todo item. A command line tool tool or GUI could plug into this code and retain support for later, more advanced versions of the format with ease. + +h3. Pre-Construction + +Instead of creating a new instance of the needed object from scratch every time, we shift the burden to a separate object that we can then tweak during the object creation process. + +{% highlight coffeescript %} +builder = new TodoTxtBuilder(date: "10/13/2011") + +builder.newTodo "Order new netbook" + +# => '2011-10-13 Order new netbook' + +builder.projects.push "summerVacation" + +builder.newTodo "Buy suntan lotion" + +# => '2011-10-13 Buy suntan lotion +summerVacation' + +builder.contexts.push "phone" + +builder.newTodo "Order tickets" + +# => '2011-10-13 Order tickets @phone +summerVacation' + +delete builder.contexts[0] + +builder.newTodo "Fill gas tank" + +# => '2011-10-13 Fill gas tank +summerVacation' +{% endhighlight %} + From 518919911efb5d5f1b49910c93d8e32d0ae4cca7 Mon Sep 17 00:00:00 2001 From: James Holder Date: Sat, 4 Jun 2011 22:07:05 -0400 Subject: [PATCH 05/12] Add Decorator Pattern recipe. Add vim swap files to .gitignore. --- .gitignore | 5 +- .../design_patterns/.decorator.textile.swp | Bin 0 -> 12288 bytes chapters/design_patterns/decorator.textile | 81 ++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 chapters/design_patterns/.decorator.textile.swp create mode 100644 chapters/design_patterns/decorator.textile diff --git a/.gitignore b/.gitignore index 0cdcda3..3042ecd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .rvmrc -_site \ No newline at end of file +_site + +*.swp +*.swo diff --git a/chapters/design_patterns/.decorator.textile.swp b/chapters/design_patterns/.decorator.textile.swp new file mode 100644 index 0000000000000000000000000000000000000000..e62d5867edb93f8fdd7d4d48766bb4ae9d491a63 GIT binary patch literal 12288 zcmeHNy>A>v6rTj~O$bRu5Z!Q>aCgDJvrPdy97_>NBqSRGPKZ!!tnuF5?!WW)aMx56O27|>zAb~UmRvDhZwskI#Sd4U0M>SMv1#D!t{GqVZ&EuEtN^E zm5Ea&b;HxsUL0?%0}U?_iIJ&pw4_#kw3Y~iZbW@AP6Z_DMs6Dw%DeC9RyYP60|f(_ zX-~hnk3By(J5w$_!w(%i=csWEI0hU8jseGjW56-s7;p?Y2L5LZm}C$88$Ry^UI+FR zz>jN=0mp!2z%k$$a11yG90QI4$ADwNG2j?*4BR6Oh!$hl?`LfPgK!A{|4)Ad_;WvF zw}G3$=fHK~8gLc(6gUT*1(t!+z$suaum`yHIAh-cmw_Iz3Y37Sfxq@K_7iXu_zJiI zTm>!x?*l%t2s{fM0DgUpu^)l2fzN<VT@FZ{_@bjb41zZI#0RngpcojGZ8~|=V z!q{)XB_IO~Ab}RJ_hHx#+<1twFM&^hkAM$=6j%e6fJI;yI12m#uD%1l1ug?01Ixf0 zKnWl&|9F70-+}Le>%cYO3h*KD4&Vc;fNPEc$ADwto@AhRl*_0UC_flJh3FZ6bcanC z;%F0wcMtV;4>1y^Haa!>Y`2{xSmwtUcx67AT{tl&T-Z4}9Z1oVDK~*IydyTG=G{2s zJ>9{5w4M0|R8$yC(%6$){4piH4+E~d`+lh!^7h~jqs3K_}s8gjLKW+!>xO5i^PjcPutjFOLFZIejx=gpT zQ>yWHoDL>vkjL#py|&s^JJ&qEB~;rjYJ7Sj-@fuvt-_nUw56hrl9jA9t9GQF##>Q; z=%gJAGGDIwV5dKv%UV85EZ<7=;}k0tMS1Laal0+0_EMGDKw{@5_e7M_)`?rH-Ngw> zF1M97MEuGA)j=MwUz8qPi|m4S*ljX)UYWpl&%*07{j=*YO>yE-7xS%DM1Cl_2t%TV zHn21ePFrJ@FEO4@+1ZYY)RIUyTJU;`Ys5hEa@^_2$ml7K^8&iOotZ3^_=!BD;z(OZ z+Od~unD2#|R+}>HlEDE)aF{-iViVBsdS&OX3A~RNkT~hFp_YH?MQC%@vyi+A|nU6G;+$Tc?MA4 z<*ibwu>N!Y3qvU(k?dO(c7l6(NZhfkOr>Jnr8`zY!@miS({zX2P;sAMHsU>8CYuK` zRfL-Em$5}yUL-pe3+o6SHHDl`LS?F@m8ev!)i1^>%Ac*GA`Wt{o4iT_wqEd}jc!8K zEtRbdWy%ic&y-ShI3!T0BWorpqb2>!XVjr4Lp`jCG%$$ZYJV-l7{1pS%~C_X8!Tcp zP*{RJ`yx}r6$5_;GP*gO)oeV7P&9|@hNY7j9Te%tiaxf|SZUOc9cfI#GBy@?yX!8Q zNNptKc6nVwLX>;74PI815mo~K6ve$B%E(7pF7O#UHKqW`S?=YpuSH7G9jMB?I z`u$>hsD8K%c6WsFf(vu2gUv^D4yI>U3tQ|em}jLhZ8^$2jkl}i=W3v-T0d4h(qN;h zIL6O^zdg=r3N<w)gxc(qQEW3VMa6fl6wmu z1-90rm`eL5Z3!c=3qBj@gCUZ#McRuXpn}=SLZeWF5K}R# + if match = line.match /^(#+)\s*(.*)$/ + headerLevel = match[1].length + headerText = match[2] + "#{headerText}" + else + if line.length > 0 + "

#{line}

" + else + '' + +StripComments = (line) -> + line.replace /\s*\/\/.*$/, '' + +TextProcessor = (processors) -> + processors: processors + reducer: (existing, processor) -> + if processor + processor(existing or '') + else + existing + processLine: (text) -> + this.processors.reduce this.reducer, text + processString: (text) -> + (this.processLine(line) for line in text.split("\n")).join("\n") + +exampleText = ''' + # A level 1 header + A regular line + // a comment + ## A level 2 header + A line // with a comment + ''' + +processor = new TextProcessor [StripComments, MiniMarkdown] + +processor.processString exampleText + +# => "

A level 1 header

\n

A regular line

\n\n

A level 2 header

\n

A line

" +{% endhighlight %} + +h2. Discussion + +With the TextProcessor object ready to wrangle all of the text processors together, the miniMarkdown, stripComments, and any future functions can focus exclusively on handling nothing but a single line of text. Future developers only have to write functions that return a string and add it to the array of processors in order to partake in the process. + +We can even modify the existing Decorator object on the fly: + +{% highlight coffeescript %} +smilies = + ':)' : "smile" + ':D' : "huge_grin" + ':(' : "frown" + ';)' : "wink" + +smilieExpander = (line) -> + if line + (line = line.replace symbol, "{#{text}}") for symbol, text of smilies + line + +processor.processors.unshift smilieExpander + +processor.processString "# A header that makes you :) // you may even laugh" + +# => '

A header that makes you {smile}

' +{% endhighlight %} From f5732330f86c2e59778da3e10ba0c94bbc03872d Mon Sep 17 00:00:00 2001 From: James Holder Date: Sun, 5 Jun 2011 11:09:14 -0400 Subject: [PATCH 06/12] Tweak the Decorator Discussion to better explain the utility value of the pattern. Delete an unintentionally-tracked temporary file. Rename the Decorator functions to use more function-like capitalization. --- .../design_patterns/.decorator.textile.swp | Bin 12288 -> 0 bytes chapters/design_patterns/decorator.textile | 18 +++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) delete mode 100644 chapters/design_patterns/.decorator.textile.swp diff --git a/chapters/design_patterns/.decorator.textile.swp b/chapters/design_patterns/.decorator.textile.swp deleted file mode 100644 index e62d5867edb93f8fdd7d4d48766bb4ae9d491a63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeHNy>A>v6rTj~O$bRu5Z!Q>aCgDJvrPdy97_>NBqSRGPKZ!!tnuF5?!WW)aMx56O27|>zAb~UmRvDhZwskI#Sd4U0M>SMv1#D!t{GqVZ&EuEtN^E zm5Ea&b;HxsUL0?%0}U?_iIJ&pw4_#kw3Y~iZbW@AP6Z_DMs6Dw%DeC9RyYP60|f(_ zX-~hnk3By(J5w$_!w(%i=csWEI0hU8jseGjW56-s7;p?Y2L5LZm}C$88$Ry^UI+FR zz>jN=0mp!2z%k$$a11yG90QI4$ADwNG2j?*4BR6Oh!$hl?`LfPgK!A{|4)Ad_;WvF zw}G3$=fHK~8gLc(6gUT*1(t!+z$suaum`yHIAh-cmw_Iz3Y37Sfxq@K_7iXu_zJiI zTm>!x?*l%t2s{fM0DgUpu^)l2fzN<VT@FZ{_@bjb41zZI#0RngpcojGZ8~|=V z!q{)XB_IO~Ab}RJ_hHx#+<1twFM&^hkAM$=6j%e6fJI;yI12m#uD%1l1ug?01Ixf0 zKnWl&|9F70-+}Le>%cYO3h*KD4&Vc;fNPEc$ADwto@AhRl*_0UC_flJh3FZ6bcanC z;%F0wcMtV;4>1y^Haa!>Y`2{xSmwtUcx67AT{tl&T-Z4}9Z1oVDK~*IydyTG=G{2s zJ>9{5w4M0|R8$yC(%6$){4piH4+E~d`+lh!^7h~jqs3K_}s8gjLKW+!>xO5i^PjcPutjFOLFZIejx=gpT zQ>yWHoDL>vkjL#py|&s^JJ&qEB~;rjYJ7Sj-@fuvt-_nUw56hrl9jA9t9GQF##>Q; z=%gJAGGDIwV5dKv%UV85EZ<7=;}k0tMS1Laal0+0_EMGDKw{@5_e7M_)`?rH-Ngw> zF1M97MEuGA)j=MwUz8qPi|m4S*ljX)UYWpl&%*07{j=*YO>yE-7xS%DM1Cl_2t%TV zHn21ePFrJ@FEO4@+1ZYY)RIUyTJU;`Ys5hEa@^_2$ml7K^8&iOotZ3^_=!BD;z(OZ z+Od~unD2#|R+}>HlEDE)aF{-iViVBsdS&OX3A~RNkT~hFp_YH?MQC%@vyi+A|nU6G;+$Tc?MA4 z<*ibwu>N!Y3qvU(k?dO(c7l6(NZhfkOr>Jnr8`zY!@miS({zX2P;sAMHsU>8CYuK` zRfL-Em$5}yUL-pe3+o6SHHDl`LS?F@m8ev!)i1^>%Ac*GA`Wt{o4iT_wqEd}jc!8K zEtRbdWy%ic&y-ShI3!T0BWorpqb2>!XVjr4Lp`jCG%$$ZYJV-l7{1pS%~C_X8!Tcp zP*{RJ`yx}r6$5_;GP*gO)oeV7P&9|@hNY7j9Te%tiaxf|SZUOc9cfI#GBy@?yX!8Q zNNptKc6nVwLX>;74PI815mo~K6ve$B%E(7pF7O#UHKqW`S?=YpuSH7G9jMB?I z`u$>hsD8K%c6WsFf(vu2gUv^D4yI>U3tQ|em}jLhZ8^$2jkl}i=W3v-T0d4h(qN;h zIL6O^zdg=r3N<w)gxc(qQEW3VMa6fl6wmu z1-90rm`eL5Z3!c=3qBj@gCUZ#McRuXpn}=SLZeWF5K}R# +miniMarkdown = (line) -> if match = line.match /^(#+)\s*(.*)$/ headerLevel = match[1].length headerText = match[2] @@ -25,7 +25,7 @@ MiniMarkdown = (line) -> else '' -StripComments = (line) -> +stripComments = (line) -> line.replace /\s*\/\/.*$/, '' TextProcessor = (processors) -> @@ -48,7 +48,7 @@ exampleText = ''' A line // with a comment ''' -processor = new TextProcessor [StripComments, MiniMarkdown] +processor = new TextProcessor [stripComments, miniMarkdown] processor.processString exampleText @@ -57,9 +57,9 @@ processor.processString exampleText h2. Discussion -With the TextProcessor object ready to wrangle all of the text processors together, the miniMarkdown, stripComments, and any future functions can focus exclusively on handling nothing but a single line of text. Future developers only have to write functions that return a string and add it to the array of processors in order to partake in the process. +The TextProcessor serves the role of Decorator and binds the individual, specialized text processors together. This frees up the miniMarkdown, stripComments, and any future components to focus on handling nothing but a single line of text. Future developers only have to write functions that return a string and add it to the array of processors. -We can even modify the existing Decorator object on the fly: +We can even modify the existing Decorator object on the fly. We could add a processor for transforming text smilies into images, for example, but only when the end user enables the feature: {% highlight coffeescript %} smilies = @@ -70,12 +70,16 @@ smilies = smilieExpander = (line) -> if line - (line = line.replace symbol, "{#{text}}") for symbol, text of smilies + (line = line.replace symbol, "#{text}") for symbol, text of smilies line processor.processors.unshift smilieExpander processor.processString "# A header that makes you :) // you may even laugh" -# => '

A header that makes you {smile}

' +# => "

A header that makes you smile

" + +processor.processors.shift() + +# => "

A header that makes you :)

" {% endhighlight %} From fcd2a1afb3ef98002baaa8e7997f915d678d192d Mon Sep 17 00:00:00 2001 From: James Holder Date: Sun, 5 Jun 2011 12:55:45 -0400 Subject: [PATCH 07/12] Add initial Strategy recipe to the Design Patterns chapter. --- chapters/design_patterns/strategy.textile | 76 +++++++++++++++++++++++ wanted-recipes.textile | 1 - 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 chapters/design_patterns/strategy.textile diff --git a/chapters/design_patterns/strategy.textile b/chapters/design_patterns/strategy.textile new file mode 100644 index 0000000..4ee9673 --- /dev/null +++ b/chapters/design_patterns/strategy.textile @@ -0,0 +1,76 @@ +--- +layout: recipe +title: Strategy Pattern +chapter: Design Patterns +--- + +h2. Problem + +You have more than one way to solve a problem but you need to choose (or even switch) between them at run time. + +h2. Solution + +Encapsulate your algorithms inside of Strategy objects. + +Given an unsorted list, for example, we can change the sorting algorithm under different circumstances. + +{% highlight coffeescript %} +StringSorter = (algorithm) -> + algorithm: algorithm + sort: (list) -> + this.algorithm list + +bubbleSort = (list) -> + anySwaps = false + swapPass = -> + for r in [0..list.length-1] + if list[r] > list[r+1] + anySwaps = true + [list[r], list[r+1]] = [list[r+1], list[r]] + + swapPass() + while anySwaps + anySwaps = false + swapPass() + list + +reverseBubbleSort = (list) -> + anySwaps = false + swapPass = -> + for r in [list.length-1..1] + if list[r] < list[r-1] + anySwaps = true + [list[r], list[r-1]] = [list[r-1], list[r]] + + swapPass() + while anySwaps + anySwaps = false + swapPass() + list + +sorter = new StringSorter bubbleSort + +unsortedList = ['e', 'b', 'd', 'c', 'x', 'a'] + +sorter.sort unsortedList + +# => ['a', 'b', 'c', 'd', 'e', 'x'] + +unsortedList.push 'w' + +# => ['a', 'b', 'c', 'd', 'e', 'x', 'w'] + +sorter.algorithm = reverseBubbleSort + +sorter.sort unsortedList + +# => ['a', 'b', 'c', 'd', 'e', 'w', 'x'] +{% endhighlight %} + +h2. Discussion + +Like a skilled general, we must prepare to change our plans under constantly-changing circumstances. At the end of the example, we know that only the newest item in the array is out of order. We can then speed the sort up by switching to an algorithm that starts from the end of the array. + +h3. Exercises + +* Expand StringSorter into an AlwaysSortedArray class that implements all of the functionality of a regular array but which automatically sorts new items based on the method of insertion (e.g. push vs. shift). diff --git a/wanted-recipes.textile b/wanted-recipes.textile index f370475..8b2d2b6 100644 --- a/wanted-recipes.textile +++ b/wanted-recipes.textile @@ -147,7 +147,6 @@ h2. Design patterns ** Memento ** Observer ** State -** Strategy ** Template Method ** Visitor From 50829feae5b06f6e155d1b14502e7e9dfbe122fc Mon Sep 17 00:00:00 2001 From: James Holder Date: Mon, 6 Jun 2011 19:35:33 -0400 Subject: [PATCH 08/12] Editorial changes and clarifications. --- chapters/design_patterns/builder.textile | 26 +++++++++++++--------- chapters/design_patterns/decorator.textile | 19 +++++++++++----- chapters/design_patterns/strategy.textile | 4 ++-- wanted-recipes.textile | 1 - 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/chapters/design_patterns/builder.textile b/chapters/design_patterns/builder.textile index f3d5598..1e71e2c 100644 --- a/chapters/design_patterns/builder.textile +++ b/chapters/design_patterns/builder.textile @@ -6,11 +6,13 @@ chapter: Design Patterns h2. Problem -You need to prepare a complicated, multi-part object, more than once or with varying configurations. +You need to prepare a complicated, multi-part object, but you expect to do it more than once or with varying configurations. h2. Solution -Create a Builder to encapsulate the production process. +Create a Builder to encapsulate the object production process. + +The Todo.txt format provides an advanced but still plain-text method for maintaining lists of to-do items. Typing out each item by hand would provide exhausting and error-prone, however, so a TodoTxtBuilder class could save us the trouble: {% highlight coffeescript %} TodoTxtBuilder = (defaultParameters={ }) -> @@ -24,9 +26,9 @@ TodoTxtBuilder = (defaultParameters={ }) -> projects = this.projects.concat(parameters.projects or [ ]) priorityLevel = parameters.priority or this.priority createdAt = [date.getFullYear(), date.getMonth()+1, date.getDate()].join("-") - contextNames = ("@" + context for context in contexts).join(" ") - projectNames = ("+" + project for project in projects).join(" ") - priority = if priorityLevel then "(" + priorityLevel + ")" else "" + contextNames = ("@#{context}" for context in contexts).join(" ") + projectNames = ("+#{project}" for project in projects).join(" ") + priority = if priorityLevel then "(#{priorityLevel})" else "" [priority, createdAt, description, contextNames, projectNames].reduce (whole, part) -> if part then (whole and whole + " ") + part else whole @@ -38,19 +40,19 @@ builder.newTodo "Wash laundry" workBuilder = new TodoTxtBuilder(date: "10/13/2011", contexts: ["work"]) -workBuilder.newTodo "Show the new design pattern to Sean", contexts: ["desk", "xpSession"] +workBuilder.newTodo "Show the new design pattern to Lucy", contexts: ["desk", "xpSession"] -# => '2011-10-13 Show the new design pattern to Sean @work @desk @xpSession' +# => '2011-10-13 Show the new design pattern to Lucy @work @desk @xpSession' -workBuilder.newTodo "Remind Lucy about the failing unit tests", contexts: ["meeting"], projects: ["compilerRefactor"], priority: 'A' +workBuilder.newTodo "Remind Sean about the failing unit tests", contexts: ["meeting"], projects: ["compilerRefactor"], priority: 'A' -# => '(A) 2011-10-13 Remind Lucy about the failing unit tests @work @meeting +compilerRefactor' +# => '(A) 2011-10-13 Remind Sean about the failing unit tests @work @meeting +compilerRefactor' {% endhighlight %} h2. Discussion -Based on the Todo.txt format, the TodoTxtBuilder class takes care of all the heavy lifting of text generation and lets the programmer focus on the unique elements of each todo item. A command line tool tool or GUI could plug into this code and retain support for later, more advanced versions of the format with ease. +The TodoTxtBuilder class takes care of all the heavy lifting of text generation and lets the programmer focus on the unique elements of each to-do item. Additionally, a command line tool or GUI could plug into this code and still retain support for later, more advanced versions of the format with ease. h3. Pre-Construction @@ -82,3 +84,7 @@ builder.newTodo "Fill gas tank" # => '2011-10-13 Fill gas tank +summerVacation' {% endhighlight %} +h3. Exercises +* Expand the project- and context-tag generation code to filter out duplicate entries. +** Some Todo.txt users like to insert project and context tags inside the description of their to-do items. Add code to identify these tags and filter them out of the end tags. + diff --git a/chapters/design_patterns/decorator.textile b/chapters/design_patterns/decorator.textile index e80e03f..0359d6d 100644 --- a/chapters/design_patterns/decorator.textile +++ b/chapters/design_patterns/decorator.textile @@ -8,10 +8,9 @@ h2. Problem You have a set of data that you need to process in multiple, possibly varying ways. - h2. Solution -Use the Decorator pattern to structure the application of changes. +Use the Decorator pattern in order to structure how you apply the changes. {% highlight coffeescript %} miniMarkdown = (line) -> @@ -26,7 +25,7 @@ miniMarkdown = (line) -> '' stripComments = (line) -> - line.replace /\s*\/\/.*$/, '' + line.replace /\s*\/\/.*$/, '' # Removes one-line, double-slash C-style comments TextProcessor = (processors) -> processors: processors @@ -55,11 +54,21 @@ processor.processString exampleText # => "

A level 1 header

\n

A regular line

\n\n

A level 2 header

\n

A line

" {% endhighlight %} +h3. Results + +{% highlight html %} +

A level 1 header

+

A regular line

+ +

A level 1 header

+

A line

+{% endhighlight %} + h2. Discussion -The TextProcessor serves the role of Decorator and binds the individual, specialized text processors together. This frees up the miniMarkdown, stripComments, and any future components to focus on handling nothing but a single line of text. Future developers only have to write functions that return a string and add it to the array of processors. +The TextProcessor serves the role of Decorator by binding the individual, specialized text processors together. This frees up the miniMarkdown and stripComments components to focus on handling nothing but a single line of text. Future developers only have to write functions that return a string and add it to the array of processors. -We can even modify the existing Decorator object on the fly. We could add a processor for transforming text smilies into images, for example, but only when the end user enables the feature: +We can even modify the existing Decorator object on the fly: {% highlight coffeescript %} smilies = diff --git a/chapters/design_patterns/strategy.textile b/chapters/design_patterns/strategy.textile index 4ee9673..88cd958 100644 --- a/chapters/design_patterns/strategy.textile +++ b/chapters/design_patterns/strategy.textile @@ -6,7 +6,7 @@ chapter: Design Patterns h2. Problem -You have more than one way to solve a problem but you need to choose (or even switch) between them at run time. +You have more than one way to solve a problem but you need to choose (or even switch) between these methods at run time. h2. Solution @@ -69,7 +69,7 @@ sorter.sort unsortedList h2. Discussion -Like a skilled general, we must prepare to change our plans under constantly-changing circumstances. At the end of the example, we know that only the newest item in the array is out of order. We can then speed the sort up by switching to an algorithm that starts from the end of the array. +"No plan survives first contact with the enemy", nor users, but we can use the knowledge gained from changing circumstances to adapt. Near the end of the example, for instance, the newest item in the array now lies out of order. Knowing that detail, we can then speed the sort up by switching to an algorithm optimized for that exact scenario with nothing but a simple reassignment. h3. Exercises diff --git a/wanted-recipes.textile b/wanted-recipes.textile index 8b2d2b6..b8fa1fc 100644 --- a/wanted-recipes.textile +++ b/wanted-recipes.textile @@ -133,7 +133,6 @@ h2. Design patterns ** Adapter ** Bridge ** Composite -** Decorator ** Facade ** Flyweight ** Proxy From 266cae4605af0a17c806dcc3ba7152e688ddee55 Mon Sep 17 00:00:00 2001 From: James Holder Date: Mon, 6 Jun 2011 20:10:51 -0400 Subject: [PATCH 09/12] Replace instances of "this." with "@" shorthand for greater legibility. --- chapters/design_patterns/builder.textile | 8 ++++---- chapters/design_patterns/decorator.textile | 4 ++-- chapters/design_patterns/strategy.textile | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/chapters/design_patterns/builder.textile b/chapters/design_patterns/builder.textile index 1e71e2c..e3bf7e2 100644 --- a/chapters/design_patterns/builder.textile +++ b/chapters/design_patterns/builder.textile @@ -21,10 +21,10 @@ TodoTxtBuilder = (defaultParameters={ }) -> projects: defaultParameters.projects or [ ] priority: defaultParameters.priority or undefined newTodo: (description, parameters={ }) -> - date = (parameters.date and new Date(parameters.date)) or this.date - contexts = this.contexts.concat(parameters.contexts or [ ]) - projects = this.projects.concat(parameters.projects or [ ]) - priorityLevel = parameters.priority or this.priority + date = (parameters.date and new Date(parameters.date)) or @date + contexts = @contexts.concat(parameters.contexts or [ ]) + projects = @projects.concat(parameters.projects or [ ]) + priorityLevel = parameters.priority or @priority createdAt = [date.getFullYear(), date.getMonth()+1, date.getDate()].join("-") contextNames = ("@#{context}" for context in contexts).join(" ") projectNames = ("+#{project}" for project in projects).join(" ") diff --git a/chapters/design_patterns/decorator.textile b/chapters/design_patterns/decorator.textile index 0359d6d..d40fbdf 100644 --- a/chapters/design_patterns/decorator.textile +++ b/chapters/design_patterns/decorator.textile @@ -35,9 +35,9 @@ TextProcessor = (processors) -> else existing processLine: (text) -> - this.processors.reduce this.reducer, text + @processors.reduce @reducer, text processString: (text) -> - (this.processLine(line) for line in text.split("\n")).join("\n") + (@processLine(line) for line in text.split("\n")).join("\n") exampleText = ''' # A level 1 header diff --git a/chapters/design_patterns/strategy.textile b/chapters/design_patterns/strategy.textile index 88cd958..879a9da 100644 --- a/chapters/design_patterns/strategy.textile +++ b/chapters/design_patterns/strategy.textile @@ -18,7 +18,7 @@ Given an unsorted list, for example, we can change the sorting algorithm under d StringSorter = (algorithm) -> algorithm: algorithm sort: (list) -> - this.algorithm list + @algorithm list bubbleSort = (list) -> anySwaps = false From f74f6b4a618e580485b2839fa16c559c7bbfee3a Mon Sep 17 00:00:00 2001 From: James Holder Date: Mon, 6 Jun 2011 20:23:54 -0400 Subject: [PATCH 10/12] Replace explicity initializer assignments with more concise property arguments. --- chapters/design_patterns/decorator.textile | 3 +-- chapters/design_patterns/strategy.textile | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/chapters/design_patterns/decorator.textile b/chapters/design_patterns/decorator.textile index d40fbdf..89cee57 100644 --- a/chapters/design_patterns/decorator.textile +++ b/chapters/design_patterns/decorator.textile @@ -27,8 +27,7 @@ miniMarkdown = (line) -> stripComments = (line) -> line.replace /\s*\/\/.*$/, '' # Removes one-line, double-slash C-style comments -TextProcessor = (processors) -> - processors: processors +TextProcessor = (@processors) -> reducer: (existing, processor) -> if processor processor(existing or '') diff --git a/chapters/design_patterns/strategy.textile b/chapters/design_patterns/strategy.textile index 879a9da..0ca8402 100644 --- a/chapters/design_patterns/strategy.textile +++ b/chapters/design_patterns/strategy.textile @@ -15,8 +15,7 @@ Encapsulate your algorithms inside of Strategy objects. Given an unsorted list, for example, we can change the sorting algorithm under different circumstances. {% highlight coffeescript %} -StringSorter = (algorithm) -> - algorithm: algorithm +StringSorter = (@algorithm) -> sort: (list) -> @algorithm list From a34e2ae0824c00d226b8ca58e1dd0c7f5ce238b8 Mon Sep 17 00:00:00 2001 From: James Holder Date: Mon, 6 Jun 2011 22:52:25 -0400 Subject: [PATCH 11/12] Incorporate the "when" keyword into the string-building loops for more natural-reading code. --- chapters/design_patterns/builder.textile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chapters/design_patterns/builder.textile b/chapters/design_patterns/builder.textile index e3bf7e2..8ac936d 100644 --- a/chapters/design_patterns/builder.textile +++ b/chapters/design_patterns/builder.textile @@ -26,17 +26,17 @@ TodoTxtBuilder = (defaultParameters={ }) -> projects = @projects.concat(parameters.projects or [ ]) priorityLevel = parameters.priority or @priority createdAt = [date.getFullYear(), date.getMonth()+1, date.getDate()].join("-") - contextNames = ("@#{context}" for context in contexts).join(" ") - projectNames = ("+#{project}" for project in projects).join(" ") + contextNames = ("@#{context}" for context in contexts when context).join(" ") + projectNames = ("+#{project}" for project in projects when project).join(" ") priority = if priorityLevel then "(#{priorityLevel})" else "" - [priority, createdAt, description, contextNames, projectNames].reduce (whole, part) -> - if part then (whole and whole + " ") + part else whole + todoParts = [priority, createdAt, description, contextNames, projectNames] + (part for part in todoParts when part.length > 0).join " " builder = new TodoTxtBuilder(date: "10/13/2011") builder.newTodo "Wash laundry" -# => 2011-10-13 Wash laundry +# => '2011-10-13 Wash laundry' workBuilder = new TodoTxtBuilder(date: "10/13/2011", contexts: ["work"]) From ab0394b8acd6a5e69cfc2b5296efc39e3c994740 Mon Sep 17 00:00:00 2001 From: James Holder Date: Tue, 7 Jun 2011 13:21:48 -0400 Subject: [PATCH 12/12] Begin move to using "class" keyword for more explicit code. --- chapters/design_patterns/builder.textile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/chapters/design_patterns/builder.textile b/chapters/design_patterns/builder.textile index 8ac936d..9fc8191 100644 --- a/chapters/design_patterns/builder.textile +++ b/chapters/design_patterns/builder.textile @@ -15,11 +15,12 @@ Create a Builder to encapsulate the object production process. The Todo.txt format provides an advanced but still plain-text method for maintaining lists of to-do items. Typing out each item by hand would provide exhausting and error-prone, however, so a TodoTxtBuilder class could save us the trouble: {% highlight coffeescript %} -TodoTxtBuilder = (defaultParameters={ }) -> - date: new Date(defaultParameters.date) or new Date - contexts: defaultParameters.contexts or [ ] - projects: defaultParameters.projects or [ ] - priority: defaultParameters.priority or undefined +class TodoTxtBuilder + constructor: (defaultParameters={ }) -> + @date = new Date(defaultParameters.date) or new Date + @contexts = defaultParameters.contexts or [ ] + @projects = defaultParameters.projects or [ ] + @priority = defaultParameters.priority or undefined newTodo: (description, parameters={ }) -> date = (parameters.date and new Date(parameters.date)) or @date contexts = @contexts.concat(parameters.contexts or [ ])