├── .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 | 6 | 7 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 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 | 116 | 117 | 118 | 120 | 121 | 124 | 125 | 126 | 127 | 128 | 129 | 149 | 150 | 151 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 190 | 191 | 192 | 193 | 196 | 197 | 200 | 201 | 202 | 203 | 206 | 207 | 210 | 211 | 214 | 215 | 216 | 217 | 220 | 221 | 224 | 225 | 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 | 521 | 522 | 523 | 524 | 1281993996010 525 | 1281993996010 526 | 527 | 528 | 1380051823307 529 | 1380051823307 530 | 531 | 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 | 565 | 566 | 570 | 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 | 601 | 602 | 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 | --------------------------------------------------------------------------------