Skip to content

Commit 2619e15

Browse files
committed
Merge pull request coffeescript-cookbook#14 from thirdtruck/master
Design Patterns
2 parents 899ddb2 + ab0394b commit 2619e15

File tree

8 files changed

+309
-2
lines changed

8 files changed

+309
-2
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
.rvmrc
2-
_site
2+
_site
3+
4+
*.swp
5+
*.swo

authors.textile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ _The following people are totally rad and awesome because they have contributed
1313
* David Moulton [email protected]_
1414
* Sebastian Slomski [email protected]_
1515
* Aaron Weinberger [email protected]_
16+
* James C. Holder [email protected]_
1617
* ...You! What are you waiting for? Check out the <a href="/contributing">contributing</a> section and get cracking!
1718

1819

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
layout: recipe
3+
title: Builder Pattern
4+
chapter: Design Patterns
5+
---
6+
7+
h2. Problem
8+
9+
You need to prepare a complicated, multi-part object, but you expect to do it more than once or with varying configurations.
10+
11+
h2. Solution
12+
13+
Create a Builder to encapsulate the object production process.
14+
15+
The <a href="http://todotxt.com">Todo.txt</a> 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:
16+
17+
{% highlight coffeescript %}
18+
class TodoTxtBuilder
19+
constructor: (defaultParameters={ }) ->
20+
@date = new Date(defaultParameters.date) or new Date
21+
@contexts = defaultParameters.contexts or [ ]
22+
@projects = defaultParameters.projects or [ ]
23+
@priority = defaultParameters.priority or undefined
24+
newTodo: (description, parameters={ }) ->
25+
date = (parameters.date and new Date(parameters.date)) or @date
26+
contexts = @contexts.concat(parameters.contexts or [ ])
27+
projects = @projects.concat(parameters.projects or [ ])
28+
priorityLevel = parameters.priority or @priority
29+
createdAt = [date.getFullYear(), date.getMonth()+1, date.getDate()].join("-")
30+
contextNames = ("@#{context}" for context in contexts when context).join(" ")
31+
projectNames = ("+#{project}" for project in projects when project).join(" ")
32+
priority = if priorityLevel then "(#{priorityLevel})" else ""
33+
todoParts = [priority, createdAt, description, contextNames, projectNames]
34+
(part for part in todoParts when part.length > 0).join " "
35+
36+
builder = new TodoTxtBuilder(date: "10/13/2011")
37+
38+
builder.newTodo "Wash laundry"
39+
40+
# => '2011-10-13 Wash laundry'
41+
42+
workBuilder = new TodoTxtBuilder(date: "10/13/2011", contexts: ["work"])
43+
44+
workBuilder.newTodo "Show the new design pattern to Lucy", contexts: ["desk", "xpSession"]
45+
46+
# => '2011-10-13 Show the new design pattern to Lucy @work @desk @xpSession'
47+
48+
workBuilder.newTodo "Remind Sean about the failing unit tests", contexts: ["meeting"], projects: ["compilerRefactor"], priority: 'A'
49+
50+
# => '(A) 2011-10-13 Remind Sean about the failing unit tests @work @meeting +compilerRefactor'
51+
52+
{% endhighlight %}
53+
54+
h2. Discussion
55+
56+
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.
57+
58+
h3. Pre-Construction
59+
60+
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.
61+
62+
{% highlight coffeescript %}
63+
builder = new TodoTxtBuilder(date: "10/13/2011")
64+
65+
builder.newTodo "Order new netbook"
66+
67+
# => '2011-10-13 Order new netbook'
68+
69+
builder.projects.push "summerVacation"
70+
71+
builder.newTodo "Buy suntan lotion"
72+
73+
# => '2011-10-13 Buy suntan lotion +summerVacation'
74+
75+
builder.contexts.push "phone"
76+
77+
builder.newTodo "Order tickets"
78+
79+
# => '2011-10-13 Order tickets @phone +summerVacation'
80+
81+
delete builder.contexts[0]
82+
83+
builder.newTodo "Fill gas tank"
84+
85+
# => '2011-10-13 Fill gas tank +summerVacation'
86+
{% endhighlight %}
87+
88+
h3. Exercises
89+
* Expand the project- and context-tag generation code to filter out duplicate entries.
90+
** 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.
91+
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
layout: recipe
3+
title: Decorator Pattern
4+
chapter: Design Patterns
5+
---
6+
7+
h2. Problem
8+
9+
You have a set of data that you need to process in multiple, possibly varying ways.
10+
11+
h2. Solution
12+
13+
Use the Decorator pattern in order to structure how you apply the changes.
14+
15+
{% highlight coffeescript %}
16+
miniMarkdown = (line) ->
17+
if match = line.match /^(#+)\s*(.*)$/
18+
headerLevel = match[1].length
19+
headerText = match[2]
20+
"<h#{headerLevel}>#{headerText}</h#{headerLevel}>"
21+
else
22+
if line.length > 0
23+
"<p>#{line}</p>"
24+
else
25+
''
26+
27+
stripComments = (line) ->
28+
line.replace /\s*\/\/.*$/, '' # Removes one-line, double-slash C-style comments
29+
30+
TextProcessor = (@processors) ->
31+
reducer: (existing, processor) ->
32+
if processor
33+
processor(existing or '')
34+
else
35+
existing
36+
processLine: (text) ->
37+
@processors.reduce @reducer, text
38+
processString: (text) ->
39+
(@processLine(line) for line in text.split("\n")).join("\n")
40+
41+
exampleText = '''
42+
# A level 1 header
43+
A regular line
44+
// a comment
45+
## A level 2 header
46+
A line // with a comment
47+
'''
48+
49+
processor = new TextProcessor [stripComments, miniMarkdown]
50+
51+
processor.processString exampleText
52+
53+
# => "<h1>A level 1 header</h1>\n<p>A regular line</p>\n\n<h2>A level 2 header</h2>\n<p>A line</p>"
54+
{% endhighlight %}
55+
56+
h3. Results
57+
58+
{% highlight html %}
59+
<h1>A level 1 header</h1>
60+
<p>A regular line</p>
61+
62+
<h2>A level 1 header</h2>
63+
<p>A line</p>
64+
{% endhighlight %}
65+
66+
h2. Discussion
67+
68+
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.
69+
70+
We can even modify the existing Decorator object on the fly:
71+
72+
{% highlight coffeescript %}
73+
smilies =
74+
':)' : "smile"
75+
':D' : "huge_grin"
76+
':(' : "frown"
77+
';)' : "wink"
78+
79+
smilieExpander = (line) ->
80+
if line
81+
(line = line.replace symbol, "<img src='#{text}.png' alt='#{text}' />") for symbol, text of smilies
82+
line
83+
84+
processor.processors.unshift smilieExpander
85+
86+
processor.processString "# A header that makes you :) // you may even laugh"
87+
88+
# => "<h1>A header that makes you <img src='smile.png' alt='smile' /></h1>"
89+
90+
processor.processors.shift()
91+
92+
# => "<h1>A header that makes you :)</h1>"
93+
{% endhighlight %}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
layout: chapter
3+
title: Design Patterns
4+
chapter: Design Patterns
5+
---
6+
7+
{% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %}
8+
{% capture indexurl %}{{ url }}/index.html{% endcapture %}
9+
10+
{% for page in site.pages %}
11+
{% if page.url contains url %}
12+
{% unless page.url == indexurl %}
13+
* <a href="{{ page.url | replace: '.html', '' }}">{{ page.title }}</a>
14+
{% endunless %}
15+
{% endif %}
16+
{% endfor %}
17+
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
layout: recipe
3+
title: Strategy Pattern
4+
chapter: Design Patterns
5+
---
6+
7+
h2. Problem
8+
9+
You have more than one way to solve a problem but you need to choose (or even switch) between these methods at run time.
10+
11+
h2. Solution
12+
13+
Encapsulate your algorithms inside of Strategy objects.
14+
15+
Given an unsorted list, for example, we can change the sorting algorithm under different circumstances.
16+
17+
{% highlight coffeescript %}
18+
StringSorter = (@algorithm) ->
19+
sort: (list) ->
20+
@algorithm list
21+
22+
bubbleSort = (list) ->
23+
anySwaps = false
24+
swapPass = ->
25+
for r in [0..list.length-1]
26+
if list[r] > list[r+1]
27+
anySwaps = true
28+
[list[r], list[r+1]] = [list[r+1], list[r]]
29+
30+
swapPass()
31+
while anySwaps
32+
anySwaps = false
33+
swapPass()
34+
list
35+
36+
reverseBubbleSort = (list) ->
37+
anySwaps = false
38+
swapPass = ->
39+
for r in [list.length-1..1]
40+
if list[r] < list[r-1]
41+
anySwaps = true
42+
[list[r], list[r-1]] = [list[r-1], list[r]]
43+
44+
swapPass()
45+
while anySwaps
46+
anySwaps = false
47+
swapPass()
48+
list
49+
50+
sorter = new StringSorter bubbleSort
51+
52+
unsortedList = ['e', 'b', 'd', 'c', 'x', 'a']
53+
54+
sorter.sort unsortedList
55+
56+
# => ['a', 'b', 'c', 'd', 'e', 'x']
57+
58+
unsortedList.push 'w'
59+
60+
# => ['a', 'b', 'c', 'd', 'e', 'x', 'w']
61+
62+
sorter.algorithm = reverseBubbleSort
63+
64+
sorter.sort unsortedList
65+
66+
# => ['a', 'b', 'c', 'd', 'e', 'w', 'x']
67+
{% endhighlight %}
68+
69+
h2. Discussion
70+
71+
"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.
72+
73+
h3. Exercises
74+
75+
* 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).

chapters/index.textile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ chapters:
1313
- jQuery
1414
- Regular Expressions
1515
- AJAX
16+
- Design Patterns
1617
---
1718

1819

wanted-recipes.textile

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,30 @@ h2. AJAX
123123

124124
h2. Design patterns
125125

126-
* Singleton pattern
126+
* Creational Patterns
127+
** Abstract Factory
128+
** Factory Method
129+
** Prototype
130+
** Singleton
131+
132+
* Structural Patterns
133+
** Adapter
134+
** Bridge
135+
** Composite
136+
** Facade
137+
** Flyweight
138+
** Proxy
139+
140+
* Behavioral Patterns
141+
** Chain of Responsibility
142+
** Command
143+
** Interpreter
144+
** Iterator
145+
** Mediator
146+
** Memento
147+
** Observer
148+
** State
149+
** Template Method
150+
** Visitor
151+
152+

0 commit comments

Comments
 (0)