Examples of Patterns in Ruby
- Adapter
- Builder
- Command
- Composite
- Decorator
- Facade
- Factory
- Interpreter
- Iterator
- Observer
- Proxy
- Singleton
- State
- Strategy
- Template
- 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- 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- 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- 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- 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- 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- 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- 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- 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- 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- Provide a surrogate or placeholder for another object to control access to it.[link]
require'forwardable'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- 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- 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- 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- 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