├── .idea
├── design_patterns_in_ruby.iml
├── encodings.xml
├── misc.xml
├── modules.xml
├── scopes
│ └── scope_settings.xml
├── vcs.xml
└── workspace.xml
├── .ruby-gemset
├── .ruby-version
├── README
├── Rakefile
├── behavioral
├── chain_of_responsibility.rb
├── command1.rb
├── command2.rb
├── domain_function.rb
├── interpreter.rb
├── iterator1.rb
├── iterator2.rb
├── iterator3.rb
├── iterator4.rb
├── mediator.rb
├── memento.rb
├── observer1.rb
├── observer2.rb
├── observer3.rb
├── state.rb
├── strategy1.rb
├── strategy2.rb
├── template_method1.rb
├── template_method2.rb
├── template_method3.rb
├── visitor1.rb
└── visitor2.rb
├── creational
├── abstract-factory.rb
├── builder1.rb
├── builder2.rb
├── factory-method.rb
├── lazy-initialization.rb
├── prototype.rb
├── singleton1.rb
├── singleton2.rb
└── singleton3.rb
├── enterprise
├── data-access-object.rb
├── dci1.rb
├── dci2.rb
├── dci3.rb
├── dsl11.rb
├── dsl12.rb
├── dsl13.rb
├── dsl2.rb
├── hexagonal.rb
├── map-reduce.rb
└── mvc.rb
├── meta_tricks
├── aspect_for_class_and_instance.rb
├── block_as_module.rb
├── class_field_and_instance_field_and_class_instance_field.rb
├── common_method_for_class_and_method.rb
├── data_inside_file.rb
├── locals_to_hash.rb
├── pipeable.rb
├── simple_dsl.rb
└── utility_module.rb
├── notes.txt
├── problems
└── monty_hall_problem.rb
└── structural
├── adapter.rb
├── bridge.rb
├── composite.rb
├── decorator1.rb
├── decorator2.rb
├── decorator3.rb
├── facade.rb
├── flyweight.rb
├── proxy1.rb
└── proxy2.rb
/.idea/design_patterns_in_ruby.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/scopes/scope_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 | $USER_HOME$/.subversion_IDEA
518 | false
519 | 125
520 |
521 |
522 |
523 |
524 | 1281993996010
525 | 1281993996010
526 |
527 |
528 | 1380051823307
529 | 1380051823307
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 | file://$PROJECT_DIR$/enterprise/dsl4.rb
582 | 263
583 |
584 |
585 | file://$PROJECT_DIR$/../../ui-r16/src/app/models/banner_mapping.rb
586 | 16
587 |
588 |
589 | file://$PROJECT_DIR$/../../ui-r16/src/vendor/plugins/triton/app/helpers/triton/display_banner_helper.rb
590 | 12
591 |
592 |
593 | file://$PROJECT_DIR$/meta_tricks/class_field_and_instance_field_and_class_instance_field.rb
594 | 24
595 |
596 |
597 | file://$PROJECT_DIR$/meta_tricks/class_field_and_instance_field_and_class_instance_field.rb
598 | 1
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
--------------------------------------------------------------------------------
/.ruby-gemset:
--------------------------------------------------------------------------------
1 | dpr
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 3.3.4
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | Implementation of Design Patterns in Ruby
2 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 |
3 | include Rake::DSL
4 |
5 | task :fix_debug do
6 | system "mkdir -p $GEM_HOME/gems/debugger-ruby_core_source-1.2.3/lib"
7 | system "cp -R ~/debugger-ruby_core_source/lib $GEM_HOME/gems/debugger-ruby_core_source-1.2.3"
8 | end
9 |
10 |
--------------------------------------------------------------------------------
/behavioral/chain_of_responsibility.rb:
--------------------------------------------------------------------------------
1 | # chain-of-responsibility.rb
2 |
3 | # Avoid coupling the sender of a request to it's receiver by giving more than
4 | # one object a chance to handle the request. Chains the receiving object and passes
5 | # the request along the chain until an object handles it.
6 |
7 | # 1. type interface
8 |
9 | class Chain
10 | def handle
11 | end
12 |
13 | def initialize(next_in_chain)
14 | @next_in_chain = next_in_chain
15 | end
16 | end
17 |
18 | # 2. type implementation
19 |
20 | class MyChain < Chain
21 | def initialize(name, next_in_chain=nil)
22 | super(next_in_chain)
23 |
24 | @name = name
25 | end
26 |
27 | def handle
28 | puts "Handling by #{@name}."
29 |
30 | unless @next_in_chain.nil?
31 | @next_in_chain.handle
32 | end
33 | end
34 | end
35 |
36 | # 3. test
37 |
38 | chain = MyChain.new('chain1', MyChain.new('chain2', MyChain.new('chain3', MyChain.new('chain5', MyChain.new('chain4')))))
39 |
40 | chain.handle
41 |
42 |
--------------------------------------------------------------------------------
/behavioral/command1.rb:
--------------------------------------------------------------------------------
1 | # command1.rb
2 |
3 | # Encapsulate a request as an object, thereby letting you to parametrize
4 | # clients with different requests, queue or log requests, and support
5 | # undoable operations.
6 |
7 | # 1. command abstraction
8 |
9 | class Command
10 | def execute()
11 | end
12 | end
13 |
14 | # 2. command implementations
15 |
16 | class MyCommand1 < Command
17 | def execute
18 | puts 'command1'
19 | end
20 | end
21 |
22 | class MyCommand2 < Command
23 | def execute
24 | puts 'command2'
25 | end
26 | end
27 |
28 | class MyCommand3 < Command
29 | def execute
30 | puts 'command3'
31 | end
32 | end
33 |
34 |
35 | # 3. test
36 |
37 | command1 = MyCommand1.new
38 | command2 = MyCommand2.new
39 | command3 = MyCommand3.new
40 |
41 | command1.execute
42 | command2.execute
43 | command3.execute
44 |
45 |
46 |
--------------------------------------------------------------------------------
/behavioral/command2.rb:
--------------------------------------------------------------------------------
1 | # command2.rb
2 |
3 | # We use blocks for command implementation.
4 |
5 | # 1. class that uses commands-blocks
6 |
7 | class CommandUser
8 | def initialize(&command)
9 | @command = command
10 | end
11 |
12 | def operation
13 | @command.call unless @command.nil?
14 | end
15 | end
16 |
17 |
18 | # 2. test
19 |
20 | command_user = CommandUser.new do
21 | puts 'command1'
22 | end
23 |
24 | command_user.operation
25 |
--------------------------------------------------------------------------------
/behavioral/domain_function.rb:
--------------------------------------------------------------------------------
1 | # domain-function.bsh
2 | # better than visitor
3 | # http://blogs.concedere.net:8080/blog/discipline/java/?permalink=Domain-Function-Pattern.html
4 |
5 | class Employee
6 | def initialize(type, name)
7 | @type = type
8 | @name = name
9 | end
10 |
11 | attr_reader :type
12 |
13 | def to_s
14 | @name
15 | end
16 | end
17 |
18 | class EmployeeDomainFunction
19 | def call(employee); end
20 | end
21 |
22 | class VacationPlanner < EmployeeDomainFunction
23 | def call(employee)
24 | case employee.type
25 | when :contractor
26 | puts 'Vacation planning for Contractor: 0'
27 | when :executive
28 | puts 'Vacation planning for Executive: 90'
29 | when :manager
30 | puts 'Vacation planning for Manager: 45'
31 | when :secretary
32 | puts 'Vacation planning for Secretary: 15'
33 | when :engineer
34 | puts 'Vacation planning for Engineer: 10'
35 | else
36 | raise "unknown employee type: #{employee.type}"
37 | end
38 | end
39 | end
40 |
41 |
42 | vacation_planner = VacationPlanner.new
43 |
44 | employees = []
45 |
46 | employees << Employee.new(:contractor, 'contractor 1')
47 | employees << Employee.new(:contractor, 'contractor 2')
48 | employees << Employee.new(:executive, 'executive 1')
49 | employees << Employee.new(:manager, 'manager 1')
50 | employees << Employee.new(:secretary, 'secretary 1')
51 | employees << Employee.new(:engineer, 'engineer 1')
52 | employees << Employee.new(:engineer, 'engineer 2')
53 |
54 | puts "employees: #{employees.join(', ')}"
55 |
56 | puts 'Vacation planning:'
57 |
58 | employees.each do |employee|
59 | vacation_planner.call(employee)
60 | end
61 |
62 |
--------------------------------------------------------------------------------
/behavioral/interpreter.rb:
--------------------------------------------------------------------------------
1 | # interpreter.rb
2 |
3 | # Given a language, defines a representation for it's grammar along with an interpreter
4 | # that uses the representation to interpret sentences in the language
5 |
6 | # 1. context
7 |
8 | class NamesInterpreterContext
9 | def initialize
10 | @names = []
11 |
12 | @names << 'monitor'
13 | @names << 'keyboard'
14 | @names << 'mouse'
15 | @names << 'system-block'
16 | end
17 |
18 | attr_reader :names
19 | end
20 |
21 | class NamesInterpreter
22 | def initialize(context)
23 | @context = context
24 | end
25 |
26 | # expression syntax:
27 | # show names
28 | def interpret(expression)
29 | result = ''
30 |
31 | tokens = expression.chomp.scan(/\(|\)|[\w.*]+/) # extract each word
32 |
33 | i = 0
34 | while i <= tokens.size do
35 | token = tokens[i]
36 |
37 | unless token.nil?
38 | if token == 'show'
39 | token = tokens[i + 1]
40 | i += 1
41 |
42 | result = if token == 'names'
43 | result + @context.names.join(', ')
44 | else
45 | "#{result}error!"
46 | end
47 | else
48 | result += 'error!'
49 | end
50 | end
51 |
52 | i += 1
53 | end
54 |
55 | result
56 | end
57 | end
58 |
59 | # test
60 |
61 | interpreter = NamesInterpreter.new(NamesInterpreterContext.new)
62 |
63 | puts "interpreting show names: #{interpreter.interpret('show names')}"
64 |
--------------------------------------------------------------------------------
/behavioral/iterator1.rb:
--------------------------------------------------------------------------------
1 | # iterator1.rb
2 |
3 | # Provides a way to access the elements of an aggregate object sequentially without exposing
4 | # its underlying representation.
5 |
6 | # 1. iterator implementation (original collection is initialized internally)
7 |
8 | module Iterator
9 | def initialize
10 | @array = []
11 | end
12 |
13 | def each
14 | copy = Array.new(@array)
15 |
16 | i = 0
17 |
18 | while i < copy.length
19 | if block_given?
20 | yield(copy[i])
21 | end
22 |
23 | i += 1
24 | end
25 | end
26 | end
27 |
28 | class MyIterator
29 | include Iterator
30 |
31 | def initialize
32 | super
33 |
34 | @array = %w[e1 e2 e3 e4]
35 | end
36 | end
37 |
38 |
39 | # test
40 |
41 | iterator = MyIterator.new
42 |
43 | iterator.each {|e| puts e}
44 |
--------------------------------------------------------------------------------
/behavioral/iterator2.rb:
--------------------------------------------------------------------------------
1 | # iterator2.rb
2 |
3 | # 1. iterator implementation (original collection is initialized externally)
4 |
5 | class ExternalIterator
6 | def initialize(array)
7 | @array = Array.new(array)
8 | @index = 0
9 | end
10 |
11 | def next?
12 | @index < @array.length
13 | end
14 |
15 | def next
16 | value = @array[@index]
17 |
18 | @index += 1
19 |
20 | value
21 | end
22 |
23 | def current_element
24 | @array[@index]
25 | end
26 | end
27 |
28 | # 2. test
29 |
30 | array = %w[e1 e2 e3 e4]
31 |
32 | iterator = ExternalIterator.new(array)
33 |
34 | puts iterator.next while iterator.next?
35 |
--------------------------------------------------------------------------------
/behavioral/iterator3.rb:
--------------------------------------------------------------------------------
1 | # iterator3.rb
2 |
3 | # Iterator with typical ruby implementation
4 |
5 | # 1. iterator implementation
6 |
7 | class MyEnumerable
8 | def for_each(array)
9 | i = 0
10 | while i < array.length
11 | yield(array[i])
12 | i += 1
13 | end
14 | end
15 | end
16 |
17 | # 2. test
18 |
19 | array = %w[e1 e2 e3 e4]
20 |
21 | my_enumerable = MyEnumerable.new
22 |
23 | my_enumerable.for_each(array) do |e|
24 | puts e
25 | end
26 |
27 |
--------------------------------------------------------------------------------
/behavioral/iterator4.rb:
--------------------------------------------------------------------------------
1 | # iterator4.rb
2 |
3 | # Iterator with help of Enumerable
4 |
5 | # 1. iterator implementation
6 |
7 | class MyEnumerable
8 | include Enumerable
9 |
10 | def initialize
11 | @array = []
12 | end
13 |
14 | def each(&block)
15 | @array.each(&block)
16 | end
17 |
18 | def <<(element)
19 | @array << element
20 | end
21 | end
22 |
23 |
24 | # 2. implementation of collection's element
25 |
26 | class MyElement
27 | attr_accessor :name, :value
28 |
29 | def initialize(name, value)
30 | @name = name
31 | @value = value
32 | end
33 |
34 | # this method is important for Enumerable
35 | def <=>(other)
36 | value <=> other.value
37 | end
38 |
39 | def to_s
40 | "[name: #{name}, value: #{value}]"
41 | end
42 | end
43 |
44 |
45 | # 3. test
46 |
47 | my_enumerable = MyEnumerable.new
48 |
49 | el = MyElement.new('n2', 2001)
50 |
51 | my_enumerable << MyElement.new('n1', 1)
52 | my_enumerable << el
53 | my_enumerable << MyElement.new('n3', 22)
54 | my_enumerable << MyElement.new('n4', 64)
55 |
56 | puts "Has n2: #{my_enumerable.include?(el)}"
57 |
58 | puts 'All elements have value >= 10: ' + (my_enumerable.all? { |e| e.value >= 10 }).to_s
59 |
60 | puts 'Has any element with value >= 2000: ' + (my_enumerable.any? { |e| e.value > 2000 }).to_s
61 |
62 | puts 'Display collection: '
63 |
64 | my_enumerable.each do |e|
65 | puts e
66 | end
67 |
--------------------------------------------------------------------------------
/behavioral/mediator.rb:
--------------------------------------------------------------------------------
1 | # mediator.bsh
2 |
3 | # Define an object that encapsulates how a set of objects interact.
4 | # Promotes loose coupling by keeping objects from referring to each other
5 | # explicitly and it lets you vary their interactions independently.
6 |
7 | # 1. mediator interface
8 |
9 | class Mediator
10 | def operation1
11 | end
12 |
13 | def operation2
14 | end
15 | end
16 |
17 | # 2. Mediator implementation
18 |
19 | class MyMediator < Mediator
20 | def operation1
21 | puts 'mediator: operation 1'
22 | end
23 |
24 | def operation2
25 | puts 'mediator: operation 2'
26 | end
27 | end
28 |
29 | # 3. Concrete classes that are aware of mediator
30 |
31 | class Product1
32 | def initialize(mediator)
33 | @mediator = mediator
34 | end
35 |
36 | def perform
37 | @mediator.operation1
38 | end
39 | end
40 |
41 | class Product2
42 | def initialize(mediator)
43 | @mediator = mediator
44 | end
45 |
46 | def perform
47 | @mediator.operation1
48 | end
49 | end
50 |
51 | class Product3
52 | def initialize(mediator)
53 | @mediator = mediator
54 | end
55 |
56 | def perform
57 | @mediator.operation2
58 | end
59 | end
60 |
61 | # 4. test
62 |
63 | mediator = MyMediator.new
64 |
65 | product11 = Product1.new(mediator)
66 | product12 = Product1.new(mediator)
67 |
68 | product21 = Product2.new(mediator)
69 | product22 = Product2.new(mediator)
70 |
71 | product31 = Product3.new(mediator)
72 | product32 = Product3.new(mediator)
73 |
74 | product11.perform
75 | product12.perform
76 |
77 | product21.perform
78 | product21.perform
79 |
80 | product31.perform
81 | product31.perform
82 |
83 |
--------------------------------------------------------------------------------
/behavioral/memento.rb:
--------------------------------------------------------------------------------
1 | # memento.bsh
2 |
3 | # Without violating encapsulation, capture and externalize an object's internal state
4 | # so that the object can be restored to this state later.
5 |
6 | # 1. This class don't want to reveal it's internal state and delegates presistance work to memento
7 | # object. This object should have access to private properties of enclosed class.
8 |
9 | class Memento
10 | def initialize(originator, memento_state)
11 | @originator = originator
12 | @memento_state = memento_state
13 | end
14 |
15 | def restore_memento
16 | @originator.state = @memento_state
17 | end
18 | end
19 |
20 | class Originator
21 | def initialize
22 | @state = nil
23 | end
24 |
25 | attr_writer :state
26 |
27 | def state_to_s
28 | @state.to_s
29 | end
30 |
31 | def create_memento
32 | Memento.new(self, @state)
33 | end
34 |
35 | def restore_memento(m)
36 | m.restore_memento
37 | end
38 | end
39 |
40 |
41 | # 2. Caretaker holds previously created mementos: storage
42 |
43 | class Caretaker
44 | def initialize
45 | @saved_states = []
46 | end
47 |
48 | def add_memento(memento)
49 | @saved_states << memento
50 | end
51 |
52 | def [](index)
53 | @saved_states[index]
54 | end
55 | end
56 |
57 | # 3. test
58 |
59 | caretaker = Caretaker.new
60 | originator = Originator.new
61 |
62 | originator.state = 'State1'
63 | puts "step1: #{originator.state_to_s}"
64 |
65 | originator.state = 'State2'
66 | puts "step2: #{originator.state_to_s}"
67 |
68 | caretaker.add_memento(originator.create_memento)
69 | puts "step3: #{originator.state_to_s}"
70 |
71 | originator.state = 'State3'
72 | puts "step4: #{originator.state_to_s}"
73 |
74 | caretaker.add_memento(originator.create_memento)
75 | puts "step5: #{originator.state_to_s}"
76 |
77 | originator.state = 'State4'
78 | puts "step6: #{originator.state_to_s}"
79 |
80 | originator.restore_memento(caretaker[1])
81 | puts "step7: #{originator.state_to_s}"
82 |
--------------------------------------------------------------------------------
/behavioral/observer1.rb:
--------------------------------------------------------------------------------
1 | # observer1.rb
2 |
3 | # Define a one-to-many dependency between objects so that when one object
4 | # changes state, all it's dependents are notified and updated automatically.
5 |
6 | # 1. observer
7 |
8 | class Observer
9 | def initialize(name)
10 | @name = name
11 | end
12 |
13 | def update(value)
14 | puts "updated #{@name}. New value: #{value}"
15 | end
16 | end
17 |
18 | # 2. Observable (serves as a container for observers and takes care of notifying them)
19 |
20 | module Observable
21 | def initialize
22 | @observers = []
23 | end
24 |
25 | def <<(observer)
26 | @observers << observer
27 | end
28 |
29 | def >>(observer)
30 | @observers.delete(observer)
31 | end
32 |
33 | protected
34 |
35 | def notify_observers(value)
36 | @observers.clone.each {|observer| observer.update(value) }
37 | end
38 | end
39 |
40 | # 3. Implementation of the observer
41 | class MyObservable
42 | include Observable
43 |
44 | attr_reader :my_property
45 |
46 | def my_property=(my_property)
47 | @my_property = my_property
48 |
49 | notify_observers(my_property)
50 | end
51 | end
52 |
53 | observer1 = Observer.new('n1')
54 | observer2 = Observer.new('n2')
55 | observer3 = Observer.new('n3')
56 |
57 | my_observable = MyObservable.new
58 |
59 | my_observable << observer1
60 | my_observable << observer2
61 | my_observable << observer3
62 |
63 | my_observable.my_property = 'red'
64 |
65 | puts 'Deleting observer 3 ----------'
66 |
67 | my_observable >> observer3
68 |
69 | my_observable.my_property = 'green'
70 |
--------------------------------------------------------------------------------
/behavioral/observer2.rb:
--------------------------------------------------------------------------------
1 | # observer2.rb
2 |
3 | # Slight modification of previous example with metaprogramming to wrap methods
4 | # that needs to be observed.
5 |
6 | # 1. observer
7 |
8 | class Observer
9 | def initialize(name)
10 | @name = name
11 | end
12 |
13 | def update(value)
14 | puts "updated #{@name}. New value: #{value}"
15 | end
16 | end
17 |
18 | # 2. Observable, serves as a container for observers and takes care of notifying them
19 |
20 | module ObservableClassMethods
21 | def act_as_observable(* list)
22 | list.each do |observable|
23 | method_name = "#{observable}="
24 | no_callback_method_name = "no_callback_#{observable}="
25 |
26 | alias_method no_callback_method_name, method_name
27 |
28 | define_method method_name do |value|
29 | send no_callback_method_name, value
30 |
31 | notify_observers(value)
32 | end
33 | end
34 | end
35 | end
36 |
37 | module Observable
38 | def self.included(base)
39 | base.extend(ObservableClassMethods)
40 | end
41 |
42 | def initialize
43 | @observers = []
44 | end
45 |
46 | def <<(observer)
47 | @observers << observer
48 | end
49 |
50 | def >>(observer)
51 | @observers.delete(observer)
52 | end
53 |
54 | protected
55 |
56 | def notify_observers(value)
57 | @observers.clone.each do |observer|
58 | observer.update(value) if observer.is_a? Observer
59 | observer.call(value) if observer.is_a? Proc
60 | end
61 | end
62 | end
63 |
64 | # 3. Implementation of the observer
65 | class MyObservable
66 | include Observable
67 |
68 | attr_accessor :my_property
69 |
70 | # def my_property=(my_property)
71 | # @my_property = my_property
72 | #
73 | # notify_observers(my_property)
74 | # end
75 | act_as_observable :my_property
76 | end
77 |
78 | # 4. test
79 |
80 | observer1 = Observer.new('n1')
81 | observer2 = Observer.new('n2')
82 | observer3 = Observer.new('n3')
83 | observer4 = ->(value) { puts "updated from lambda (update is :#{value})" }
84 |
85 | my_observable = MyObservable.new
86 |
87 | my_observable << observer1
88 | my_observable << observer2
89 | my_observable << observer3
90 | my_observable << observer4
91 |
92 | my_observable.my_property = 'red'
93 |
94 | puts 'Deleting observer 3 ----------'
95 |
96 | my_observable >> observer3
97 |
98 | my_observable.my_property = 'green'
99 |
--------------------------------------------------------------------------------
/behavioral/observer3.rb:
--------------------------------------------------------------------------------
1 | # observer3.rb
2 |
3 | # Implements Observer pattern with the help of ruby observer library
4 |
5 | require 'observer'
6 |
7 | # 1. observer
8 |
9 | class Observer
10 | def initialize(name)
11 | @name = name
12 | end
13 |
14 | def update(value)
15 | puts "updated #{@name}. New value: #{value}"
16 | end
17 | end
18 |
19 | # 2. Observable is imported from 'observer' module
20 |
21 | # 3. Implementation of the observer
22 |
23 | class MyObservable
24 | include Observable
25 |
26 | def my_property=(my_property)
27 | @my_property = my_property
28 |
29 | changed # specific to ruby Observable library
30 | notify_observers(my_property)
31 | end
32 | end
33 |
34 | # 4. test
35 |
36 | observer1 = Observer.new('n1')
37 | observer2 = Observer.new('n2')
38 | observer3 = Observer.new('n3')
39 |
40 | observer4 = Proc.new {}
41 |
42 | observer4.instance_eval do
43 | def update(value)
44 | puts "updated from lambda (update is :#{value})"
45 | end
46 | end
47 |
48 | my_observable = MyObservable.new
49 |
50 | my_observable.add_observer observer1
51 | my_observable.add_observer observer2
52 | my_observable.add_observer observer3
53 | my_observable.add_observer observer4
54 |
55 | my_observable.my_property = 'red'
56 |
57 | puts 'Deleting observer 3 ----------'
58 |
59 | my_observable.delete_observer observer3
60 |
61 | my_observable.my_property = 'green'
62 |
--------------------------------------------------------------------------------
/behavioral/state.rb:
--------------------------------------------------------------------------------
1 | # states.bsh
2 |
3 | # Allow an object to alter it's behavior when it's internal state changes.
4 | # The object will appear to change it's class.
5 |
6 | # in ruby you can achieve same goal with metaprogramming
7 |
8 | # 1. state type and it's implementations
9 |
10 | class State
11 | def handle
12 | end
13 | end
14 |
15 | class MyState1 < State
16 | def handle
17 | puts 'handle1'
18 | end
19 | end
20 |
21 | class MyState2 < State
22 | def handle
23 | puts 'handle2'
24 | end
25 | end
26 |
27 | # 2. context's type and it's implementation
28 |
29 | class Context
30 | def state=(state)
31 | @state = state
32 | end
33 |
34 | def request
35 | @state.handle
36 | end
37 | end
38 |
39 | # 3. test
40 |
41 | context = Context.new
42 |
43 | state1 = MyState1.new
44 | state2 = MyState2.new
45 |
46 | context.state = state1
47 | context.request
48 |
49 | context.state = state2
50 | context.request
51 |
--------------------------------------------------------------------------------
/behavioral/strategy1.rb:
--------------------------------------------------------------------------------
1 | # strategy1.rb
2 |
3 | # Defines a family of algorithms, encapsulate each one and make them interchangeable.
4 | # Let the algorithm vary independently from clients that use it.
5 |
6 | # 1. strategy implementations
7 |
8 | class MyStrategy1
9 | def operation
10 | puts 'operation1'
11 | end
12 | end
13 |
14 | class MyStrategy2
15 | def operation
16 | puts 'operation2'
17 | end
18 | end
19 |
20 | # 2. strategy context
21 |
22 | class StrategyContext
23 | attr_writer :strategy
24 |
25 | def execute
26 | @strategy.operation
27 | end
28 | end
29 |
30 | # 3. test
31 |
32 | context = StrategyContext.new
33 |
34 | strategy1 = MyStrategy1.new
35 | strategy2 = MyStrategy2.new
36 |
37 | context.strategy = strategy1
38 | context.execute
39 |
40 | context.strategy = strategy2
41 | context.execute
42 |
--------------------------------------------------------------------------------
/behavioral/strategy2.rb:
--------------------------------------------------------------------------------
1 | # strategy2.rb
2 |
3 | # Defines strategy on the fly with the help of ruby blocks.
4 |
5 | # 1. strategy context
6 |
7 | class StrategyContext
8 | def execute &strategy
9 | strategy.call(self)
10 | end
11 | end
12 |
13 | # 2. test
14 |
15 | context = StrategyContext.new
16 |
17 | context.execute do |context|
18 | # strategy 1 implementation
19 | puts "operation1 from #{context}"
20 | end
21 |
22 | context.execute do |context|
23 | # strategy 2 implementation
24 | puts "operation2 from #{context}"
25 | end
26 |
--------------------------------------------------------------------------------
/behavioral/template_method1.rb:
--------------------------------------------------------------------------------
1 | # template_method1.rb
2 |
3 | # Defines the skeleton of an algorithm in an operation, deferring some steps
4 | # to subclasses. Let subclasses redefine certain steps of an algorithm without
5 | # changing the algorithm's structure.
6 |
7 | # 1. template
8 |
9 | module AlgorithmTemplate
10 | # These methods are "primitive operations" and must be overridden in the concrete templates
11 | def step1
12 | raise 'Called abstract method: step1'
13 | end
14 |
15 | def step2
16 | raise 'Called abstract method: step2'
17 | end
18 |
19 | def step3
20 | raise 'Called abstract method: step3'
21 | end
22 |
23 | # The "template method" - calls the concrete class methods, is not overridden
24 | def some_template_method
25 | step1
26 | step2
27 | step3
28 | end
29 | end
30 |
31 | # 2. adding template behavior
32 |
33 | class MyAlgorithmTemplate1
34 | include AlgorithmTemplate
35 |
36 | protected
37 |
38 | def step1
39 | puts 'algorithm1: step1'
40 | end
41 |
42 | def step2
43 | puts 'algorithm1: step2'
44 | end
45 |
46 | def step3
47 | puts 'algorithm1: step3'
48 | end
49 | end
50 |
51 | class MyAlgorithmTemplate2
52 | include AlgorithmTemplate
53 |
54 | protected
55 |
56 | def step1
57 | puts 'algorithm2: step1'
58 | end
59 |
60 | def step2
61 | puts 'algorithm2: step2'
62 | end
63 |
64 | def step3
65 | puts 'algorithm2: step3'
66 | end
67 | end
68 |
69 | # test
70 |
71 | template1 = MyAlgorithmTemplate1.new
72 | template2 = MyAlgorithmTemplate2.new
73 |
74 | template1.some_template_method
75 | template2.some_template_method
76 |
--------------------------------------------------------------------------------
/behavioral/template_method2.rb:
--------------------------------------------------------------------------------
1 | # template_method2.rb
2 |
3 | # This example is slightly different from previous implementation in the way of handling abstract methods.
4 | # We extend ruby Module class to support "abstract_methods" meta-method.
5 |
6 | # ruby language extension
7 | class Module
8 |
9 | # Invoked during class definitions
10 | def abstract_methods(*names)
11 | names.each do |name|
12 | define_method name.to_s do
13 | raise "Called abstract method: #{name}"
14 | end
15 | end
16 | end
17 | end
18 |
19 | # 1. template
20 |
21 | module AlgorithmTemplate
22 | # These methods are "primitive operations" and must be overridden in the concrete templates
23 | abstract_methods :step1, :step2, :step3
24 |
25 | # The "template method" - calls the concrete class methods, is not overridden
26 | def some_template_method
27 | step1
28 | step2
29 | step3
30 | end
31 | end
32 |
33 | # 2. adding template behavior
34 |
35 | class MyAlgorithmTemplate1
36 | include AlgorithmTemplate
37 |
38 | protected
39 |
40 | def step1
41 | puts 'algorithm1: step1'
42 | end
43 |
44 | def step2
45 | puts 'algorithm1: step2'
46 | end
47 |
48 | def step3
49 | puts 'algorithm1: step3'
50 | end
51 | end
52 |
53 | class MyAlgorithmTemplate2
54 | include AlgorithmTemplate
55 |
56 | protected
57 |
58 | def step1
59 | puts 'algorithm2: step1'
60 | end
61 |
62 | def step2
63 | puts 'algorithm2: step2'
64 | end
65 |
66 | def step3
67 | puts 'algorithm2: step3'
68 | end
69 | end
70 |
71 | # test
72 |
73 | template1 = MyAlgorithmTemplate1.new
74 | template2 = MyAlgorithmTemplate2.new
75 |
76 | template1.some_template_method
77 | template2.some_template_method
78 |
--------------------------------------------------------------------------------
/behavioral/template_method3.rb:
--------------------------------------------------------------------------------
1 | # template-method3.bsh
2 |
3 | # This example uses Strategy Pattern for storing steps of template method algorithm.
4 |
5 | # 1. Strategy
6 |
7 | class TemplateMethodStep
8 | def initialize(&code)
9 | @code = code
10 | end
11 |
12 | def operation
13 | @code.call
14 | end
15 | end
16 |
17 | # 2. template method algorithm; also acts as strategy context
18 |
19 | module AlgorithmTemplate
20 | def initialize
21 | @steps = [] # strategies
22 | end
23 |
24 | # The "template method"
25 | def some_template_method
26 | @steps.each(&:operation)
27 | end
28 | end
29 |
30 | class MyAlgorithmTemplate1
31 | include AlgorithmTemplate
32 |
33 | def initialize
34 | super
35 |
36 | @steps << TemplateMethodStep.new { puts 'stategy1: step1' }
37 | @steps << TemplateMethodStep.new { puts 'stategy1: step2' }
38 | @steps << TemplateMethodStep.new { puts 'stategy1: step3' }
39 | end
40 | end
41 |
42 | class MyAlgorithmTemplate2
43 | include AlgorithmTemplate
44 |
45 | def initialize
46 | super
47 |
48 | @steps << TemplateMethodStep.new { puts 'strategy2: step1' }
49 | @steps << TemplateMethodStep.new { puts 'strategy2: step2' }
50 | @steps << TemplateMethodStep.new { puts 'strategy2: step3' }
51 | end
52 | end
53 |
54 |
55 | # test
56 |
57 | template1 = MyAlgorithmTemplate1.new
58 | template2 = MyAlgorithmTemplate2.new
59 |
60 | template1.some_template_method
61 | template2.some_template_method
62 |
63 |
--------------------------------------------------------------------------------
/behavioral/visitor1.rb:
--------------------------------------------------------------------------------
1 | # visitor1.rb
2 |
3 | # Represents an operation to be performed on the elements of an object structure.
4 | # Lets you define a new operation without changing the classes of the elements
5 | # on which it operates.
6 |
7 | # 1. visitable and visitor interfaces
8 |
9 |
10 | def underscore(camel_cased_word)
11 | camel_cased_word.to_s.gsub(/::/, '/').
12 | gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
13 | gsub(/([a-z\d])([A-Z])/, '\1_\2').
14 | tr('-', '_').
15 | downcase
16 | end
17 |
18 | module Visitable
19 | def accept(visitor)
20 | visitor.visit(self)
21 | end
22 | end
23 |
24 | module Visitor
25 | def visit(visitable)
26 | end
27 | end
28 |
29 | # 2. type implementation with visitable behavior
30 |
31 | # basic parts
32 |
33 | class MyVisitable1
34 | include Visitable
35 | end
36 |
37 | class MyVisitable2
38 | include Visitable
39 | end
40 |
41 | class MyVisitable3
42 | include Visitable
43 | end
44 |
45 |
46 | # compound
47 |
48 | class MyCompoundVisitable
49 | include Visitable
50 |
51 | def initialize
52 | @visitable1 = MyVisitable1.new
53 | @visitable2 = MyVisitable2.new
54 |
55 | @visitables3 = [
56 | MyVisitable3.new, MyVisitable3.new, MyVisitable3.new
57 | ]
58 | end
59 |
60 | alias original_accept accept
61 |
62 | def accept(visitor)
63 | original_accept visitor
64 |
65 | # takes care of components
66 | @visitable1.accept(visitor)
67 | @visitable2.accept(visitor)
68 |
69 | @visitables3.each { |visitable| visitable.accept(visitor) }
70 | end
71 | end
72 |
73 | # 3. visitor implementations
74 |
75 | class MyCompoundVisitor1
76 | include Visitor
77 |
78 | def visit(visitable)
79 | method_name = "visit_#{underscore(visitable.class.name)}"
80 |
81 | send method_name.to_sym, visitable
82 | end
83 |
84 | def visit_my_visitable1 visitable
85 | puts 'visitor: visiting my visitable 11'
86 | end
87 |
88 | def visit_my_visitable2 visitable
89 | puts 'visitor: visiting my visible 12'
90 | end
91 |
92 | def visit_my_visitable3 visitable
93 | puts 'visitor: visiting my visitable 13'
94 | end
95 |
96 | def visit_my_compound_visitable visitable
97 | puts 'visitor: visiting my compound visitable 1'
98 | end
99 | end
100 |
101 | # 4. test
102 |
103 | # creating complex visitable
104 |
105 | visitable = MyCompoundVisitable.new
106 |
107 | visitor1 = MyCompoundVisitor1.new
108 |
109 | visitable.accept(visitor1)
110 |
111 | # creating visitor dynamically
112 |
113 | class MyCompoundVisitor2
114 | include Visitor
115 | def visit visitable
116 | case visitable
117 | when MyVisitable1
118 | puts 'visitor: visiting my visitable 21'
119 | when MyVisitable2
120 | puts 'visitor: visiting my visible 22'
121 | when MyVisitable3
122 | puts 'visitor: visiting my visitable 23'
123 | when MyCompoundVisitable
124 | puts 'visitor: visiting my compound visitable 2'
125 | else
126 | # type code here
127 | end
128 | end
129 | end
130 |
131 | visitor2 = MyCompoundVisitor2.new
132 |
133 | visitable.accept(visitor2)
134 |
135 |
--------------------------------------------------------------------------------
/behavioral/visitor2.rb:
--------------------------------------------------------------------------------
1 | # visitor2.rb
2 |
3 | # Instead of using separate visitor class this example uses blocks of code.
4 |
5 | # 1. type interface
6 |
7 | module Visitable
8 | def accept(&visitor_code)
9 | visitor_code.call(self)
10 | end
11 | end
12 |
13 | # we don't need visitor here: use code fragment instead
14 |
15 | #class Visitor
16 | # def visit(visitable)
17 | # end
18 | #end
19 |
20 |
21 | # 2. type implementation with visitable behavior
22 |
23 | # basic parts
24 |
25 | class MyVisitable1
26 | include Visitable
27 | end
28 |
29 | class MyVisitable2
30 | include Visitable
31 | end
32 |
33 | class MyVisitable3
34 | include Visitable
35 | end
36 |
37 | # compound
38 |
39 | class MyCompoundVisitable
40 | include Visitable
41 |
42 | def initialize
43 | @visitable1 = MyVisitable1.new
44 | @visitable2 = MyVisitable2.new
45 |
46 | @visitables3 = [
47 | MyVisitable3.new, MyVisitable3.new, MyVisitable3.new
48 | ]
49 | end
50 |
51 | def accept(&visitor_code)
52 | visitor_code.call(self)
53 | #visitor.visit(self)
54 |
55 | # takes care of components
56 | @visitable1.accept(&visitor_code)
57 | @visitable2.accept(&visitor_code)
58 |
59 | @visitables3.each { |visitable| visitable.accept(&visitor_code) }
60 | end
61 | end
62 |
63 | # 3. visitor implementations
64 |
65 | # 4. test
66 |
67 | visitable = MyCompoundVisitable.new
68 |
69 | # creating visitor dynamically
70 |
71 | visitor_code = Proc.new do |visitable|
72 | if visitable.is_a? MyVisitable1
73 | puts 'visitor: visiting my visitable 1'
74 | elsif visitable.is_a? MyVisitable2
75 | puts 'visitor: visiting my visible 2'
76 | elsif visitable.is_a? MyVisitable3
77 | puts 'visitor: visiting my visitable 3'
78 | elsif visitable.is_a? MyCompoundVisitable
79 | puts 'visitor: visiting my compound visitable'
80 | end
81 | end
82 |
83 | visitable.accept &visitor_code
84 |
--------------------------------------------------------------------------------
/creational/abstract-factory.rb:
--------------------------------------------------------------------------------
1 | # abstract-factory.rb
2 |
3 | # Provides an interface for creating families of related or dependent objects without specifying
4 | # their concrete class.
5 | #
6 | # Provides one level of interface higher than the Factory Method pattern.
7 | # It is used to return one of the several factories.
8 |
9 | # 1. common interface for type and it's specializations
10 |
11 | class Alive
12 | def live
13 | end
14 | end
15 |
16 | class Plant < Alive
17 | end
18 |
19 | class Animal < Alive
20 | end
21 |
22 | # 2. implementations of specializations
23 |
24 | class Roze < Plant
25 | def live
26 | puts 'roze'
27 | end
28 | end
29 |
30 | class Elephant < Animal
31 | def live
32 | puts 'elephant'
33 | end
34 | end
35 |
36 | # 3. interface for factory (abstract factory)
37 |
38 | class AliveFactory
39 | def create_alive
40 | end
41 | end
42 |
43 | # 4. different factories implementations
44 |
45 | class PlantFactory < AliveFactory
46 | def create_alive
47 | Roze.new
48 | end
49 | end
50 |
51 | class AnimalFactory < AliveFactory
52 | def create_alive
53 | Elephant.new
54 | end
55 | end
56 |
57 | class DynamicAliveFactory
58 | def initialize(alive_class)
59 | @alive_class = alive_class
60 | end
61 |
62 | def create_alive
63 | @alive_class.new
64 | end
65 | end
66 |
67 | # 5. factories manager (optional)
68 |
69 | class AliveManager
70 | def create_alive_factory(type)
71 | if type == :plant
72 | PlantFactory.new
73 | elsif type == :animal
74 | AnimalFactory.new
75 | end
76 | end
77 | end
78 |
79 | # 6. test
80 |
81 | alive_manager = AliveManager.new
82 |
83 | alive_factory1 = alive_manager.create_alive_factory(:plant)
84 | alive_factory2 = alive_manager.create_alive_factory(:animal)
85 | alive_factory3 = DynamicAliveFactory.new(Roze)
86 |
87 | alive1 = alive_factory1.create_alive
88 | alive2 = alive_factory2.create_alive
89 | alive3 = alive_factory3.create_alive
90 |
91 | alive1.live
92 | alive2.live
93 | alive3.live
94 |
--------------------------------------------------------------------------------
/creational/builder1.rb:
--------------------------------------------------------------------------------
1 | # builder1.rb
2 |
3 | # Separates the construction of a complex object from its representation so that the same
4 | # construction process can create different representations.
5 |
6 | # 1. type builder interface
7 |
8 | class ProductBuilder
9 | def build_part1; end
10 |
11 | def build_part2; end
12 |
13 | def build_part3; end
14 | end
15 |
16 | # 2. type builder implementations
17 |
18 | class ComputerBuilder < ProductBuilder
19 | def build_part1
20 | puts 'Building part1: motherboard'
21 | end
22 |
23 | def build_part2
24 | puts 'Building part2: CPU'
25 | end
26 |
27 | def build_part3
28 | puts 'Building part3: display'
29 | end
30 | end
31 |
32 | class TableBuilder < ProductBuilder
33 | def build_part1
34 | puts 'Building part1: legs'
35 | end
36 |
37 | def build_part2
38 | puts 'Building part2: top'
39 | end
40 |
41 | def build_part3
42 | puts 'Building part3: mounting'
43 | end
44 | end
45 |
46 | # 3. director
47 |
48 | class Director
49 | def construct(builder)
50 | builder.build_part1
51 | builder.build_part2
52 | builder.build_part3
53 | end
54 | end
55 |
56 | # 4. test
57 |
58 | director = Director.new
59 |
60 | director.construct(ComputerBuilder.new)
61 | director.construct(TableBuilder.new)
62 |
63 |
--------------------------------------------------------------------------------
/creational/builder2.rb:
--------------------------------------------------------------------------------
1 | # builder2.rb
2 |
3 | # This example uses ruby "method_missing" to implement so-called "magic method"
4 |
5 | # 1. type builder interface
6 |
7 | class ProductBuilder
8 | def build_part1; end
9 |
10 | def build_part2; end
11 |
12 | def build_part3; end
13 |
14 | def method_missing(name, *args)
15 | words = name.to_s.split('_')
16 |
17 | return super(name, *args) unless words.shift == 'build'
18 |
19 | words.each do |word|
20 | next if word == 'and'
21 |
22 | build_part1 if word == 'part1'
23 | build_part2 if word == 'part2'
24 | build_part3 if word == 'part3'
25 | end
26 | end
27 |
28 | end
29 |
30 | # 2. type builder implementations
31 |
32 | class ComputerBuilder < ProductBuilder
33 | def build_part1
34 | puts 'Building part1: motherboard'
35 | end
36 |
37 | def build_part2
38 | puts 'Building part2: CPU'
39 | end
40 |
41 | def build_part3
42 | puts 'Building part3: display'
43 | end
44 | end
45 |
46 | class TableBuilder < ProductBuilder
47 | def build_part1
48 | puts 'Building part1: legs'
49 | end
50 |
51 | def build_part2
52 | puts 'Building part2: top'
53 | end
54 |
55 | def build_part3
56 | puts 'Building part3: mounting'
57 | end
58 | end
59 |
60 | # 3. director
61 |
62 | class Director
63 | def construct_with_magic(builder)
64 | builder.build_part1_and_part2_and_part3
65 | end
66 | end
67 |
68 | # 4. test
69 |
70 | director = Director.new
71 |
72 | puts 'with magic:'
73 | director.construct_with_magic(ComputerBuilder.new)
74 | director.construct_with_magic(TableBuilder.new)
75 |
--------------------------------------------------------------------------------
/creational/factory-method.rb:
--------------------------------------------------------------------------------
1 | # factory-method.rb
2 |
3 | # Defines an interface for creating an object, but let subclasses decide which
4 | # class to instantiate. Lets a class to defer instantiation to subclasses.
5 |
6 | # 1. common type interface
7 |
8 | class Shape
9 | def draw; end
10 | end
11 |
12 | # 2. implementations of common type interface
13 |
14 | class Line < Shape
15 | def draw
16 | puts 'line'
17 | end
18 | end
19 |
20 | class Square < Shape
21 | def draw
22 | puts 'square'
23 | end
24 | end
25 |
26 | # 3. Creator class for various types
27 |
28 | class ShapeCreator
29 |
30 | # factory methods
31 |
32 | def create_line
33 | Line.new
34 | end
35 |
36 | def create_square
37 | Square.new
38 | end
39 |
40 | # or universal factory method (parameterized factory method)
41 |
42 | def create_shape(type)
43 | if type == :line
44 | Line.new
45 | elsif type == :square
46 | Square.new
47 | end
48 | end
49 |
50 | end
51 |
52 | # 4. test
53 |
54 | shape_creator = ShapeCreator.new
55 |
56 | shape1 = shape_creator.create_line
57 | shape2 = shape_creator.create_square
58 | shape3 = shape_creator.create_shape :line
59 |
60 | shape1.draw
61 | shape2.draw
62 | shape3.draw
63 |
64 |
--------------------------------------------------------------------------------
/creational/lazy-initialization.rb:
--------------------------------------------------------------------------------
1 | # lazy-initialization.rb
2 |
3 | # Lazy initialization is the tactic of delaying the creation of an object,
4 | # the calculation of a value, or some other expensive process until
5 | # the first time it is needed.
6 |
7 | class Item
8 | def operation
9 | puts 'executing operation...'
10 | end
11 | end
12 |
13 | class ItemProxy < Item
14 | def self.creational_block(&creation_block)
15 | @@creation_block = creation_block
16 | end
17 |
18 | def operation
19 | @subject.operation
20 | end
21 |
22 | def self.subject
23 | if @subject.nil?
24 | puts 'Creating new instance'
25 | else
26 | puts 'Using existing instance'
27 | end
28 |
29 | @subject || (@subject = @@creation_block.call)
30 | end
31 | end
32 |
33 |
34 | # test
35 |
36 | ItemProxy.creational_block { Item.new }
37 |
38 | item1 = ItemProxy.subject
39 |
40 | item2 = ItemProxy.subject
41 |
42 | item3 = ItemProxy.subject
43 |
44 | item1.operation
45 | item2.operation
46 | item3.operation
47 |
--------------------------------------------------------------------------------
/creational/prototype.rb:
--------------------------------------------------------------------------------
1 | # prototype.rb
2 |
3 | # Specify the kind of objects to create using prototypical instance,
4 | # and create new objects by copying this prototype.
5 |
6 | class Prototype
7 | def make_prototype
8 | end
9 | end
10 |
11 | class Product < Prototype
12 | attr_accessor :name
13 |
14 | def initialize
15 | @name = 'name'
16 | end
17 |
18 | def make_prototype
19 | cloned_product = Product.new
20 |
21 | cloned_product.name = @name
22 |
23 | cloned_product
24 | end
25 |
26 | def to_s
27 | super + ", name: #{@name}"
28 | end
29 | end
30 |
31 | # test
32 |
33 | original_product = Product.new
34 |
35 | cloned_product = original_product.make_prototype
36 |
37 | puts "original product: #{original_product}"
38 | puts "cloned product : #{cloned_product}"
39 |
--------------------------------------------------------------------------------
/creational/singleton1.rb:
--------------------------------------------------------------------------------
1 | # singleton1.rb
2 |
3 | # Ensure a class only has one instance and provide a global point of access to it.
4 |
5 | class MySingleton
6 | @@instance = nil
7 |
8 | def self.instance()
9 | if @@instance == nil
10 | public_class_method :new # enables instance creation for this class
11 |
12 | @@instance = new unless @@instance
13 |
14 | private_class_method :new # disable instance creation for this class
15 | end
16 |
17 | @@instance
18 | end
19 |
20 | private_class_method :new # disable instance creation for this class
21 | end
22 |
23 |
24 | # MySingleton1.new # error
25 |
26 | puts "singleton: #{MySingleton.instance.to_s}"
27 | puts "singleton: #{MySingleton.instance.to_s}"
28 |
--------------------------------------------------------------------------------
/creational/singleton2.rb:
--------------------------------------------------------------------------------
1 | # singleton2.rb
2 |
3 | # This example does the same with the help of 'singleton' library.
4 |
5 | require 'singleton'
6 |
7 | class MySingleton
8 | include Singleton
9 | end
10 |
11 | puts "singleton: #{MySingleton.instance}"
12 | puts "singleton: #{MySingleton.instance}"
13 |
--------------------------------------------------------------------------------
/creational/singleton3.rb:
--------------------------------------------------------------------------------
1 | # singleton3.rb
2 |
3 | # Usually you have to avoid using Singleton, because it's not possible to mock (and consequently test) it.
4 | #
5 | # This example is trying to replace Singleton pattern with interface inheritance and dependency injection.
6 | # Now we can mock the singleton.
7 |
8 | # 1. Define singleton.
9 |
10 | class Singleton
11 | def operation
12 | end
13 | end
14 |
15 | # 2. Singleton implementation.
16 |
17 | class MySingleton < Singleton
18 | def operation
19 | puts 'operation'
20 | end
21 | end
22 |
23 | # 3. Class that uses the singleton object (we should be able to set it up from outside).
24 |
25 | class SingletonUser
26 | def initialize(singleton)
27 | @singleton = singleton
28 | end
29 |
30 | def do_something
31 | @singleton.operation
32 | end
33 | end
34 |
35 | # 4. Create mock object for the singleton.
36 |
37 | class MockSingleton < Singleton
38 | def operation
39 | puts 'mock operation'
40 | end
41 |
42 | end
43 |
44 |
45 | # 5. test
46 |
47 | class SingletonTest
48 | def test_singleton
49 | singleton = MockSingleton.new
50 |
51 | singleton_user = SingletonUser.new(singleton)
52 |
53 | singleton_user.do_something
54 |
55 | # assertions
56 | end
57 | end
58 |
59 | tester = SingletonTest.new
60 |
61 | tester.test_singleton
62 |
63 |
--------------------------------------------------------------------------------
/enterprise/data-access-object.rb:
--------------------------------------------------------------------------------
1 | # data-access-object.bsh
2 |
3 | # 1.
4 |
5 | class TransferObject
6 | def data(key); end
7 |
8 | def set_data(key, value); end
9 | end
10 |
11 | class TransferObjectDAO
12 | def set_data_source(dataSource); end
13 |
14 | def create; end
15 |
16 | def update(transfer_object); end
17 |
18 | def delete(transfer_object); end
19 |
20 | def find(id); end
21 |
22 | def find_all; end
23 | end
24 |
25 | class DataSource
26 | def generate_next_id; end
27 |
28 | def execute_command(command, param); end
29 | end
30 |
31 | class TransferObjectDAOFactory
32 | def transfer_object_dao; end
33 | end
34 |
35 | # 2.
36 |
37 | class MyTransferObject < TransferObject
38 | def initialize
39 | @data = {}
40 | end
41 |
42 | def data(key)
43 | @data[key]
44 | end
45 |
46 | def set_data(key, value)
47 | @data[key] = value
48 | end
49 |
50 | def to_s
51 | @data.to_s
52 | end
53 | end
54 |
55 | class MyDataSource < DataSource
56 | def initialize
57 | @generated_id = 0
58 |
59 | @objects = []
60 | end
61 |
62 | def generate_next_id
63 | @generated_id += 1
64 | end
65 |
66 | def execute_command(command, param=nil)
67 | result = nil
68 |
69 | if command == :insert
70 | result = (@objects << param)
71 | elsif command == :update
72 | index = @objects.index(param)
73 | @objects.delete_at(index)
74 | @objects.insert(index, param)
75 |
76 | result = true
77 | elsif command == :delete
78 | result = (@objects.delete(param))
79 | elsif command == :find
80 | id = param
81 |
82 | @objects.each do |transfer_object|
83 | current_id = transfer_object.data('id')
84 |
85 | if(current_id == id)
86 | result = transfer_object
87 |
88 | break
89 | end
90 | end
91 | elsif command == :find_all
92 | result = @objects
93 | end
94 |
95 | result
96 | end
97 | end
98 |
99 | class MyTransferObjectDAO < TransferObjectDAO
100 |
101 | attr_writer :data_source
102 |
103 | def create
104 | transfer_object = MyTransferObject.new
105 |
106 | id = @data_source.generate_next_id
107 |
108 | transfer_object.set_data('id', id)
109 |
110 | @data_source.execute_command(:insert, transfer_object)
111 |
112 | id
113 | end
114 |
115 | def update(transfer_object)
116 | @data_source.execute_command(:update, transfer_object)
117 | end
118 |
119 | def delete(transfer_object)
120 | @data_source.execute_command(:delete, transfer_object)
121 | end
122 |
123 | def find(id)
124 | @data_source.execute_command(:find, id)
125 | end
126 |
127 | def find_all
128 | @data_source.execute_command(:find_all)
129 | end
130 | end
131 |
132 | class MyTransferObjectDAOFactory < TransferObjectDAOFactory
133 |
134 | def initialize(data_source)
135 | @data_source = data_source
136 | end
137 |
138 | def transfer_object_dao
139 | transfer_object_dao = MyTransferObjectDAO.new
140 |
141 | transfer_object_dao.data_source = @data_source
142 |
143 | transfer_object_dao
144 | end
145 | end
146 |
147 |
148 | # test - or business object, or client
149 |
150 | data_source = MyDataSource.new
151 | factory = MyTransferObjectDAOFactory.new(data_source)
152 |
153 | dao = factory.transfer_object_dao
154 |
155 | # create a new domain object
156 | new_id = dao.create
157 |
158 | # Find a domain object.
159 | o1 = dao.find(new_id)
160 |
161 | puts "o1: #{o1}"
162 |
163 | # modify the values in the Transfer Object.
164 | o1.set_data('address', 'a1')
165 | o1.set_data('email', 'em1')
166 |
167 | # update the domain object using the DAO
168 | dao.update(o1)
169 |
170 | puts "o1: #{o1}"
171 |
172 | list = dao.find_all
173 |
174 | puts "list before delete: #{list.join(', ')}"
175 |
176 | # delete a domain object
177 | dao.delete(o1)
178 |
179 | puts "list after delete: #{list.join(', ')}"
180 |
--------------------------------------------------------------------------------
/enterprise/dci1.rb:
--------------------------------------------------------------------------------
1 | # dci1.rb
2 | # Data-Context-Interaction pattern
3 |
4 | # The code for separate use case in OO is typically spread out between lots of differen classes.
5 | # To understand how the code works, you must also know the relationships between objects in runtime.
6 | # These relationships aren't set in code, they depend on the situation.
7 | # What DCI proposes is that code for a given use-case is separated out from the classes and put into
8 | # a different artifact called context. Objects of different classes can enter into a relationship in this
9 | # context and take part in interaction where they have different roles.
10 |
11 | # Suggestions:
12 | #
13 | # a) keep core model classes very thin
14 | # b) keep additional logic/behaviour in roles
15 | # c) do not do class oriented programming, do object oriented programming
16 |
17 | # 1. Data
18 | # thin data/model
19 | class DataObject
20 | attr_accessor :basic_property
21 | end
22 |
23 | # 2. Roles: broken into pieces functionality
24 | module Role1
25 | attr_accessor :role1_property
26 | end
27 |
28 | module Role2
29 | attr_accessor :role2_property
30 | end
31 |
32 | # 3. The place where role is assigned to data/model.
33 | # We do it dynamically at run-time, on object, not class declaration level.
34 |
35 | # or use case
36 | class Context
37 | def initialize data
38 | @data = data
39 | end
40 |
41 | def execute
42 | p @data.basic_property # OK
43 |
44 | begin
45 | @data.role1_property
46 | rescue StandardError
47 | p 'Error: role1_property is not available yet'
48 | end
49 |
50 | @data.extend Role1
51 |
52 | @data.role1_property = 'test_role1'
53 | p @data.role1_property
54 | end
55 | end
56 |
57 | # or context
58 | class UseCase
59 | def initialize data
60 | @data = data
61 | end
62 |
63 | def execute
64 | p @data.basic_property # OK
65 |
66 | begin
67 | @data.role2_property
68 | rescue StandardError
69 | p 'Error: role2_property is not available yet'
70 | end
71 |
72 | @data.extend Role2
73 |
74 | @data.role2_property = 'test_role2'
75 | p @data.role2_property
76 | end
77 | end
78 |
79 | # 4. Actual Program
80 |
81 | class Interaction
82 | def initialize
83 | @data = DataObject.new
84 | @data.basic_property = 'test_basic'
85 | end
86 |
87 | def interact
88 | context = Context.new @data
89 | context.execute
90 |
91 | use_case = UseCase.new @data
92 | use_case.execute
93 | end
94 | end
95 |
96 | interaction = Interaction.new
97 | interaction.interact
98 |
--------------------------------------------------------------------------------
/enterprise/dci2.rb:
--------------------------------------------------------------------------------
1 | # dci2.rb
2 | # Data-Context-Interaction pattern
3 |
4 | require 'rubygems'
5 | require 'mixology'
6 |
7 | # 1. Data (or model)
8 | # thin data/model
9 | class DataObject
10 | attr_accessor :basic_property
11 | end
12 |
13 | # 2. Roles: broken into pieces functionality
14 | module Role1
15 | attr_accessor :role1_property
16 | end
17 |
18 | module Role2
19 | attr_accessor :role2_property
20 | end
21 |
22 | # 3. The place where role is assigned to data/model.
23 | # We do it dynamically at run-time, on object, not class declaration level.
24 |
25 | # or use case (or controller)
26 | class Context
27 | def initialize data
28 | @data = data
29 | end
30 |
31 | def execute
32 | p @data.basic_property # OK
33 |
34 | begin
35 | @data.role1_property
36 | rescue
37 | p 'Error: role1_property is not available yet'
38 | end
39 |
40 | @data.mixin Role1
41 |
42 | @data.role1_property = 'test_role1'
43 | p @data.role1_property
44 |
45 | @data.unmix Role1
46 |
47 | begin
48 | @data.role1_property
49 | rescue
50 | p 'Error: role1_property is not available again'
51 | end
52 |
53 | end
54 | end
55 |
56 | # or context
57 | class UseCase
58 | def initialize data
59 | @data = data
60 | end
61 |
62 | def execute
63 | p @data.basic_property # OK
64 |
65 | begin
66 | @data.role2_property
67 | rescue
68 | p 'Error: role2_property is not available yet'
69 | end
70 |
71 | @data.mixin Role2
72 |
73 | @data.role2_property = 'test_role2'
74 | p @data.role2_property
75 |
76 | @data.unmix Role2
77 |
78 | begin
79 | @data.role2_property
80 | rescue
81 | p 'Error: role2_property is not available again'
82 | end
83 | end
84 | end
85 |
86 | # 4. Actual Program
87 |
88 | class Interaction
89 | def initialize
90 | @data = DataObject.new
91 | @data.basic_property = 'test_basic'
92 | end
93 |
94 | def interact
95 | context = Context.new @data
96 | context.execute
97 |
98 | use_case = UseCase.new @data
99 | use_case.execute
100 | end
101 | end
102 |
103 | interaction = Interaction.new
104 | interaction.interact
105 |
--------------------------------------------------------------------------------
/enterprise/dci3.rb:
--------------------------------------------------------------------------------
1 | # dci3.rb
2 | # Data-Context-Interaction pattern example based on account transfer money
3 |
4 | # 1. Data
5 |
6 | class Account
7 | attr_accessor :balance
8 |
9 | def initialize balance
10 | @balance = balance
11 | end
12 |
13 | def decrease_balance(amount)
14 | @balance -= amount
15 | end
16 |
17 | def increase_balance(amount)
18 | @balance += amount
19 | end
20 |
21 | def update_log(message, amount)
22 | puts "#{message} : #{amount}"
23 | end
24 | end
25 |
26 | # 2. Helper mixins to support context
27 |
28 | module ContextAccessor
29 | def context
30 | Thread.current[:context]
31 | end
32 | end
33 |
34 | module Context
35 | include ContextAccessor
36 |
37 | def context=(ctx)
38 | Thread.current[:context] = ctx
39 | end
40 |
41 | def in_context
42 | old_context = self.context
43 | self.context = self
44 |
45 | res = yield
46 |
47 | self.context = old_context
48 |
49 | res
50 | end
51 | end
52 |
53 | # 3. Role(s)
54 |
55 | module SourceAccount
56 | include ContextAccessor
57 |
58 | def transfer_out amount
59 | raise 'Insufficient funds' if balance < amount
60 |
61 | decrease_balance amount
62 |
63 | context.destination_account.transfer_in amount
64 |
65 | update_log 'Transferred out', amount
66 | end
67 | end
68 |
69 | module DestinationAccount
70 | include ContextAccessor
71 |
72 | def transfer_in amount
73 | increase_balance amount
74 |
75 | update_log 'Transferred in', amount
76 | end
77 | end
78 |
79 | # 4. Context
80 | # The place where role is assigned to data/model.
81 | # We do it dynamically at run-time, on object, not class declaration level.
82 |
83 | class TransferringMoney
84 | include Context
85 |
86 | attr_reader :source_account, :destination_account
87 |
88 | def initialize source_account, destination_account
89 | @source_account = source_account.extend SourceAccount
90 | @destination_account = destination_account.extend DestinationAccount
91 | end
92 |
93 | def transfer amount
94 | in_context do
95 | source_account.transfer_out amount
96 | end
97 | end
98 | end
99 |
100 | # 5. Actual Program
101 |
102 | class Interaction
103 | attr_reader :source, :destination
104 |
105 | def initialize
106 | @source = Account.new 57
107 | @destination = Account.new 124
108 | end
109 |
110 | def interact
111 | context = TransferringMoney.new(source, destination)
112 |
113 | puts 'Before:'
114 | puts "Source balance: #{source.balance}"
115 | puts "Destination balance: #{destination.balance}"
116 |
117 | context.transfer 12
118 |
119 | puts 'After:'
120 | puts "Source balance: #{source.balance}"
121 | puts "Destination balance: #{destination.balance}"
122 | end
123 | end
124 |
125 | interaction = Interaction.new
126 | interaction.interact
127 |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/enterprise/dsl11.rb:
--------------------------------------------------------------------------------
1 | # dsl11.rb
2 |
3 | # Domain Specific Language (DSL) is simple set of commands for serving a specific domain.
4 | # It could be implemented on object or class level. With object level we've got DSL scripts, with
5 | # the class level - meta-programming statements
6 |
7 | # This implementation uses object level.
8 |
9 | # 1. Defines DSL elements: tree, trunk, branch, root, leaf.
10 | # If one element appears inside another, use collection and don't forget to eval
11 | # incoming block for the parent.
12 |
13 | module HasName
14 | attr_reader :name
15 |
16 | def initialize(name = '')
17 | @name = name
18 | end
19 |
20 | def to_s
21 | "[name: #{name}]"
22 | end
23 | end
24 |
25 | class Leaf
26 | include HasName
27 |
28 | def to_s
29 | "Leaf[name: #{name}]"
30 | end
31 | end
32 |
33 | class Branch
34 | include HasName
35 |
36 | attr_reader :leaves
37 |
38 | def initialize name
39 | super
40 |
41 | @leaves = []
42 | end
43 |
44 | def leaf(name, &block)
45 | leaf = Leaf.new(name)
46 | leaf.instance_eval(&block) if block_given?
47 |
48 | @leaves << leaf
49 |
50 | leaf
51 | end
52 |
53 | def to_s
54 | "Branch[name: #{name}; leaves: #{leaves.join(', ')}]"
55 | end
56 | end
57 |
58 | class Root
59 | include HasName
60 |
61 | def to_s
62 | "Root[name: #{name}]"
63 | end
64 | end
65 |
66 | class Trunk
67 | include HasName
68 |
69 | attr_reader :branches, :roots
70 |
71 | def initialize
72 | super
73 |
74 | @branches = []
75 | @roots = []
76 | end
77 |
78 | def branch(name, &block)
79 | branch = Branch.new(name)
80 | branch.instance_eval(&block) if block_given?
81 |
82 | @branches << branch
83 |
84 | branch
85 | end
86 |
87 | def root(name, &block)
88 | root = Root.new(name)
89 | root.instance_eval(&block) if block_given?
90 |
91 | @roots << root
92 |
93 | root
94 | end
95 |
96 | def to_s
97 | " Trunk[\n" +
98 | " branches: #{branches.join(', ')};\n" +
99 | " roots: #{roots.join(', ')};\n" +
100 | " ]"
101 | end
102 | end
103 |
104 | class Tree
105 | include HasName
106 |
107 | def initialize name
108 | super
109 | @trunk = Trunk.new
110 | end
111 |
112 | def type(type)
113 | @type = type
114 | end
115 |
116 | def trunk(&block)
117 | @trunk.instance_eval(&block)
118 | end
119 |
120 | def to_s
121 | "Tree [\n name: #{name};\n type: #{@type};\n trunk: #{@trunk}\n]"
122 | end
123 | end
124 |
125 | # 2. Defines DSL for describing the tree. It acts as the tree builder.
126 |
127 | module Tree::DSL
128 | def tree(name, &block)
129 | tree = Tree.new(name)
130 | tree.instance_eval(&block)
131 |
132 | tree
133 | end
134 | end
135 |
136 | # 3. test
137 |
138 | include Tree::DSL
139 |
140 | # tree description
141 |
142 | tree :my_favorite_tree do
143 | type :oak
144 |
145 | trunk do
146 | branch :b1 do
147 | leaf :l1
148 | leaf :l2
149 | end
150 |
151 | branch :b2 do
152 | leaf :l3
153 | end
154 |
155 | root :r1
156 | root :r2
157 | end
158 |
159 | puts self # prints the tree
160 | end
161 |
--------------------------------------------------------------------------------
/enterprise/dsl12.rb:
--------------------------------------------------------------------------------
1 | # dsl12.rb
2 |
3 | # This is another implementation of DSL with the help of DSL build.
4 |
5 | module Kernel
6 | def singleton_class
7 | class << self;
8 | self;
9 | end
10 | end
11 |
12 | def define_attribute(name, value=nil)
13 | unless instance_variable_defined?("@#{name}".to_sym)
14 | singleton_class.send :attr_accessor, name
15 |
16 | send "#{name}=".to_sym, value unless value.nil?
17 | end
18 |
19 | instance_variable_get "@#{name}".to_sym
20 | end
21 |
22 | def constantize name
23 | Kernel.const_get(name.to_s.capitalize)
24 | end
25 | end
26 |
27 |
28 | module Visitable
29 | def accept(level, &visitor_code)
30 | visitor_code.call(level, self)
31 | end
32 | end
33 |
34 | module HasName
35 | attr_accessor :name
36 | end
37 |
38 | # 1. Defines DSL elements: tree, trunk, branch, root, leaf. No need to specify
39 | # relationship between them - we'll do it later with DSL builder.
40 |
41 | class TreeElement
42 | include HasName
43 | end
44 |
45 | class Leaf < TreeElement
46 | end
47 |
48 | class Branch < TreeElement
49 | end
50 |
51 | class Root < TreeElement
52 | end
53 |
54 | class Trunk < TreeElement
55 | end
56 |
57 | class Tree < TreeElement
58 | def type(type=nil)
59 | @type = type.nil? ? @type : type
60 | end
61 | end
62 |
63 |
64 | # 2. DSL Builder
65 |
66 | class DSLBuilder
67 | def initialize &block
68 | @nodes = []
69 | @records = []
70 |
71 | instance_eval(&block)
72 | end
73 |
74 | def parent
75 | @parent
76 | end
77 |
78 | def add_node node
79 | @nodes << node unless @nodes.include? node
80 | end
81 |
82 | def parent parent
83 | @parent = parent
84 | add_node(parent)
85 |
86 | @records << {root: parent}
87 |
88 | parent_class = constantize(parent)
89 |
90 | parent_class.instance_eval do
91 | define_method parent do |name, &block|
92 | instance = parent_class.new(name)
93 |
94 | instance.instance_eval(&block)
95 | end
96 | end
97 | end
98 |
99 | def child(parent, child)
100 | add_node(parent)
101 | add_node(child)
102 |
103 | @records << { parent: parent, child: child }
104 |
105 | parent_class = constantize(parent)
106 | child_class = constantize(child)
107 |
108 | parent_class.instance_eval do
109 | define_method child do |&block|
110 | child = define_attribute(child, child_class.new)
111 |
112 | child.instance_eval(&block) unless block.nil?
113 | end
114 | end
115 | end
116 |
117 | def children(parent, child)
118 | add_node(parent)
119 | add_node(child)
120 |
121 | @records << { parent: parent, children: child }
122 |
123 | parent_class = constantize(parent)
124 | child_class = constantize(child)
125 |
126 | parent_class.instance_eval do
127 | define_method child do |name, &block|
128 | instance = child_class.new
129 | instance.name = name
130 |
131 | children = define_attribute("#{child}_list".to_sym, [])
132 |
133 | children << instance
134 |
135 | instance.instance_eval(&block) unless block.nil?
136 | end
137 | end
138 | end
139 |
140 | def build(name)
141 | language_class = Class.new
142 | language = language_class.new
143 | language.define_attribute(:parent, @parent)
144 | language.define_attribute(:name, name)
145 |
146 | def language.program(name, &block)
147 | instance = constantize(@parent).new
148 | instance.name = name
149 |
150 | instance.instance_eval(&block)
151 |
152 | instance
153 | end
154 |
155 | assign_visitors
156 |
157 | language
158 | end
159 |
160 | private
161 |
162 |
163 | def assign_visitor_to(element)
164 | element_class = constantize(element)
165 | element_class.send :include, Visitable unless element_class.include? Visitable
166 | end
167 |
168 | def override_accept_method(element, components_patch)
169 | element_class = constantize(element)
170 |
171 | element_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
172 | alias original_accept accept
173 |
174 | def accept(level, &visitor_code)
175 | original_accept level, &visitor_code
176 |
177 | # takes care of components
178 | #{components_patch}
179 | end
180 | CODE
181 | end
182 |
183 | def assign_visitors
184 | @nodes.each do |node|
185 | assign_visitor_to(node)
186 |
187 | children_records = @records.select { |record| record[:parent] == node && !record[:children].nil? }
188 |
189 | unless children_records.empty?
190 | patch = ''
191 | children_records.each do |record|
192 | child = record[:children]
193 |
194 | patch += <<-CODE
195 | @#{child}_list.each do |visitable|
196 | visitable.accept(level+1, &visitor_code)
197 | end
198 | CODE
199 | end
200 |
201 | override_accept_method(node, patch)
202 | end
203 |
204 | child_records = @records.select { |record| record[:parent] == node && !record[:child].nil? }
205 |
206 | unless child_records.empty?
207 | child = child_records[0][:child]
208 |
209 | patch = "@#{child}.accept(level+1, &visitor_code)"
210 |
211 | override_accept_method(node, patch)
212 | end
213 | end
214 | end
215 | end
216 |
217 |
218 | # 3. Creates new 'tree' language with the help of DSL builder.
219 |
220 | dsl_builder = DSLBuilder.new do
221 | parent :tree
222 |
223 | child :tree, :trunk
224 | children :trunk, :branch
225 | children :trunk, :root
226 | children :branch, :leaf
227 | end
228 |
229 | tree_language = dsl_builder.build :tree
230 |
231 | puts "language: #{tree_language.name}"
232 |
233 | # 4. Creates new tree description in 'tree' language.
234 |
235 | tree_program = tree_language.program :my_favorite_tree do
236 | type :oak
237 |
238 | trunk do
239 | branch :b1 do
240 | leaf :l1
241 | leaf :l2
242 | end
243 |
244 | branch :b2 do
245 | leaf :l3
246 | end
247 |
248 | root :r1
249 | root :r2
250 | end
251 | end
252 |
253 | # printing the program
254 |
255 | puts "program: #{tree_program.name}"
256 | puts 'program body:'
257 |
258 | # visiting all program's elements
259 |
260 |
261 | tree_program.accept(1) do |level, visitable|
262 | spaces = ' ' * ((level-1)*2)
263 | case visitable
264 | when Tree
265 | puts "#{spaces}Tree[name: #{visitable.name}; type: #{visitable.type}]"
266 | when Trunk
267 | puts "#{spaces}Trunk"
268 | when Branch
269 | puts "#{spaces}Branch[name: #{visitable.name}]"
270 | when Root
271 | puts "#{spaces}Root[name: #{visitable.name}]"
272 | when Leaf
273 | puts "#{spaces}Leaf[name: #{visitable.name}]"
274 | else
275 | puts "oops: #{visitable.class.name}"
276 | end
277 | end
278 |
279 |
--------------------------------------------------------------------------------
/enterprise/dsl13.rb:
--------------------------------------------------------------------------------
1 | # http://www.skorks.com/2011/02/a-unit-testing-framework-in-44-lines-of-ruby/
2 |
3 | module Kernel
4 | def describe(description, &block)
5 | tests = Dsl.new.parse(description, block)
6 | tests.execute
7 | end
8 | end
9 |
10 | class Object
11 | def should
12 | self
13 | end
14 | end
15 |
16 | class Dsl
17 | def initialize
18 | @tests = {}
19 | end
20 |
21 | def parse(description, block)
22 | self.instance_eval(&block)
23 | Executor.new(description, @tests)
24 | end
25 |
26 | def it(description, &block)
27 | @tests[description] = block
28 | end
29 | end
30 |
31 | class Executor
32 | def initialize(description, tests)
33 | @description = description
34 | @tests = tests
35 | @success_count = 0
36 | @failure_count = 0
37 | end
38 |
39 | def execute
40 | puts @description
41 | @tests.each_pair do |name, block|
42 | print " - #{name}"
43 | result = self.instance_eval(&block)
44 | result ? @success_count += 1 : @failure_count += 1
45 | puts result ? ' SUCCESS' : ' FAILURE'
46 | end
47 | summary
48 | end
49 |
50 | def summary
51 | puts "\n#{@tests.keys.size} tests, #{@success_count} success, #{@failure_count} failure"
52 | end
53 | end
54 |
55 | describe 'some test' do
56 | it 'should be true' do
57 | true.should == true
58 | end
59 |
60 | it 'should show that an expression can be true' do
61 | (5 == 5).should == true
62 | end
63 |
64 | it 'should be failing deliberately' do
65 | 5.should == 6
66 | end
67 | end
--------------------------------------------------------------------------------
/enterprise/dsl2.rb:
--------------------------------------------------------------------------------
1 | # dsl2.rb
2 |
3 | # This implementation uses class level.
4 |
5 | class Object
6 | def self.property_creator(*names)
7 | names.each do |name|
8 | class_eval <<-CODE, __FILE__, __LINE__ + 1
9 | def #{name}
10 | @#{name}
11 | end
12 |
13 | def #{name}=(val)
14 | @#{name} = val
15 | end
16 | CODE
17 | end
18 | end
19 | end
20 |
21 | class Bean
22 | property_creator :prop1, :prop2
23 |
24 | def initialize(prop1, prop2)
25 | @prop1 = prop1
26 | @prop2 = prop2
27 | end
28 | end
29 |
30 | bean = Bean.new('red', 'morning')
31 |
32 | puts bean.prop1
33 | puts bean.prop2
34 |
--------------------------------------------------------------------------------
/enterprise/hexagonal.rb:
--------------------------------------------------------------------------------
1 | # 1. Have some database implementation (storage device)
2 |
3 | class SomeDatabase
4 |
5 | def save(model)
6 | puts "Model #{model} saved."
7 | end
8 |
9 | end
10 |
11 | # 2. Have some Web UI implementation (interaction device)
12 |
13 | class WebUI
14 | def good_request
15 | 'some_name'
16 | end
17 |
18 | def bad_request
19 | ''
20 | end
21 | end
22 |
23 | # 3. Create model
24 |
25 | class SomeModel
26 |
27 | # put some attributes here
28 |
29 | attr_accessor :name
30 |
31 | def initialize name
32 | @name = name
33 | end
34 |
35 | def valid?
36 | not name.nil? and !name.strip.empty?
37 | end
38 |
39 | def to_s
40 | name.nil? ? 'unknown' : name
41 | end
42 | end
43 |
44 | # 4. Create repository for the model
45 | # - decouples model from storage device (database)
46 |
47 | class SomeModelRepository
48 |
49 | def save model
50 | # save model in database
51 | SomeDatabase.new.save model
52 | end
53 |
54 | end
55 |
56 | # 5. Create Use Case Service (coordinator)
57 | # - decouples model from controller
58 | # - has business/decision logic
59 | # - should not have any computation or state management
60 |
61 | class SomeUseCaseService
62 | attr_reader :repository, :controller
63 |
64 | def initialize(repository, controller)
65 | @repository = repository
66 | @controller = controller
67 | end
68 |
69 | def create_some_model params
70 | model = SomeModel.new params[:name]
71 |
72 | if model.valid? # decision logic
73 | repository.save(model)
74 | controller.model_creation_succeeded model
75 | else
76 | controller.model_creation_failed model
77 | end
78 | end
79 | end
80 |
81 | # 6. Create controller
82 | # - decouples model from interaction device (Web UI)
83 | # - should not contain business logic (moved to use case service)
84 | # - could have more than one use case service (1-n)
85 | # - acts as listener for use case service: has callback methods on success or failure
86 |
87 | class SomeController
88 |
89 | def create_some_model(params)
90 | # 1. Create repository
91 | repository = SomeModelRepository.new
92 |
93 | # 2. Create use case service
94 | service = SomeUseCaseService.new repository, self
95 |
96 | # 3. Create model
97 | service.create_some_model params
98 | end
99 |
100 | # successful path
101 | def model_creation_succeeded(model)
102 | puts "Model #{model} was created successfully."
103 | end
104 |
105 | # failure path
106 | def model_creation_failed(model)
107 | puts "Model #{model} creation failed."
108 | end
109 |
110 | end
111 |
112 | # 7. Code example
113 |
114 | controller = SomeController.new
115 |
116 | # Successful model creation
117 | controller.create_some_model({name: WebUI.new.good_request})
118 |
119 | # Failed model creation
120 | controller.create_some_model({name: WebUI.new.bad_request})
121 |
--------------------------------------------------------------------------------
/enterprise/map-reduce.rb:
--------------------------------------------------------------------------------
1 | # map-reduce.bsh
2 |
3 | # http://www.theserverside.com/tt/knowledgecenter-tc/knowledgecenter-tc.tss?l=MapReduce
4 | # http://www.theserverside.com/tt/articles/article.tss?l=MapReduceRedux
5 |
6 | # MapReduce is a distributed programming model intended for processing massive amounts of data in large clusters,
7 | # developed by Jeffrey Dean and Sanjay Ghemawat at Google. The implementation of MapReduce separates the business
8 | # logic from the multi-processing logic.
9 | #
10 | # MapReduce is implemented as two functions, Map which applies a function to all the members of a collection and
11 | # returns a list of results based on that processing, and Reduce, which collates and resolves the results from
12 | # two or more Maps executed in parallel by multiple threads, processors, or stand-alone systems. Both Map()
13 | # and Reduce() may run in parallel, though not necessarily in the same system at the same time.
14 |
15 | # 1. functors interfaces
16 |
17 | class MapFunctor
18 | def function(object); end
19 | end
20 |
21 | class ReduceFunctor
22 | def function(list, object); end
23 | end
24 |
25 | # 2. map-reduce interface
26 |
27 | class MapReduce
28 | def map(map_functor, list); end
29 |
30 | def reduce(reduce_functor, list, init_list); end
31 | end
32 |
33 |
34 | # 3. implementations
35 |
36 | class MapFunctorImpl < MapFunctor
37 | # copier
38 | def function(object)
39 | object
40 | end
41 | end
42 |
43 | class ReduceFunctorImpl < ReduceFunctor
44 |
45 | # duplication reducer
46 | def function(list, object)
47 | if(!list.include? object)
48 | list << object
49 | end
50 |
51 | list
52 | end
53 |
54 | end
55 |
56 | class MapReduceImpl < MapReduce
57 |
58 | def map(map_functor, list)
59 | intermediate_result = []
60 |
61 | list.each do |element|
62 | result = map_functor.function(element)
63 |
64 | intermediate_result << result
65 | end
66 |
67 | intermediate_result
68 | end
69 |
70 | def reduce(reduce_functor, list, init_list)
71 | result = init_list
72 |
73 | list.each do |value|
74 | result = reduce_functor.function(result, value)
75 | end
76 |
77 | result
78 | end
79 |
80 | end
81 |
82 | # 4. test
83 |
84 | # input data
85 |
86 | bucket1 = %w[1 2 3 4 5]
87 |
88 | bucket2 = %w[6 4 8 5 10]
89 |
90 | # Business logic is concentrated in functors
91 |
92 | map_functor = MapFunctorImpl.new
93 | reduce_functor = ReduceFunctorImpl.new
94 |
95 | # Different instances of map-reduce objects. They can be used for "map" or "reduce" operations.
96 |
97 | map_reduce1 = MapReduceImpl.new
98 | map_reduce2 = MapReduceImpl.new
99 | map_reduce3 = MapReduceImpl.new
100 |
101 | intermediate_data1 = map_reduce1.map(map_functor, bucket1)
102 | intermediate_data2 = map_reduce2.map(map_functor, bucket2)
103 |
104 | final_data = map_reduce3.reduce(reduce_functor, intermediate_data2, intermediate_data1)
105 |
106 | puts "bucket1: #{bucket1.join(', ')}"
107 | puts "bucket2: #{bucket2.join(', ')}"
108 |
109 | puts "intermediate data 1: #{intermediate_data1.join(', ')}"
110 | puts "intermediate data 2: #{intermediate_data2.join(', ')}"
111 |
112 | puts "final data: #{final_data.join(', ')}"
113 |
--------------------------------------------------------------------------------
/enterprise/mvc.rb:
--------------------------------------------------------------------------------
1 | # mvc.bsh
2 |
3 | # 1. observer and observable interfaces (see Observer pattern)
4 |
5 | class Observer
6 | def update(key, value); end
7 | end
8 |
9 | # see Observer pattern
10 | class Observable
11 | def add_observer(observer); end
12 |
13 | def remove_observer(observer); end
14 |
15 | def notify_observers(key, value); end
16 | end
17 |
18 | # 2. Model, Controller-Mediator-Observer, View
19 |
20 | class Model
21 | def value(key); end
22 |
23 | def set_data(key, value); end
24 |
25 | def get_observable; end
26 | end
27 |
28 | class Controller < Observer
29 | # Binds a model to this controller. Once added, the controller will listen for all
30 | # model property changes and propagate them on to registered views. In addition,
31 | # it is also responsible for resetting the model properties when a view changes state.
32 | def add_model(model); end
33 |
34 | def remove_model(model); end
35 |
36 | # Binds a view to this controller. The controller will propagate all model property
37 | # changes to each view for consideration.
38 | def add_view(view); end
39 |
40 | def remove_view(view); end
41 |
42 | def models; end
43 |
44 | def views; end
45 |
46 | def operation1(value); end
47 |
48 | def operation2(value); end
49 | end
50 |
51 | class View < Observer
52 | def set_controller(controller); end
53 | end
54 |
55 | # 3. Implementation of observable
56 |
57 | class AbstractObservable < Observable
58 | def initialize
59 | @observers = []
60 | end
61 |
62 | def add_observer(observer)
63 | @observers << observer
64 | end
65 |
66 | def remove_observer(observer)
67 | @observers.delete(observer)
68 | end
69 |
70 | def notify_observers(key, value)
71 | @observers.clone.each { |observer| observer.update(key, value) }
72 | end
73 | end
74 |
75 | # 4. implementations
76 |
77 | class MyModel < Model
78 | def initialize
79 | @observable = AbstractObservable.new
80 | @data = {}
81 | end
82 |
83 | def observable
84 | @observable
85 | end
86 |
87 | # model implementation
88 |
89 | def value(key)
90 | @data[key]
91 | end
92 |
93 | # mutator
94 | def set_data(key, value)
95 | @data[key] = value
96 |
97 | @observable.notify_observers(key, value) # notify about state change
98 | end
99 |
100 | def to_s
101 | "model: #{@data}"
102 | end
103 | end
104 |
105 | # this controller mediates all changes in model and propagates them to
106 | # all registered views through update() method
107 | class AbstractController < Controller
108 | # controller behavior
109 |
110 | def initialize
111 | @models = []
112 | @views = []
113 | end
114 |
115 | def add_model(model)
116 | @models << model
117 |
118 | model.observable.add_observer(self)
119 | end
120 |
121 | def remove_model(model)
122 | @models.remove(model)
123 |
124 | model.observable.remove_observer(self)
125 | end
126 |
127 | def add_view(view)
128 | @views << view
129 |
130 | view.controller = self
131 | end
132 |
133 | def remove_view(view)
134 | @views.remove(view)
135 | end
136 |
137 | def models
138 | @models
139 | end
140 |
141 | def views
142 | @views
143 | end
144 |
145 | # observer behavior
146 |
147 | # This method represents changes model -> views
148 |
149 | # Use this to observe property changes from registered models
150 | # and propagate them on to all registered views.
151 | def update(key, value)
152 | @views.each { |view| view.update(key, value) }
153 | end
154 |
155 | # This method represents changes view -> models
156 |
157 | # Convenience method that subclasses can call upon to fire off property changes
158 | # back to the models. This method used reflection to inspect each of the model
159 | # classes to determine if it is the owner of the property in question. If it
160 | # isn't, a NoSuchMethodException is throws (which the method ignores).
161 | def set_model_property(key, value)
162 | @models.each { |model| model.set_data(key, value) }
163 | end
164 | end
165 |
166 | class MyView < View
167 | def controller=(controller)
168 | @controller = controller
169 | end
170 |
171 | def property1=(value)
172 | @property1 = value
173 |
174 | @controller.operation1(value)
175 | end
176 |
177 | def property2=(value)
178 | @property2 = value
179 |
180 | @controller.operation2(value)
181 | end
182 |
183 | def update(key, value)
184 | if key == :property1
185 | @property1 = value
186 | end
187 |
188 | if key == :property2
189 | @property2 = value
190 | end
191 | end
192 |
193 | def to_s
194 | "view[property1: #{@property1}; property2: #{@property2}]"
195 | end
196 | end
197 |
198 | class MyController < AbstractController
199 | # implementing Mediator
200 |
201 | def operation1(value)
202 | set_model_property(:property1, value)
203 | end
204 |
205 | def operation2(value)
206 | set_model_property(:property2, value)
207 | end
208 | end
209 |
210 | # 5. test
211 |
212 | def print_controller(controller)
213 | i = 1
214 | controller.models.each { |model| puts "model#{i}: #{model}"; i = i+1 }
215 |
216 | i = 1
217 | controller.views.each { |view| puts "view#{i}: #{view}"; i = i+1 }
218 | end
219 |
220 | controller1 = MyController.new
221 |
222 | model1 = MyModel.new
223 |
224 | view11 = MyView.new
225 | view12 = MyView.new
226 |
227 | controller1.add_model(model1)
228 | controller1.add_view(view11)
229 | controller1.add_view(view12)
230 |
231 | controller2 = MyController.new
232 |
233 | model2 = MyModel.new
234 | model3 = MyModel.new
235 |
236 | view21 = MyView.new
237 | view22 = MyView.new
238 | view23 = MyView.new
239 |
240 | controller2.add_model(model2)
241 | controller2.add_model(model3)
242 | controller2.add_view(view21)
243 | controller2.add_view(view22)
244 | controller2.add_view(view23)
245 |
246 | puts '1. changes in model1'
247 |
248 | model1.set_data(:property1, '555')
249 |
250 | print_controller(controller1)
251 |
252 | puts '2. changes in view12'
253 |
254 | view12.property2 = '777'
255 |
256 | print_controller(controller1)
257 |
258 | puts '3. changes in model3'
259 |
260 | model3.set_data(:property1, '111')
261 |
262 | print_controller(controller2)
263 |
264 | puts '4. changes in view23'
265 |
266 | view23.property1 = '222'
267 | view23.property2 = '333'
268 |
269 | print_controller(controller2)
270 |
--------------------------------------------------------------------------------
/meta_tricks/aspect_for_class_and_instance.rb:
--------------------------------------------------------------------------------
1 | # 1. This class has methods that can add aspect to class and instance methods
2 |
3 | class AspectWrapper
4 | def wrap_class_with_aspect(clazz, methods_to_wrap, &aspect)
5 |
6 | handler_class = Class.new {
7 | @clazz = clazz
8 | @methods_to_wrap = methods_to_wrap
9 | }
10 |
11 | handler_class.instance_eval do
12 | methods_to_wrap.each do |method|
13 | define_singleton_method method do |*params|
14 | aspect.call @clazz, method, *params
15 | end
16 | end
17 | end
18 |
19 | handler_class
20 | end
21 |
22 | def wrap_instance_with_aspect(instance, methods_to_wrap, &aspect)
23 | new_module = to_module instance, methods_to_wrap, &aspect
24 |
25 | instance.extend new_module
26 | end
27 |
28 | def to_module(instance, methods_to_wrap, &aspect)
29 | cloned_instance = instance.clone
30 |
31 | Module.new do
32 | methods_to_wrap.each do |method|
33 | define_method method do |*params|
34 | aspect.call cloned_instance, method, *params
35 | end
36 | end
37 | end
38 | end
39 | end
40 |
41 | # 2. Create aspect: block of code that has caller, method_name and method parameters
42 |
43 | my_aspect = lambda do |caller, method_name, *params|
44 | puts 'Before original call'
45 |
46 | caller.send method_name, *params
47 |
48 | puts 'After original call'
49 | end
50 |
51 | # 3. Create new class
52 |
53 | class MyClass
54 | def self.class_method
55 | puts 'Original class method call...'
56 | end
57 |
58 | def instance_method
59 | puts 'Original instance method call...'
60 | end
61 | end
62 |
63 | # Test it
64 |
65 | puts 'Calling class method before applying aspect:'
66 |
67 | MyClass.class_method
68 | puts '---'
69 |
70 | weaved_class = AspectWrapper.new.wrap_class_with_aspect(MyClass, [:class_method], &my_aspect)
71 |
72 | puts 'Calling class method after applying aspect:'
73 |
74 | weaved_class.class_method
75 | puts '---'
76 |
77 | instance = MyClass.new
78 |
79 | puts 'Calling instance method before applying aspect:'
80 | instance.instance_method
81 | puts '---'
82 |
83 | AspectWrapper.new.wrap_instance_with_aspect(instance, [:instance_method], &my_aspect)
84 |
85 | puts 'Calling instance method after applying aspect:'
86 | instance.instance_method
87 | puts '---'
88 |
--------------------------------------------------------------------------------
/meta_tricks/block_as_module.rb:
--------------------------------------------------------------------------------
1 | # How to call method defined in block
2 |
3 | # 1. Have a block with new method
4 |
5 | code_block = lambda do
6 | public
7 |
8 | def my_method
9 | "my_method: #{my_field}" # NOTE: access variable from outer class
10 | end
11 | end
12 |
13 | # 2. Have a class that needs to be extended with new method from block
14 |
15 | class MyClass
16 | attr_reader :my_field
17 |
18 | def initialize(& code_block)
19 | @my_field = 'Hello, World!'
20 |
21 | # 3. This method "inserts" block's method into current class
22 |
23 | inline_block self.class, code_block
24 | end
25 |
26 | def inline_block(clazz, code_block)
27 | clazz.define_singleton_method(:include_new_context, &code_block) # define singleton method associated with block
28 |
29 | clazz.include_new_context # by executing new method we "extending" class body with block body
30 | end
31 | end
32 |
33 | # 4. Test it
34 |
35 | my_instance = MyClass.new &code_block
36 |
37 | puts my_instance.my_method # block's method uses variable defined inside this class
38 |
--------------------------------------------------------------------------------
/meta_tricks/class_field_and_instance_field_and_class_instance_field.rb:
--------------------------------------------------------------------------------
1 | class TestMethods
2 | @class_instance_field = 'value_for_class_instance_field'
3 |
4 | def initialize
5 | @@class_field = 'value_for_class_field'
6 |
7 | @instance_field = 'value_for_instance_field'
8 | end
9 |
10 | def self.class_field
11 | @@class_field
12 | end
13 |
14 | def instance_field
15 | @instance_field
16 | end
17 |
18 | def self.class_instance_field
19 | @class_instance_field
20 | end
21 | end
22 |
23 | # test
24 |
25 | instance = TestMethods.new
26 |
27 | puts TestMethods.class_field
28 |
29 | puts instance.instance_field
30 |
31 | puts TestMethods.class_instance_field
32 |
--------------------------------------------------------------------------------
/meta_tricks/common_method_for_class_and_method.rb:
--------------------------------------------------------------------------------
1 | # 1. This module has method that creates class and instance methods for block of code
2 |
3 | module UseCommonCode
4 | def use_common_code(method_name, &common_code_block)
5 | # define method for the class
6 |
7 | define_singleton_method method_name do
8 | puts 'Inside class method:'
9 | common_code_block.call
10 | end
11 |
12 | # define method for the instance
13 |
14 | send :define_method, method_name do
15 | puts 'Inside instance method:'
16 | common_code_block.call
17 | end
18 | end
19 | end
20 |
21 | # 2. New class
22 |
23 | class MyClass
24 | # 2.1 Include module
25 | extend UseCommonCode
26 |
27 | # 2.2. Define common block of code
28 |
29 | common_code_block = lambda do
30 | "I'm common!"
31 | end
32 |
33 | # 2.3. register block as common
34 | use_common_code :my_method, &common_code_block
35 | end
36 |
37 | # Test it
38 |
39 | puts MyClass.my_method # class method
40 |
41 | my_instance = MyClass.new
42 |
43 | puts my_instance.my_method # instance method
44 |
45 |
--------------------------------------------------------------------------------
/meta_tricks/data_inside_file.rb:
--------------------------------------------------------------------------------
1 |
2 | DATA.each do |line|
3 | first, last, phone, email = line.split('|')
4 | puts <<-EOM
5 | First name: #{first}
6 | Last name: #{last}
7 | Phone: #{phone}
8 | Email: #{email}
9 | EOM
10 | end
11 |
12 | __END__
13 | David|Black|123-456-7890|dblack@...
14 | Someone|Else|321-888-8888|someone@else
--------------------------------------------------------------------------------
/meta_tricks/locals_to_hash.rb:
--------------------------------------------------------------------------------
1 | # 1. This module has method that can convert list of local variables inside binding into hash
2 |
3 | module LocalsToHash
4 | def locals_to_hash(binding, exclusions)
5 | vars = binding.eval('local_variables') - Kernel.local_variables - exclusions.collect(&:to_sym)
6 |
7 | vars.inject({}) do |hash, name|
8 | hash[name] = eval(name.to_s, binding)
9 | hash
10 | end
11 | end
12 |
13 | end
14 |
15 | # 2. New class
16 |
17 | class MyClass
18 | include LocalsToHash
19 |
20 | def test
21 | r1 = 3
22 | r4 = 4
23 |
24 | hash = locals_to_hash binding, %w(hash)
25 |
26 | puts hash
27 | end
28 | end
29 |
30 | # Test it
31 |
32 | instance = MyClass.new
33 |
34 | instance.test
35 |
--------------------------------------------------------------------------------
/meta_tricks/pipeable.rb:
--------------------------------------------------------------------------------
1 | # from https://gist.github.com/petrachi/637f9367404708ec341a
2 |
3 | A = ->(s){ s << 'a' }
4 | B = ->(s){ s << 'b' }
5 | C = ->(s){ s << 'c' }
6 |
7 | str = ''
8 |
9 | class Object
10 | PIPED = ->(*args, arg){ arg.is_a?(Proc) ? arg[PIPED[*args]] : arg }
11 |
12 | def | *args
13 | PIPED.curry[self, *args]
14 | end
15 | end
16 |
17 | puts str | A | B | C
18 |
--------------------------------------------------------------------------------
/meta_tricks/simple_dsl.rb:
--------------------------------------------------------------------------------
1 | class Programs
2 | attr_reader :list
3 |
4 | def initialize
5 | @list = []
6 | end
7 |
8 | def program program
9 | list << program
10 | end
11 | end
12 |
13 | class MyConfig
14 | def name name
15 | @name = name
16 | end
17 |
18 | def os os
19 | @os = os
20 | end
21 |
22 | def memory memory
23 | @memory = memory
24 | end
25 |
26 | def display_size display_size
27 | @display_size = display_size
28 | end
29 |
30 | def hard_drive hard_drive
31 | @hard_drive = hard_drive
32 | end
33 |
34 | def programs &block
35 | @programs ||= Programs.new
36 |
37 | @programs.instance_eval(&block)
38 | end
39 |
40 | def [] property
41 | instance_variable_get("@#{property}".to_sym)
42 | end
43 |
44 | def to_s
45 | "Config: #{@name}"
46 | end
47 | end
48 |
49 | module MyConfig::DSL
50 | def config(&block)
51 | my_config = MyConfig.new
52 |
53 | my_config.instance_eval(&block)
54 |
55 | my_config
56 | end
57 | end
58 |
59 | class MyClass
60 | include MyConfig::DSL
61 |
62 | def test_dsl
63 | mac_config = config do
64 | name :mac_config
65 |
66 | os 'OSX 10.8.5'
67 | memory '8GB'
68 | display_size "27'"
69 | hard_drive '1TB'
70 |
71 | programs do
72 | program 'Finder'
73 | program 'Firefox'
74 | program 'VLC'
75 | end
76 | end
77 |
78 | puts mac_config[:os]
79 | puts mac_config[:memory]
80 |
81 | puts mac_config[:programs].list
82 | end
83 | end
84 |
85 | # test
86 |
87 | instance = MyClass.new
88 |
89 | instance.test_dsl
90 |
91 |
--------------------------------------------------------------------------------
/meta_tricks/utility_module.rb:
--------------------------------------------------------------------------------
1 | module UtilityModule1
2 | extend self
3 |
4 | def some_method
5 | 'some_method1'
6 | end
7 | end
8 |
9 | module UtilityModule2
10 | module_function # this is preferable over extend self
11 |
12 | def some_method
13 | 'some_method2'
14 | end
15 | end
16 |
17 | module UtilityModule3
18 | class << self
19 | def some_method
20 | 'some_method3'
21 | end
22 | end
23 | end
24 |
25 | require 'singleton'
26 |
27 | class UtilityModule4
28 | include Singleton
29 |
30 | def some_method
31 | 'some_method4'
32 | end
33 | end
34 |
35 | # test
36 |
37 | puts UtilityModule1.some_method
38 | puts UtilityModule2.some_method
39 | puts UtilityModule3.some_method
40 | puts UtilityModule4.instance.some_method
41 |
--------------------------------------------------------------------------------
/notes.txt:
--------------------------------------------------------------------------------
1 | Relations:
2 |
3 | 1. Decorator is Adapter with additional pre-, post- and around- code;
4 |
5 | 2. Factory Method is Template Method but applied to object creation instead of class creation.
6 |
7 | Interesting gem:
8 |
9 | https://github.com/chriswailes/filigree
10 |
--------------------------------------------------------------------------------
/problems/monty_hall_problem.rb:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env ruby
2 |
3 | # http://georgedrummond.com/monty-hall-problem-in-ruby/
4 |
5 | NUMBER_OF_GAMES = 1_000_000
6 |
7 | won_staying = 0
8 | won_changing = 0
9 |
10 | def percent(games)
11 | percent = (games.to_f / NUMBER_OF_GAMES) * 100
12 | percent.round(3)
13 | end
14 |
15 | def won_staying?
16 | doors = (1..3).to_a
17 | chosen_door = doors.sample
18 | correct_door = doors.sample
19 |
20 | chosen_door == correct_door
21 | end
22 |
23 | (1..NUMBER_OF_GAMES).each do
24 | if won_staying?
25 | won_staying += 1
26 | else
27 | won_changing += 1
28 | end
29 | end
30 |
31 | puts "Number of runs: #{NUMBER_OF_GAMES}"
32 | puts "Won staying: #{percent won_staying}% (#{won_staying})"
33 | puts "Won changing: #{percent won_changing}% (#{won_changing})"
34 |
--------------------------------------------------------------------------------
/structural/adapter.rb:
--------------------------------------------------------------------------------
1 | # adapter.rb
2 |
3 | # Convert the interface of a class into another interface clients expect.
4 | # Lets classes work together that couldn't otherwise because of incompatible interfaces.
5 |
6 | # 1. existing layout
7 |
8 | class ClientItem
9 | def some_operation
10 | end
11 | end
12 |
13 | class MyClientItem < ClientItem
14 | def some_operation
15 | puts 'some operation'
16 | end
17 | end
18 |
19 | class Client
20 | def process(item)
21 | item.some_operation
22 | end
23 | end
24 |
25 | # 2. class that creates conflict (adaptee): we cannot pass it in client's process() method.
26 |
27 | # adaptee
28 | class OtherItem
29 | def other_operation
30 | puts 'other operation'
31 | end
32 | end
33 |
34 | # 3. adaptation
35 |
36 | # adapter
37 | class OtherItemAdapter < ClientItem
38 | def initialize
39 | @other_item = OtherItem.new
40 | end
41 |
42 | # actual adaptation
43 | def some_operation
44 | @other_item.other_operation
45 | end
46 | end
47 |
48 | # 4. test
49 |
50 | client = Client.new
51 |
52 | client.process(MyClientItem.new) # OK
53 |
54 | # client.process(OtherItem.new) # OtherItem does not conform ClientItem protocol
55 |
56 | client.process(OtherItemAdapter.new) # OtherItem is adapted to ClientItem protocol through adapter
57 |
58 | # here we adapt incompatible interface "on the fly" using ruby ability to dynamically
59 | # add missing method to the class.
60 |
61 | other_item = OtherItem.new
62 |
63 | class << other_item
64 | def some_operation
65 | puts 'some operation for this instance (v1)'
66 | end
67 | end
68 |
69 | client.process(other_item)
70 |
71 | other_item2 = OtherItem.new
72 |
73 | def other_item2.some_operation
74 | puts 'some operation for this instance (v2)'
75 | end
76 |
77 | client.process(other_item2)
78 |
79 | # or adding this method directly to the class by opening it:
80 | class OtherItem
81 | def some_operation
82 | puts 'some operation for this instance (v3)'
83 | end
84 | end
85 |
86 | client.process(OtherItem.new)
87 |
--------------------------------------------------------------------------------
/structural/bridge.rb:
--------------------------------------------------------------------------------
1 | # bridge.rb
2 |
3 | # Decouples an abstraction from it's implementation so that the two can vary independently.
4 |
5 | # 1. implementor
6 |
7 | class Material
8 | def make
9 | end
10 | end
11 |
12 | # 2. concrete implementors
13 |
14 | class MyMaterial1 < Material
15 | def make
16 | puts 'make: my material 1'
17 | end
18 | end
19 |
20 | class MyMaterial2 < Material
21 | def make
22 | puts 'make: my material 2'
23 | end
24 | end
25 |
26 | # 3. abstraction
27 |
28 | class Product
29 | def taste
30 | end
31 | end
32 |
33 | # 4. refined abstraction
34 |
35 | class MyProduct < Product
36 | def initialize(material) # injecting implementor
37 | @material = material
38 | end
39 |
40 | def taste
41 | @material.make
42 | end
43 |
44 | end
45 |
46 | # 5. test (bridge)
47 |
48 | material1 = MyMaterial1.new
49 | material2 = MyMaterial1.new
50 |
51 | product1 = MyProduct.new(material1)
52 | product2 = MyProduct.new(material2)
53 |
54 | product1.taste
55 | product2.taste
56 |
57 |
--------------------------------------------------------------------------------
/structural/composite.rb:
--------------------------------------------------------------------------------
1 | # composite.rb
2 |
3 | # Compose objects into tree structures to represents part-whole hierarchies.
4 | # Lets clients treat individual objects and compositions of objects uniformly.
5 |
6 | # 1. basic elements
7 |
8 | module Component
9 | def operation
10 | puts 'component operation'
11 | end
12 | end
13 |
14 | module Composite
15 | def initialize
16 | @children = []
17 | end
18 |
19 | def <<(component)
20 | @children << component
21 | end
22 |
23 | def >>(other)
24 | @children.delete(other)
25 | end
26 |
27 | def [](index)
28 | @children[index]
29 | end
30 |
31 | def []=(index, value)
32 | @children[index] = value
33 | end
34 |
35 | def operation
36 | puts 'composite operation'
37 |
38 | @children.each(&:operation)
39 | end
40 |
41 | def to_s
42 | "children: #{@children.join(', ')}"
43 | end
44 | end
45 |
46 |
47 | # 2. implementations
48 |
49 | class Leaf
50 | include Component
51 |
52 | def initialize(name)
53 | @name = name
54 | end
55 |
56 | def to_s
57 | "leaf[name: #{@name}]"
58 | end
59 | end
60 |
61 | class Tree
62 | include Component
63 | include Composite
64 |
65 | def initialize(name)
66 | super()
67 | @name = name
68 | end
69 |
70 | def to_s
71 | "tree[name: #{@name}; children: #{@children.join(', ')}]"
72 | end
73 | end
74 |
75 | # 3. test
76 |
77 | tree1 = Tree.new('t1')
78 | tree2 = Tree.new('t2')
79 |
80 | leaf1 = Leaf.new('l1')
81 | leaf2 = Leaf.new('l2')
82 | leaf3 = Leaf.new('l3')
83 |
84 | tree1 << tree2
85 | tree1 << leaf1
86 | tree1 << leaf2
87 |
88 | tree2 << leaf3
89 |
90 | puts "Hierarchy: \n#{tree1}"
91 | puts '-------'
92 |
93 | puts "Second element: #{tree1[1]}"
94 | puts '-------'
95 |
96 | tree1[1] = Leaf.new('l4')
97 | puts "Set second element to: #{tree1[1]}"
98 | puts '-------'
99 |
100 | tree1 >> leaf1
101 |
102 | puts "Hierarchy: \n#{tree1}"
103 | puts '-------'
104 |
105 | puts "Operation on composite: \n"
106 |
107 | tree1.operation
108 | puts '-------'
109 |
--------------------------------------------------------------------------------
/structural/decorator1.rb:
--------------------------------------------------------------------------------
1 | # decorator1.rb
2 |
3 | # Attaches additional responsibilities to an object dynamically.
4 | # Provides a flexible alternative to subclassing for extending functionality.
5 |
6 | # 1. type interface
7 |
8 | class Component
9 | def operation
10 | end
11 | end
12 |
13 | # 2. type implementation
14 |
15 | class ConcreteComponent < Component
16 | def operation
17 | puts 'my component'
18 | end
19 | end
20 |
21 | # 3a. traditional solution based on inheritance
22 |
23 | class TraditionalComponent < ConcreteComponent
24 | def pre_decoration
25 | puts 'my component pre decoration'
26 | end
27 |
28 | def post_decoration
29 | puts 'my component post decoration'
30 | end
31 |
32 | def operation
33 | pre_decoration
34 |
35 | super
36 |
37 | post_decoration
38 | end
39 | end
40 |
41 | # 3b. solution based on decorator
42 |
43 | class Decorator < Component
44 | def initialize(component)
45 | @component = component
46 | end
47 |
48 | def pre_decoration
49 | puts 'my component pre decoration'
50 | end
51 |
52 | def post_decoration
53 | puts 'my component post decoration'
54 | end
55 |
56 | def operation
57 | pre_decoration
58 |
59 | @component.operation
60 |
61 | post_decoration
62 | end
63 | end
64 |
65 |
66 | # 4. test
67 |
68 | component1 = TraditionalComponent.new
69 | component1.operation
70 | puts '-------'
71 |
72 | component2 = Decorator.new(ConcreteComponent.new)
73 | component2.operation
74 | puts '-------'
75 |
76 | component4 = ConcreteComponent.new
77 |
78 | class << component4
79 | alias old_operation operation
80 |
81 | def operation
82 | puts 'my component pre decoration'
83 |
84 | old_operation
85 |
86 | puts 'my component post decoration'
87 | end
88 | end
89 |
90 | component4.operation
91 | puts '-------'
92 |
--------------------------------------------------------------------------------
/structural/decorator2.rb:
--------------------------------------------------------------------------------
1 | # decorator2.rb
2 |
3 | # Attaches additional responsibilities to an object dynamically.
4 | # Provides a flexible alternative to subclassing for extending functionality.
5 |
6 | # 1. type interface
7 |
8 | class Component
9 | def operation
10 | end
11 | end
12 |
13 | # 2. type implementation
14 |
15 | class ConcreteComponent < Component
16 | def operation
17 | puts 'my component'
18 | end
19 | end
20 |
21 | # 3. This solution uses 'forwardable' library
22 |
23 | require 'forwardable'
24 |
25 | class ForwardableDecorator
26 | extend Forwardable
27 |
28 | def_delegators :@component, :operation
29 |
30 | def initialize(component)
31 | @component = component
32 | end
33 | end
34 |
35 | # 4. test
36 |
37 | component = ForwardableDecorator.new(ConcreteComponent.new)
38 | component.operation
39 | puts '-------'
40 |
--------------------------------------------------------------------------------
/structural/decorator3.rb:
--------------------------------------------------------------------------------
1 | # decorator3.rb
2 |
3 | # This example changes original method behavior for the instance with the help of ruby "alias".
4 |
5 | # 1. type interface
6 |
7 | class Component
8 | def operation
9 | end
10 | end
11 |
12 | # 2. type implementation
13 |
14 | class ConcreteComponent < Component
15 | def operation
16 | puts 'my component'
17 | end
18 | end
19 |
20 |
21 | # 3. test
22 |
23 | component = ConcreteComponent.new
24 |
25 | class << component
26 | alias old_operation operation
27 |
28 | def operation
29 | puts 'my component pre decoration'
30 |
31 | old_operation
32 |
33 | puts 'my component post decoration'
34 | end
35 | end
36 |
37 | component.operation
38 | puts '-------'
39 |
--------------------------------------------------------------------------------
/structural/facade.rb:
--------------------------------------------------------------------------------
1 | # facade.rb
2 |
3 | # Provides an unified interface to a set of interfaces in a subsystem.
4 | # Defines a high-level interface that makes the subsystem easier to use.
5 |
6 | # 1. different parts of the system
7 |
8 | class Component1
9 | def operation1
10 | puts 'operation1'
11 | end
12 | end
13 |
14 | class Component2
15 | def operation2
16 | puts 'operation2'
17 | end
18 | end
19 |
20 | class Component3
21 | def operation3
22 | puts 'operation3'
23 | end
24 | end
25 |
26 | # 2. facade to different parts; end user will communicate with them through the facade only.
27 |
28 | class MyFacade
29 | def do_something
30 | component1 = Component1.new
31 | component2 = Component2.new
32 | component3 = Component3.new
33 |
34 | component1.operation1
35 | component3.operation3
36 | component2.operation2
37 | end
38 | end
39 |
40 | # 3. test
41 |
42 | facade = MyFacade.new
43 |
44 | facade.do_something
45 |
--------------------------------------------------------------------------------
/structural/flyweight.rb:
--------------------------------------------------------------------------------
1 | # flyweight.bsh
2 |
3 | # Uses sharing to support large numbers of fine grained objects efficiently.
4 |
5 | # 1. type interface
6 |
7 | class Item
8 | def process
9 | end
10 | end
11 |
12 | # 2. type implementations
13 |
14 | class MyItem1 < Item
15 | def process
16 | puts 'my item1 process'
17 | end
18 | end
19 |
20 | class MyItem2 < Item
21 | def process
22 | puts 'my item2 process'
23 | end
24 | end
25 |
26 | # 3. type factory; some kind of cache
27 |
28 | class ItemCache
29 | def initialize
30 | @item1 = MyItem1.new
31 | @item2 = MyItem2.new
32 | end
33 |
34 | def item(type)
35 | if type == 1
36 | @item1
37 | elsif type == 2
38 | @item2
39 | end
40 | end
41 | end
42 |
43 | # 4. test
44 |
45 | cache = ItemCache.new
46 |
47 | items = [
48 | cache.item(1),
49 | cache.item(2),
50 | cache.item(2),
51 | cache.item(2),
52 | cache.item(1),
53 | cache.item(2),
54 | cache.item(1),
55 | cache.item(2)
56 | ]
57 |
58 | items.each(&:process)
59 |
--------------------------------------------------------------------------------
/structural/proxy1.rb:
--------------------------------------------------------------------------------
1 | # proxy1.rb
2 |
3 | # Provides a surrogate or placeholder for another object to control access to it.
4 |
5 | # 1. type interface
6 |
7 | class Subject
8 | def process
9 | end
10 | end
11 |
12 | # 2. type implementation
13 |
14 | class MySubject < Subject
15 | def process
16 | puts 'my process'
17 | end
18 | end
19 |
20 | # 3. type proxy implementation
21 |
22 | class SubjectProxy < Subject
23 | def initialize(subject)
24 | @subject = subject
25 | end
26 |
27 | def process
28 | puts("Delegating 'process' message to subject.")
29 |
30 | @subject.process
31 | end
32 | end
33 |
34 |
35 | # 4. test
36 |
37 | proxy = SubjectProxy.new(MySubject.new)
38 |
39 | proxy.process
40 |
--------------------------------------------------------------------------------
/structural/proxy2.rb:
--------------------------------------------------------------------------------
1 | # proxy2.rb
2 |
3 | # Provides proxy implementation with ruby's method_missing.
4 |
5 | # 1. type interface
6 |
7 | class Subject
8 | def process
9 | end
10 | end
11 |
12 | # 2. type implementation
13 |
14 | class MySubject < Subject
15 | def process
16 | puts 'my process'
17 | end
18 | end
19 |
20 | # 3. type proxy implementation
21 |
22 | class DynamicSubjectProxy
23 | def initialize(subject)
24 | @subject = subject
25 | end
26 |
27 | def method_missing(name, *args)
28 | puts("Delegating '#{name}' message to subject.")
29 |
30 | @subject.send(name, *args)
31 | end
32 | end
33 |
34 |
35 | # 4. test
36 |
37 | dynamic_proxy = DynamicSubjectProxy.new(MySubject.new)
38 |
39 | dynamic_proxy.process
40 |
--------------------------------------------------------------------------------