Skip to content

Repository files navigation

ruby-patterns

Examples of Patterns in Ruby

Table of Contents

  1. Adapter
  2. Builder
  3. Command
  4. Composite
  5. Decorator
  6. Facade
  7. Factory
  8. Interpreter
  9. Iterator
  10. Observer
  11. Proxy
  12. Singleton
  13. State
  14. Strategy
  15. Template

Adapter

  • Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.[link]
classQuestattr_accessor:difficulty,:herodefinitialize(difficulty)@difficulty=difficulty@hero=nilenddeffinish@hero.exp += calculate_experienceenddefcalculate_experience@difficulty * 50 / @hero.levelendendclassHeroattr_accessor:level,:exp,:questsdefinitialize@level=1@exp=0@quests=[]enddeftake_quest(quest)@quests << (quest.hero=self)enddeffinish_quest(quest)quest.finish@quests.deletequestendendclassOldQuestattr_accessor:hero,:difficulty,:experiencedefinitialize@difficulty=3@experience=10enddefdonedifficulty * experienceendendclassQuestAdapterattr_accessor:herodefinitialize(old_quest,difficulty)@old_quest=old_quest@old_quest.difficulty=difficulty@hero=nilenddeffinish@hero.exp += @old_quest.doneendend# Usagehero=Hero.newquest=Quest.new5hero.take_questquesthero.finish_questquestputshero.exp# => 250some_old_quest=OldQuest.newold_quest_adapted=QuestAdapter.new(some_old_quest,5)hero.take_questold_quest_adaptedhero.finish_questold_quest_adaptedputshero.exp# => 300

Back to top

Builder

  • Separate the construction of a complex object from its representation so that the same construction process can create different representations.[link]
classBoardBuilderdefinitialize(width,height)@board=Board.new@board.width=width@board.height=height@board.tiles=[]@board.monsters=[]enddefadd_tiles(n)n.times{@board.tiles << Tile.new}enddefadd_monsters(n)n.times{@board.monsters << Monster.new}enddefboard@boardendendclassBoardattr_accessor:width,:height,:tiles,:monstersdefinitializeendendclassTile;endclassMonster;end# Usagebuilder=BoardBuilder.new2,3putsbuilder.board# => Board Objectboard=builder.boardputsboard.width# => 2builder.add_tiles(3)builder.add_monsters(2)putsboard.tiles.size# => 3putsboard.monsters.size# => 2

Back to top

Command

  • Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.[link]
classTurndefinitialize@commands=[]enddefrun_command(command)command.execute@commands << commandenddefundo_command@commands.pop.unexecuteendendclassHeroattr_accessor:money,:healthdefinitialize@money=0@health=100endendclassGetMoneyCommanddefinitialize(hero)@hero=heroenddefexecute@hero.money += 10enddefunexecute@hero.money -= 10endendclassHealCommanddefinitialize(hero)@hero=heroenddefexecute@hero.health += 10enddefunexecute@hero.health -= 10endend# Usagehero=Hero.newget_money=GetMoneyCommand.newheroheal=HealCommand.newheroturn=Turn.newturn.run_command(heal)putshero.health# => 110turn.run_command(get_money)putshero.money# => 10turn.undo_commandputshero.money# => 0

Back to top

Composite

  • Composition over inheritance. Compose objects into tree structures to represent part-whole hierarchies.[link]
classCompositeQuestdefinitialize@tasks=[]enddef <<(task)@tasks << taskenddefreward@tasks.inject(0){ |sum,task| sum += task.reward}endendclassMegaQuest < CompositeQuestendclassQuest < CompositeQuestendclassMonsterTaskattr_reader:rewarddefinitialize@reward=100endendclassPuzzleTaskattr_reader:rewarddefinitialize@reward=200endend# Usagequest1=Quest.newquest1 << MonsterTask.newquest1 << PuzzleTask.newputsquest1.reward# => 300quest2=Quest.newquest2 << MonsterTask.newquest2 << PuzzleTask.newmegaquest=MegaQuest.newmegaquest << quest1megaquest << quest2putsmegaquest.reward# => 600

Back to top

Decorator

  • Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.[link]
classItemDecoratordefinitialize(item)@item=itemend# this needs to be delegated with other efective waydefuse@item.useendendclassMagicItemDecorator < ItemDecoratordefprice@item.price * 3enddefdescription@item.description + "Magic"endendclassMasterpieceItemDecorator < ItemDecoratordefprice@item.price * 2enddefdescription@item.description + "Masterpiece"endendclassItemattr_reader:price,:descriptiondefinitialize@price=10@description="Item "enddefuse"do something"endend# Usageitem=Item.newmagic_item=MagicItemDecorator.new(item)putsmagic_item.price# => 30putsmagic_item.description# => Item Magicmasterpiece_item=MasterpieceItemDecorator.new(item)putsmasterpiece_item.price# => 20putsmasterpiece_item.description# => Item Masterpiece# all next lines puts "do something"item.usemagic_item.usemasterpiece_item.use

Back to top

Facade

  • The goal of the Facade Pattern is to provide a unified interface to a set of interfaces in a subsystem. This means you'd just have some object that can send back other objects.[link]
classHeroattr_reader:namedefinitialize(name)@name=nameenddefjoin(level)puts"#{self.name} join #{level}\n"enddefattack(enemy)puts"#{self.name} kick #{enemy}\n"endendclassEnemyattr_reader:namedefinitialize(name)@name=nameenddefdead(hero)puts"#{self.name} killed by #{hero}"endendclassLevelattr_reader:stagedefinitialize(stage)@stage=stageenddefto_sstageendendclassGameFacadeattr_reader:hero,:enemy,:leveldefinitialize@hero=Hero.new('Sonic')@enemy=Enemy.new('Eggman')@level=Level.new('Green Hill')enddefstart_gamehero.join(level)hero.attack(enemy.name)enemy.dead(hero.name)endendgame=GameFacade.newgame.start_game# => Sonic join Green Hill# Sonic kick Eggman# Eggman killed by Sonic

Back to top

Factory

  • Define an interface for creating an object, but let subclasses decide which class to instantiate.[link]
classPartyattr_reader:membersdefinitialize(factory)@members=[]@factory=factoryenddefadd_warriors(n)n.times{@members << @factory.create_warrior}enddefadd_mages(n)n.times{@members << @factory.create_mage}endendclassHeroFactorydefcreate_warriorWarrior.newenddefcreate_mageMage.newendendclassHerodefinitializeendendclassWarrior < HeroendclassMage < Heroend# Usageparty=Party.new(HeroFactory.new)party.add_warriors(3)party.add_mages(2)putsparty.members.size# => 5putsparty.members.count{ |member| member.class == "Mage"}# => 2

Back to top

Interpreter

  • This pattern provides an interpreter to deal with an abstract language. Using classes we can understand the inputs for parse them.[link]
classWorddefinitialize(value)@value=valueenddefexecute@valueendendclassPlusdefinitialize(first,second)@first=first@second=secondenddefexecute@first.execute + @second.executeendendclassMinusdefinitialize(first,second)@first=first@second=secondenddefexecuteindex=@first.execute =~ /#{@second.execute}/second_index=index + @second.execute.length@first.execute[0,index] + @first.execute[second_index..-1]endendclassInterpreterdefself.parse(input)@waiting_second_word=falsewords=[]operations=[]input.split.each_with_indexdo |value|
ifvalue =~ /^[^+-].*/ && !@waiting_second_wordwords << Word.new(value)elseifsymbol=operations.pop()first=words.size > 1 ? Word.new(words.map(&:execute).join(" ")) :
words.popsecond=Word.new(value)casesymbolwhen/\A\+/words << Word.new(Plus.new(first,second).execute)when/\A\-/words << Word.new(Minus.new(first,second).execute)end@waiting_second_word=falseelse@waiting_second_word=trueoperations << valueendendendwords.pop.executeendendputsInterpreter.parse("NA + NA + NA + BATMAN")#=> NANANABATMANputsInterpreter.parse("you know nothing Jon Snow - nothing")#=> you know Jon SnowputsInterpreter.parse("hello + world - llowo")#=>herld

Back to top

Iterator

  • Iterator helps you to iterate through a complex object using an iterator method.[link]
classParentattr_reader:first_namedefinitialize(first_name,gender)@first_name=first_name@gender=genderendendclassChild < ParentendclassFamilydefinitialize(surname)@surname=surname@children=[]enddefadd_father(first_name)@father=Parent.newfirst_name,"M"enddefadd_mother(first_name)@mother=Parent.newfirst_name,"F"enddefadd_child(first_name,gender)@children << Child.new(first_name,gender)enddefeach_member[@father,@mother,@children].flatten.eachdo |member|
yieldmemberendendend# Usagefamily=Family.new"Jackson"family.add_father("Robert")family.add_mother("Susan")family.add_child("Lucas","M")family.add_child("James","M")family.add_child("Rose","F")family.each_member{ |member| putsmember.first_name}# => Robert# Susan# Lucas# James# Rose

Back to top

Observer

  • Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.[link]
moduleObservableattr_reader:observersdefinitialize(attrs={})@observers=[]enddefadd_observer(observer)@observers << observerenddefnotify_observers@observers.each{ |observer| observer.update}endendclassTileincludeObservabledefinitialize(attrs={})super@cursed=attrs.fetch(:cursed,false)enddefcursed?@cursedenddefactivate_cursenotify_observersendendclassHeroattr_reader:healthdefinitialize@cursed=false@health=10enddefdamage(hit)@health -= hitenddefcursed?@cursedenddefdiscover(tile)iftile.cursed?@cursed=truetile.add_observer(self)endenddefupdatedamage(4)endend# Usagehero=Hero.newtile=Tile.newcursed: truehero.discover(tile)tile.activate_curseputshero.health# => 6putshero.cursed?# => true

Back to top

Proxy

  • Provide a surrogate or placeholder for another object to control access to it.[link]
classHeroattr_accessor:keywordsdefinitialize@keywords=[]endendclassComputerProxy# Forwardable allows objects to run methods on behalf# of it's members, in this case the Computer objectextendForwardable# We delegate the ComputerProxy's use of# the Computer object's add methoddef_delegators:real_object,:adddefinitialize(hero)@hero=heroenddefexecutecheck_accessreal_object.executeenddefcheck_accessunless@hero.keywords.include?(:computer)raise"You have no access"endenddefreal_object@real_object ||= Computer.newendendclassComputerdefinitialize@queue=[]enddefadd(command)@queue << commandenddefexecute"executing commands"endend# Usagehero=Hero.newproxy=ComputerProxy.new(hero)proxy.add("some command")proxy.execute# => raise errorhero.keywords << :computerproxy.execute# => executing commands

Back to top

Singleton

  • Define a unique instance of an object.[link]
classHeroFactory@@instance=nildefself.instance@@instance ||= HeroFactory.send(:new)enddefcreate_warriorWarrior.newenddefcreate_mageMage.newendprivate_class_method:newend# Usagefactory=HeroFactory.instanceanother_factory=HeroFactory.instanceputsfactory == another_factory# => trueHeroFactory.new# => Raise Exception

Back to top

State

  • This pattern tries to simplify complicated control flows changing an object's behavior dynamically.[link]
classOperationattr_reader:statedefinitialize@state=OperationOpenState.newenddeftrigger(state)@state=@state.next(state)endendclassOperationOpenStatedefnext(state)ifvalid?(state)OperationPendingPaymentState.newelseraiseIllegalStateJumpErrorendenddefvalid?(state)state == :pending_paymentendendclassOperationPendingPaymentStatedefnext(state)OperationConfirmState.newifvalid?(state)enddefvalid?(state)state == :confirmendendclassIllegalStateJumpError < StandardError;endclassOperationConfirmState;end#Usageoperation=Operation.newputsoperation.state.class#=> OperationOpenStateoperation.trigger:pending_paymentputsoperation.state.class#=> OperationPendingPaymentStateoperation.trigger:confirmputsoperation.state.class#=> OperationConfirmStateoperation=Operation.newoperation.trigger:confirm#=> raise IllegalStateJumpError

Back to top

Strategy

  • Define a family of algorithms, encapsulate each one, and make them interchangeable.[link]
classHeroattr_reader:damage,:health,:skillsattr_accessor:printerdefinitialize(printer)@damage=10@health=5@printer=printer@skills=[:stealth,:driving,:intimidation]enddefprint_statsifblock_given?yield(damage,health,skills)elseprinter.print(damage,health,skills)endendendclassBattleStatsdefprint(damage,health,skills)"Damage: #{damage}\nHealth: #{health}"endendclassSkillStatsdefprint(damage,health,skills)skills.inject(""){ |result,skill| result + skill.to_s.capitalize + "\n"}endend# UsageHero.new(BattleStats.new).print_stats# => Damage: 10# Health: 5Hero.new(SkillStats.new).print_stats# => Stealth# Driving# IntimidationHero.new(any_printer).print_statsdo |damage,health,skills|
"Looks: I'm printing a customize message about my hero with damage #{damage} and number of skills: #{skills.size}"end

Back to top

Template

  • Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template methods lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.[link]
classHeroattr_reader:damage,:abilitiesdefinitialize@damage=damage_rating@abilities=occupation_abilitiesenddefgreetgreeting=["Hello"]greeting << unique_greeting_linegreetingenddefunique_greeting_lineraise"You must define unique_greeting_line"enddefdamage_rating10enddefoccupation_abilities[]enddefattack"Atack dealing #{damage} damage"endendclassWarrior < Herodefdamage_rating15enddefoccupation_abilities[:strike]enddefunique_greeting_line"Warrior is ready to fight!"endendclassMage < Herodefdamage_rating7enddefoccupation_abilities[:magic_spell]enddefunique_greeting_line"Mage is ready to make powerful spells!"endend

Back to top

About

Examples of Patterns in Ruby

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages