Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/piscolomo/ruby-patterns
Examples of Patterns in Ruby
https://github.com/piscolomo/ruby-patterns
Last synced: about 2 months ago
JSON representation
Examples of Patterns in Ruby
- Host: GitHub
- URL: https://github.com/piscolomo/ruby-patterns
- Owner: piscolomo
- Created: 2015-03-28T13:32:48.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2017-04-17T18:55:21.000Z (over 7 years ago)
- Last Synced: 2024-02-11T16:04:23.352Z (7 months ago)
- Language: Ruby
- Size: 34.2 KB
- Stars: 1,003
- Watchers: 47
- Forks: 107
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# ruby-patterns
Examples of Patterns in Ruby## Table of Contents
1. [Adapter](#adapter)
1. [Builder](#builder)
1. [Command](#command)
1. [Composite](#composite)
1. [Decorator](#decorator)
1. [Facade](#facade)
1. [Factory](#factory)
1. [Interpreter](#interpreter)
1. [Iterator](#iterator)
1. [Observer](#observer)
1. [Proxy](#proxy)
1. [Singleton](#singleton)
1. [State](#state)
1. [Strategy](#strategy)
1. [Template](#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](#adapter)]```ruby
class Quest
attr_accessor :difficulty, :herodef initialize(difficulty)
@difficulty = difficulty
@hero = nil
enddef finish
@hero.exp += calculate_experience
enddef calculate_experience
@difficulty * 50 / @hero.level
end
endclass Hero
attr_accessor :level, :exp, :questsdef initialize
@level = 1
@exp = 0
@quests = []
enddef take_quest(quest)
@quests << (quest.hero = self)
enddef finish_quest(quest)
quest.finish
@quests.delete quest
end
endclass OldQuest
attr_accessor :hero, :difficulty, :experiencedef initialize
@difficulty = 3
@experience = 10
enddef done
difficulty * experience
end
endclass QuestAdapter
attr_accessor :herodef initialize(old_quest, difficulty)
@old_quest = old_quest
@old_quest.difficulty = difficulty
@hero = nil
enddef finish
@hero.exp += @old_quest.done
end
end# Usage
hero = Hero.new
quest = Quest.new 5
hero.take_quest quest
hero.finish_quest quest
puts hero.exp
# => 250
some_old_quest = OldQuest.new
old_quest_adapted = QuestAdapter.new(some_old_quest, 5)
hero.take_quest old_quest_adapted
hero.finish_quest old_quest_adapted
puts hero.exp
# => 300
```**[Back to top](#table-of-contents)**
## Builder
- Separate the construction of a complex object from its representation so that the same construction process can create different representations.[[link](#builder)]```ruby
class BoardBuilder
def initialize(width, height)
@board = Board.new
@board.width = width
@board.height = height
@board.tiles = []
@board.monsters = []
enddef add_tiles(n)
n.times{ @board.tiles << Tile.new }
enddef add_monsters(n)
n.times{ @board.monsters << Monster.new }
enddef board
@board
end
endclass Board
attr_accessor :width, :height, :tiles, :monsters
def initialize
end
endclass Tile; end
class Monster; end# Usage
builder = BoardBuilder.new 2, 3
puts builder.board
# => Board Object
board = builder.board
puts board.width
# => 2
builder.add_tiles(3)
builder.add_monsters(2)
puts board.tiles.size
# => 3
puts board.monsters.size
# => 2
```**[Back to top](#table-of-contents)**
## Command
- Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.[[link](#command)]```ruby
class Turn
def initialize
@commands = []
enddef run_command(command)
command.execute
@commands << command
enddef undo_command
@commands.pop.unexecute
end
endclass Hero
attr_accessor :money, :healthdef initialize
@money = 0
@health = 100
end
endclass GetMoneyCommand
def initialize(hero)
@hero = hero
enddef execute
@hero.money += 10
enddef unexecute
@hero.money -= 10
end
endclass HealCommand
def initialize(hero)
@hero = hero
enddef execute
@hero.health += 10
enddef unexecute
@hero.health -= 10
end
end# Usage
hero = Hero.new
get_money = GetMoneyCommand.new hero
heal = HealCommand.new hero
turn = Turn.new
turn.run_command(heal)
puts hero.health
# => 110
turn.run_command(get_money)
puts hero.money
# => 10
turn.undo_command
puts hero.money
# => 0
```**[Back to top](#table-of-contents)**
## Composite
- Composition over inheritance. Compose objects into tree structures to represent part-whole hierarchies.[[link](#composite)]```ruby
class CompositeQuest
def initialize
@tasks = []
enddef <<(task)
@tasks << task
enddef reward
@tasks.inject(0){ |sum, task| sum += task.reward }
end
endclass MegaQuest < CompositeQuest
endclass Quest < CompositeQuest
endclass MonsterTask
attr_reader :rewarddef initialize
@reward = 100
end
endclass PuzzleTask
attr_reader :rewarddef initialize
@reward = 200
end
end# Usage
quest1 = Quest.new
quest1 << MonsterTask.new
quest1 << PuzzleTask.new
puts quest1.reward
# => 300quest2 = Quest.new
quest2 << MonsterTask.new
quest2 << PuzzleTask.new
megaquest = MegaQuest.new
megaquest << quest1
megaquest << quest2
puts megaquest.reward
# => 600
```**[Back to top](#table-of-contents)**
## Decorator
- Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.[[link](#decorator)]```ruby
class ItemDecorator
def initialize(item)
@item = item
end
# this needs to be delegated with other efective way
def use
@item.use
end
endclass MagicItemDecorator < ItemDecorator
def price
@item.price * 3
enddef description
@item.description + "Magic"
end
endclass MasterpieceItemDecorator < ItemDecorator
def price
@item.price * 2
enddef description
@item.description + "Masterpiece"
end
endclass Item
attr_reader :price, :description
def initialize
@price = 10
@description = "Item "
enddef use
"do something"
end
end# Usage
item = Item.new
magic_item = MagicItemDecorator.new(item)
puts magic_item.price
# => 30
puts magic_item.description
# => Item Magicmasterpiece_item = MasterpieceItemDecorator.new(item)
puts masterpiece_item.price
# => 20
puts masterpiece_item.description
# => Item Masterpiece# all next lines puts "do something"
item.use
magic_item.use
masterpiece_item.use
```**[Back to top](#table-of-contents)**
## 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](#facade)]```ruby
class Hero
attr_reader :namedef initialize(name)
@name = name
enddef join(level)
puts "#{self.name} join #{level}\n"
enddef attack(enemy)
puts "#{self.name} kick #{enemy}\n"
end
endclass Enemy
attr_reader :namedef initialize(name)
@name = name
enddef dead(hero)
puts "#{self.name} killed by #{hero}"
end
endclass Level
attr_reader :stagedef initialize(stage)
@stage = stage
enddef to_s
stage
end
endclass GameFacade
attr_reader :hero, :enemy, :leveldef initialize
@hero = Hero.new('Sonic')
@enemy = Enemy.new('Eggman')
@level = Level.new('Green Hill')
enddef start_game
hero.join(level)
hero.attack(enemy.name)
enemy.dead(hero.name)
end
endgame = GameFacade.new
game.start_game
# => Sonic join Green Hill
# Sonic kick Eggman
# Eggman killed by Sonic
```**[Back to top](#table-of-contents)**
## Factory
- Define an interface for creating an object, but let subclasses decide which class to instantiate.[[link](#factory)]```ruby
class Party
attr_reader :members
def initialize(factory)
@members = []
@factory = factory
enddef add_warriors(n)
n.times{ @members << @factory.create_warrior }
enddef add_mages(n)
n.times{ @members << @factory.create_mage }
end
endclass HeroFactory
def create_warrior
Warrior.new
enddef create_mage
Mage.new
end
endclass Hero
def initialize
end
endclass Warrior < Hero
endclass Mage < Hero
end# Usage
party = Party.new(HeroFactory.new)
party.add_warriors(3)
party.add_mages(2)
puts party.members.size
# => 5
puts party.members.count{ |member| member.class == "Mage" }
# => 2
```**[Back to top](#table-of-contents)**
## Interpreter
- This pattern provides an interpreter to deal with an abstract language. Using classes we can understand the inputs for parse them.[[link](#interpreter)]```ruby
class Word
def initialize(value)
@value = value
enddef execute
@value
end
endclass Plus
def initialize(first, second)
@first = first
@second = second
enddef execute
@first.execute + @second.execute
end
endclass Minus
def initialize(first, second)
@first = first
@second = second
enddef execute
index = @first.execute =~ /#{@second.execute}/
second_index = index + @second.execute.length
@first.execute[0,index] + @first.execute[second_index..-1]
end
endclass Interpreter
def self.parse(input)
@waiting_second_word = false
words = []
operations = []
input.split.each_with_index do |value|
if value =~ /^[^+-].*/ && !@waiting_second_word
words << Word.new(value)
else
if symbol = operations.pop()
first = words.size > 1 ? Word.new(words.map(&:execute).join(" ")) :
words.pop
second = Word.new(value)
case symbol
when /\A\+/
words << Word.new(Plus.new(first, second).execute)
when /\A\-/
words << Word.new(Minus.new(first, second).execute)
end
@waiting_second_word = false
else
@waiting_second_word = true
operations << value
end
end
end
words.pop.execute
end
endputs Interpreter.parse("NA + NA + NA + BATMAN")
#=> NANANABATMANputs Interpreter.parse("you know nothing Jon Snow - nothing")
#=> you know Jon Snowputs Interpreter.parse("hello + world - llowo")
#=>herld
```**[Back to top](#table-of-contents)**
## Iterator
- Iterator helps you to iterate through a complex object using an iterator method.[[link](#iterator)]```ruby
class Parent
attr_reader :first_namedef initialize(first_name, gender)
@first_name = first_name
@gender = gender
end
endclass Child < Parent
endclass Family
def initialize(surname)
@surname = surname
@children = []
enddef add_father(first_name)
@father = Parent.new first_name, "M"
enddef add_mother(first_name)
@mother = Parent.new first_name, "F"
enddef add_child(first_name, gender)
@children << Child.new(first_name, gender)
enddef each_member
[@father, @mother, @children].flatten.each do |member|
yield member
end
end
end# Usage
family = 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| puts member.first_name }
# => Robert
# Susan
# Lucas
# James
# Rose
```**[Back to top](#table-of-contents)**
## 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](#observer)]```ruby
module Observable
attr_reader :observersdef initialize(attrs = {})
@observers = []
enddef add_observer(observer)
@observers << observer
enddef notify_observers
@observers.each{ |observer| observer.update }
end
endclass Tile
include Observabledef initialize(attrs = {})
super
@cursed = attrs.fetch(:cursed, false)
enddef cursed?
@cursed
enddef activate_curse
notify_observers
end
endclass Hero
attr_reader :healthdef initialize
@cursed = false
@health = 10
enddef damage(hit)
@health -= hit
enddef cursed?
@cursed
enddef discover(tile)
if tile.cursed?
@cursed = true
tile.add_observer(self)
end
enddef update
damage(4)
end
end# Usage
hero = Hero.new
tile = Tile.new cursed: true
hero.discover(tile)
tile.activate_curse
puts hero.health
# => 6
puts hero.cursed?
# => true
```**[Back to top](#table-of-contents)**
## Proxy
- Provide a surrogate or placeholder for another object to control access to it.[[link](#proxy)]```ruby
require 'forwardable'class Hero
attr_accessor :keywordsdef initialize
@keywords = []
end
endclass ComputerProxy
# Forwardable allows objects to run methods on behalf
# of it's members, in this case the Computer object
extend Forwardable# We delegate the ComputerProxy's use of
# the Computer object's add method
def_delegators :real_object, :adddef initialize(hero)
@hero = hero
enddef execute
check_access
real_object.execute
enddef check_access
unless @hero.keywords.include?(:computer)
raise "You have no access"
end
enddef real_object
@real_object ||= Computer.new
end
endclass Computer
def initialize
@queue = []
enddef add(command)
@queue << command
enddef execute
"executing commands"
end
end# Usage
hero = Hero.new
proxy = ComputerProxy.new(hero)
proxy.add("some command")
proxy.execute
# => raise error
hero.keywords << :computer
proxy.execute
# => executing commands
```**[Back to top](#table-of-contents)**
## Singleton
- Define a unique instance of an object.[[link](#singleton)]```ruby
class HeroFactory
@@instance = nildef self.instance
@@instance ||= HeroFactory.send(:new)
enddef create_warrior
Warrior.new
enddef create_mage
Mage.new
endprivate_class_method :new
end# Usage
factory = HeroFactory.instance
another_factory = HeroFactory.instance
puts factory == another_factory
# => true
HeroFactory.new
# => Raise Exception
```**[Back to top](#table-of-contents)**
## State
- This pattern tries to simplify complicated control flows changing an object's behavior dynamically.[[link](#state)]```ruby
class Operation
attr_reader :state
def initialize
@state = OperationOpenState.new
enddef trigger(state)
@state = @state.next(state)
end
endclass OperationOpenState
def next(state)
if valid?(state)
OperationPendingPaymentState.new
else
raise IllegalStateJumpError
end
enddef valid?(state)
state == :pending_payment
end
endclass OperationPendingPaymentState
def next(state)
OperationConfirmState.new if valid?(state)
enddef valid?(state)
state == :confirm
end
end
class IllegalStateJumpError < StandardError; end
class OperationConfirmState; end#Usage
operation = Operation.new
puts operation.state.class
#=> OperationOpenState
operation.trigger :pending_payment
puts operation.state.class
#=> OperationPendingPaymentState
operation.trigger :confirm
puts operation.state.class
#=> OperationConfirmStateoperation = Operation.new
operation.trigger :confirm
#=> raise IllegalStateJumpError
```**[Back to top](#table-of-contents)**
## Strategy
- Define a family of algorithms, encapsulate each one, and make them interchangeable.[[link](#strategy)]```ruby
class Hero
attr_reader :damage, :health, :skills
attr_accessor :printerdef initialize(printer)
@damage = 10
@health = 5
@printer = printer
@skills = [:stealth, :driving, :intimidation]
enddef print_stats
if block_given?
yield(damage, health, skills)
else
printer.print(damage, health, skills)
end
end
endclass BattleStats
def print(damage, health, skills)
"Damage: #{damage}\nHealth: #{health}"
end
endclass SkillStats
def print(damage, health, skills)
skills.inject(""){ |result, skill| result + skill.to_s.capitalize + "\n" }
end
end# Usage
Hero.new(BattleStats.new).print_stats
# => Damage: 10
# Health: 5Hero.new(SkillStats.new).print_stats
# => Stealth
# Driving
# IntimidationHero.new(any_printer).print_stats do |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](#table-of-contents)**
## 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](#template)]```ruby
class Hero
attr_reader :damage, :abilitiesdef initialize
@damage = damage_rating
@abilities = occupation_abilities
enddef greet
greeting = ["Hello"]
greeting << unique_greeting_line
greeting
enddef unique_greeting_line
raise "You must define unique_greeting_line"
enddef damage_rating
10
enddef occupation_abilities
[]
enddef attack
"Atack dealing #{damage} damage"
end
endclass Warrior < Hero
def damage_rating
15
enddef occupation_abilities
[:strike]
enddef unique_greeting_line
"Warrior is ready to fight!"
end
endclass Mage < Hero
def damage_rating
7
enddef occupation_abilities
[:magic_spell]
enddef unique_greeting_line
"Mage is ready to make powerful spells!"
end
end
```**[Back to top](#table-of-contents)**