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/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..9fc8191 --- /dev/null +++ b/chapters/design_patterns/builder.textile @@ -0,0 +1,91 @@ +--- +layout: recipe +title: Builder Pattern +chapter: Design Patterns +--- + +h2. Problem + +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 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 %} +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 [ ]) + 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 when context).join(" ") + projectNames = ("+#{project}" for project in projects when project).join(" ") + priority = if priorityLevel then "(#{priorityLevel})" else "" + 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' + +workBuilder = new TodoTxtBuilder(date: "10/13/2011", contexts: ["work"]) + +workBuilder.newTodo "Show the new design pattern to Lucy", contexts: ["desk", "xpSession"] + +# => '2011-10-13 Show the new design pattern to Lucy @work @desk @xpSession' + +workBuilder.newTodo "Remind Sean about the failing unit tests", contexts: ["meeting"], projects: ["compilerRefactor"], priority: 'A' + +# => '(A) 2011-10-13 Remind Sean about the failing unit tests @work @meeting +compilerRefactor' + +{% endhighlight %} + +h2. Discussion + +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 + +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 %} + +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 new file mode 100644 index 0000000..89cee57 --- /dev/null +++ b/chapters/design_patterns/decorator.textile @@ -0,0 +1,93 @@ +--- +layout: recipe +title: Decorator Pattern +chapter: Design Patterns +--- + +h2. Problem + +You have a set of data that you need to process in multiple, possibly varying ways. + +h2. Solution + +Use the Decorator pattern in order to structure how you apply the changes. + +{% highlight coffeescript %} +miniMarkdown = (line) -> + 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*\/\/.*$/, '' # Removes one-line, double-slash C-style comments + +TextProcessor = (@processors) -> + reducer: (existing, processor) -> + if processor + processor(existing or '') + else + existing + processLine: (text) -> + @processors.reduce @reducer, text + processString: (text) -> + (@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 %} + +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 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: + +{% 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

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

A header that makes you :)

" +{% endhighlight %} 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/design_patterns/strategy.textile b/chapters/design_patterns/strategy.textile new file mode 100644 index 0000000..0ca8402 --- /dev/null +++ b/chapters/design_patterns/strategy.textile @@ -0,0 +1,75 @@ +--- +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 these methods 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) -> + sort: (list) -> + @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 + +"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 + +* 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/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..b8fa1fc 100644 --- a/wanted-recipes.textile +++ b/wanted-recipes.textile @@ -123,4 +123,30 @@ h2. AJAX h2. Design patterns -* Singleton pattern +* Creational Patterns +** Abstract Factory +** Factory Method +** Prototype +** Singleton + +* Structural Patterns +** Adapter +** Bridge +** Composite +** Facade +** Flyweight +** Proxy + +* Behavioral Patterns +** Chain of Responsibility +** Command +** Interpreter +** Iterator +** Mediator +** Memento +** Observer +** State +** Template Method +** Visitor + +