├── .ameba.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── behavioral
├── chain_of_responsibility.cr
├── command.cr
├── interpreter.cr
├── iterator.cr
├── mediator.cr
├── memento.cr
├── observer.cr
├── state.cr
├── strategy.cr
├── template.cr
└── visitor.cr
├── creational
├── abstract_factory.cr
├── builder.cr
├── factory_method.cr
├── prototype.cr
└── singleton.cr
├── rebuild.sh
├── shard.yml
└── structural
├── adapter.cr
├── bridge.cr
├── composite.cr
├── decorator.cr
├── facade.cr
├── flyweight.cr
└── proxy.cr
/.ameba.yml:
--------------------------------------------------------------------------------
1 | LineLength:
2 | # Disallows lines longer that MaxLength number of symbols.
3 | Enabled: true
4 | MaxLength: 100
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib/
2 | .shards/
3 | shard.lock
4 | bin/ameba
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: crystal
2 | install:
3 | - shards install
4 | script:
5 | - ./rebuild.sh
6 | - bin/ameba
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Vitalii Elenhaupt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Crystal Patterns
4 | [](https://travis-ci.org/crystal-community/crystal-patterns)
5 |
6 | Design patterns implemented in Crystal language (with [MK](http://mortalkombat.wikia.com) in mind).
7 |
8 | The goal is to have a set of [GOF patterns](http://www.blackwasp.co.uk/gofpatterns.aspx) for Crystal users.
9 |
10 | ## Available implementations
11 |
12 | - Behavioral
13 | - [Command](behavioral/command.cr)
14 | - [Iterator](behavioral/iterator.cr)
15 | - [Memento](behavioral/memento.cr)
16 | - [Observer](behavioral/observer.cr)
17 | - [State](behavioral/state.cr)
18 | - [Strategy](behavioral/strategy.cr)
19 | - [Template](behavioral/template.cr)
20 | - [Visitor](behavioral/visitor.cr)
21 | - [Mediator](behavioral/mediator.cr)
22 | - [Chain of Responsibility](behavioral/chain_of_responsibility.cr)
23 | - [Interpreter](behavioral/interpreter.cr)
24 | - Creational
25 | - [Abstract Factory](creational/abstract_factory.cr)
26 | - [Builder](creational/builder.cr)
27 | - [Factory Method](creational/factory_method.cr)
28 | - [Prototype](creational/prototype.cr)
29 | - [Singleton](creational/singleton.cr)
30 | - Structural
31 | - [Adapter](structural/adapter.cr)
32 | - [Bridge](structural/bridge.cr)
33 | - [Composite](structural/composite.cr)
34 | - [Decorator](structural/decorator.cr)
35 | - [Facade](structural/facade.cr)
36 | - [Flyweight](structural/flyweight.cr)
37 | - [Proxy](structural/proxy.cr)
38 |
39 | If you found any inconsistency or a place for improvement, pull requests are welcomed.
40 |
41 | ## Want more?
42 |
43 | * [Crystal Design Patterns](https://github.com/bthachdev/crystal-design-patterns)
44 | * [Design Patterns in Ruby](https://github.com/nslocum/design-patterns-in-ruby)
45 |
46 | ## Contributors
47 |
48 | * [veelenga](https://github.com/veelenga) V. Elenhaupt - creator, maintainer
49 | * [silverweed](https://github.com/silverweed) Silverweed - contributor
50 | * [anicholson](https://github.com/anicholson) Andy Nicholson - contributor
51 |
--------------------------------------------------------------------------------
/behavioral/chain_of_responsibility.cr:
--------------------------------------------------------------------------------
1 | # The chain of responsibility pattern is a design pattern that defines a
2 | # linked list of handlers, each of which is able to process requests.
3 | # When a request is submitted to the chain, it is passed to the first
4 | # handler in the list that is able to process it.
5 |
6 | BATTLE_PLAN = %w(
7 | Scorpion
8 | Mileena
9 | Baraka
10 | Jax
11 | Johnny Cage
12 | Kitana
13 | Liu\ Kang
14 | Reptile
15 | Subzero
16 | Raiden
17 | Kung\ Lao
18 | Shang\ Tsung
19 | Kintaro
20 | Shao\ Khan
21 | ).reverse
22 |
23 | class NoMoreOpponents < Exception; end
24 |
25 | class Fighter
26 | getter name : String
27 | getter successor : Fighter?
28 |
29 | def initialize(@name, @successor)
30 | @alive = true
31 | end
32 |
33 | def defeat
34 | if @alive
35 | @alive = false
36 | puts "Defeated #{name}"
37 | elsif successor
38 | successor.as(Fighter).defeat
39 | else
40 | raise NoMoreOpponents.new "All opponents defeated!"
41 | end
42 | self
43 | end
44 | end
45 |
46 | struct Player
47 | getter name : String
48 | getter opponent : Fighter
49 |
50 | def initialize(@name, @opponent)
51 | @victorious = false
52 | end
53 |
54 | def fight
55 | puts "Fight!"
56 | opponent.defeat
57 | rescue NoMoreOpponents
58 | puts "#{name} Wins!"
59 | @victorious = true
60 | end
61 |
62 | def victorious?
63 | @victorious
64 | end
65 | end
66 |
67 | first_opponent = BATTLE_PLAN.reduce(nil) do |next_fighter, this_fighter|
68 | Fighter.new(this_fighter, next_fighter)
69 | end.as(Fighter)
70 |
71 | player = Player.new("Subzero", first_opponent)
72 |
73 | until player.victorious?
74 | player.fight
75 | end
76 |
77 | # Fight!
78 | # Defeated Scorpion
79 | # Fight!
80 | # Defeated Mileena
81 | # Fight!
82 | # Defeated Baraka
83 | # Fight!
84 | # Defeated Jax
85 | # Fight!
86 | # Defeated Johnny Cage
87 | # Fight!
88 | # Defeated Kitana
89 | # Fight!
90 | # Defeated Liu Kang
91 | # Fight!
92 | # Defeated Reptile
93 | # Fight!
94 | # Defeated Subzero
95 | # Fight!
96 | # Defeated Raiden
97 | # Fight!
98 | # Defeated Kung Lao
99 | # Fight!
100 | # Defeated Shang Tsung
101 | # Fight!
102 | # Defeated Kintaro
103 | # Fight!
104 | # Defeated Shao Khan
105 | # Fight!
106 | # Subzero Wins!
107 |
--------------------------------------------------------------------------------
/behavioral/command.cr:
--------------------------------------------------------------------------------
1 | # The command pattern is a design pattern that enables all of the
2 | # information for a request to be contained within a single object.
3 | # The command can then be invoked as required, often as part of a
4 | # batch of queued commands with rollback capabilities.
5 |
6 | abstract class Command
7 | abstract def execute
8 | abstract def undo
9 | end
10 |
11 | class MoveLeft < Command
12 | def execute
13 | puts "One step left"
14 | end
15 |
16 | def undo
17 | puts "Undo step left"
18 | end
19 | end
20 |
21 | class MoveRight < Command
22 | def execute
23 | puts "One step right"
24 | end
25 |
26 | def undo
27 | puts "Undo step right"
28 | end
29 | end
30 |
31 | class Hit < Command
32 | def execute
33 | puts "Do one hit"
34 | end
35 |
36 | def undo
37 | puts "Undo one hit"
38 | end
39 | end
40 |
41 | class CommandSequence < Command
42 | def initialize
43 | @commands = [] of Command
44 | end
45 |
46 | def <<(command)
47 | @commands << command
48 | end
49 |
50 | def execute
51 | @commands.each &.execute
52 | end
53 |
54 | def undo
55 | @commands.reverse.each &.undo
56 | end
57 | end
58 |
59 | class CommandSequencePlayer
60 | def initialize(@sequence : CommandSequence)
61 | end
62 |
63 | def forward
64 | @sequence.execute
65 | end
66 |
67 | def backward
68 | @sequence.undo
69 | end
70 | end
71 |
72 | # Sample
73 | sequence = CommandSequence.new.tap do |r|
74 | r << MoveLeft.new
75 | r << MoveLeft.new
76 | r << MoveLeft.new
77 | r << Hit.new
78 | r << MoveRight.new
79 | end
80 |
81 | player = CommandSequencePlayer.new sequence
82 |
83 | player.forward
84 | # One step left
85 | # One step left
86 | # One step left
87 | # Do one hit
88 | # One step right
89 |
90 | player.backward
91 | # Undo step right
92 | # Undo one hit
93 | # Undo step left
94 | # Undo step left
95 | # Undo step left
96 |
--------------------------------------------------------------------------------
/behavioral/interpreter.cr:
--------------------------------------------------------------------------------
1 | # The interpreter pattern is a design pattern that is useful when developing
2 | # domain-specific languages or notations. The pattern allows the grammar for
3 | # such a notation to be represented in an object-oriented fashion
4 | # that can easily be extended.
5 | #
6 | # In this example, we parse a sequence of Button presses
7 | # into a stream of moves. Nil is used to represent no press,
8 | # ending the combo.
9 |
10 | enum Fighter
11 | LiuKang
12 | Reptile
13 | Jax
14 | end
15 |
16 | struct Context
17 | property fighter : Fighter
18 | property buttons : Array(Button)
19 | property moves : Array(Move)
20 |
21 | def initialize(@fighter, @buttons = [] of Button, @moves = [] of Move)
22 | end
23 | end
24 |
25 | @[Flags]
26 | enum Button
27 | Left
28 | Right
29 | Up
30 | Down
31 | HighKick
32 | LowKick
33 | HighPunch
34 | LowPunch
35 | Block
36 | end
37 |
38 | enum Move
39 | Back = 1
40 | Forward = 2
41 | Up = 4
42 | Down = 8
43 | HighKick = 16
44 | LowKick = 32
45 | HighPunch = 64
46 | LowPunch = 128
47 | Block = 256
48 | HighFireball
49 | AcidSpit
50 | EnergyBlast
51 | end
52 |
53 | abstract class Evaluator
54 | def initialize(@context)
55 | end
56 |
57 | abstract def evaluate(press)
58 |
59 | protected getter context : Context
60 | end
61 |
62 | class TerminalExpression < Evaluator
63 | def evaluate(press)
64 | if special_move
65 | context.moves << special_move.as(Move)
66 | else
67 | context.moves = context.moves + regular_moves
68 | end
69 |
70 | context.tap {|c| c.buttons = [] of Button }
71 | end
72 |
73 | private def special_move
74 | case context.buttons
75 | when .== [Button::Right, Button::Right, Button::HighPunch]
76 | case context.fighter
77 | when Fighter::LiuKang
78 | Move::HighFireball
79 | when Fighter::Reptile
80 | Move::AcidSpit
81 | else
82 | nil
83 | end
84 | when .== [Button::Down, Button::Left, Button::HighKick]
85 | case context.fighter
86 | when Fighter::Jax
87 | Move::EnergyBlast
88 | else
89 | nil
90 | end
91 | else
92 | nil
93 | end
94 | end
95 |
96 | private def regular_moves
97 | context.buttons.map { |b| Move.from_value(b.to_i) }
98 | end
99 | end
100 |
101 | class PressExpression < Evaluator
102 | def evaluate(press)
103 | context.tap { |c| c.buttons << press }
104 | end
105 | end
106 |
107 | alias Input = Array(Int32?)
108 |
109 | class Parser
110 | def parse(sequence : Input, fighter) : Array(Move)
111 | presses = sequence.map do |press|
112 | press ? Button.from_value?(press.as(Int32)) : nil
113 | end
114 |
115 | context = Context.new(fighter: fighter)
116 |
117 | evaluate(presses, context)
118 | end
119 |
120 | def evaluate(presses, context)
121 | final_context = presses.reduce(context) do |current_context, press|
122 | case press
123 | when .nil?
124 | TerminalExpression.new(current_context).evaluate(press)
125 | else
126 | PressExpression.new(current_context).evaluate(press)
127 | end
128 | end
129 |
130 | final_context.moves
131 | end
132 | end
133 |
134 | MOVE_LIST = [
135 | [16, nil],
136 | [2, 2, 64, nil],
137 | [8, 1, 16, nil, 32, nil]
138 | ]
139 |
140 | parser = Parser.new
141 |
142 | puts parser.parse sequence: MOVE_LIST[0], fighter: Fighter::LiuKang # => [HighKick]
143 | puts parser.parse sequence: MOVE_LIST[0], fighter: Fighter::Reptile # => [HighKick]
144 | puts parser.parse sequence: MOVE_LIST[1], fighter: Fighter::LiuKang # => [HighFireball]
145 | puts parser.parse sequence: MOVE_LIST[1], fighter: Fighter::Reptile # => [AcidSpit]
146 | puts parser.parse sequence: MOVE_LIST[1], fighter: Fighter::Jax # => [Forward, Forward, HighPunch]
147 | puts parser.parse sequence: MOVE_LIST[2], fighter: Fighter::Jax # => [EnergyBlast, LowKick]
148 | puts parser.parse sequence: MOVE_LIST[2], fighter: Fighter::Reptile # => [Down, Back, HighKick, LowKick]
149 |
--------------------------------------------------------------------------------
/behavioral/iterator.cr:
--------------------------------------------------------------------------------
1 | # The iterator pattern is a design pattern that provides a means for
2 | # the elements of an aggregate object to be accessed sequentially without
3 | # knowledge of its structure. This allows traversing of lists, trees and
4 | # other structures in a standard manner.
5 |
6 | class Fighter
7 | getter name, weight
8 |
9 | def initialize(@name : String, @weight : Int32)
10 | end
11 | end
12 |
13 | class Tournament
14 | include Enumerable(Fighter)
15 |
16 | def initialize
17 | @fighters = [] of Fighter
18 | end
19 |
20 | def <<(fighter)
21 | @fighters << fighter
22 | end
23 |
24 | def each
25 | @fighters.each { |fighter| yield fighter }
26 | end
27 | end
28 |
29 | # Sample
30 | tournament = Tournament.new.tap do |t|
31 | t << Fighter.new "Jax", 150
32 | t << Fighter.new "Liu Kang", 84
33 | t << Fighter.new "Scorpion", 95
34 | t << Fighter.new "Sub-Zero", 95
35 | t << Fighter.new "Smoke", 252
36 | end
37 |
38 | p tournament.select { |fighter| fighter.weight > 100 }
39 | .map { |fighter| fighter.name }
40 | # ["Jax", "Smoke"]
41 |
--------------------------------------------------------------------------------
/behavioral/mediator.cr:
--------------------------------------------------------------------------------
1 | # The mediator pattern is a design pattern that promotes loose coupling of objects
2 | # by removing the need for classes to communicate with each other directly.
3 | # Instead, mediator objects are used to encapsulate and centralise the
4 | # interactions between classes.
5 |
6 | enum Move
7 | Die = -1
8 | Block = 0
9 | Kick = 10
10 | Punch = 30
11 | end
12 |
13 | abstract class MediatorBase
14 | abstract def perform_move(move, performer)
15 | end
16 |
17 | abstract class ColleagueBase
18 | protected getter mediator : MediatorBase
19 |
20 | def initialize(@mediator)
21 | @mediator.register(self)
22 | end
23 |
24 | def send(move : Move)
25 | mediator.perform_move(move, self)
26 | end
27 |
28 | def receive(move, sender)
29 | handle_move(move, sender)
30 | end
31 |
32 | abstract def handle_move(move, fighter)
33 | end
34 |
35 | class Match < MediatorBase
36 | property player1 : Fighter?
37 | property player2 : Fighter?
38 |
39 | def initialize
40 | puts "New match created"
41 | end
42 |
43 | def register(fighter : Fighter)
44 | if player1.nil?
45 | puts "Player 1: #{fighter.name}"
46 | self.player1 = fighter
47 | elsif player2.nil?
48 | puts "Player 2: #{fighter.name}"
49 | self.player2 = fighter
50 | else
51 | raise "Game already full"
52 | end
53 | end
54 |
55 | def perform_move(move, performer)
56 | if can_perform_move?(move, performer)
57 | if (performer == player1)
58 | player2.as(Fighter).receive(move, performer)
59 | elsif (performer == player2)
60 | player1.as(Fighter).receive(move, performer)
61 | end
62 | else
63 | puts "Ignoring move"
64 | end
65 | end
66 |
67 | private def can_perform_move?(move, fighter)
68 | two_players? &&
69 | playing?(fighter) &&
70 | can_perform?(move, fighter)
71 | end
72 |
73 | private def two_players?
74 | !(player1.nil? || player2.nil?)
75 | end
76 |
77 | private def playing?(fighter)
78 | !fighter.nil? && [player1, player2].includes? fighter
79 | end
80 |
81 | private def can_perform?(move, fighter)
82 | (move == Move::Die) || everyone_alive?
83 | end
84 |
85 | private def everyone_alive?
86 | player1.as(Fighter).alive? && player2.as(Fighter).alive?
87 | end
88 | end
89 |
90 | class Fighter < ColleagueBase
91 | property name : String
92 |
93 | def initialize(@mediator, @name)
94 | super(@mediator)
95 | @health_bar = 100_i8
96 | end
97 |
98 | def alive?
99 | @health_bar > 0
100 | end
101 |
102 | def kick
103 | puts "#{name} kicks"
104 | send(Move::Kick)
105 | end
106 |
107 | def punch
108 | puts "#{name} punches"
109 | send(Move::Punch)
110 | end
111 |
112 | def block
113 | puts "#{name} blocks"
114 | send(Move::Block)
115 | end
116 |
117 | def die
118 | puts "#{name} died"
119 | send(Move::Die)
120 | end
121 |
122 | def handle_move(move, fighter)
123 | if fighter != self
124 | value = move.value
125 | case move
126 | when Move::Die
127 | puts "#{name} defeated #{fighter.name}!"
128 | else
129 | if value > 0
130 | @health_bar -= move.to_i
131 | puts "#{name} is damaged for #{value} (#{@health_bar})"
132 | end
133 | end
134 | if @health_bar < 0
135 | self.die
136 | end
137 | end
138 | end
139 | end
140 |
141 | match = Match.new
142 | # New match created
143 |
144 | p1 = Fighter.new(match, name: "Liu Kang")
145 | # Player 1: Liu Kang
146 |
147 | p1.punch
148 | # Liu Kang punches
149 | # ignoring move
150 |
151 | p2 = Fighter.new(match, name: "Shang Tsung")
152 | # Player 2: Shang Tsung
153 |
154 | p1.kick
155 | # Liu Kang kicks
156 | # Shang Tsung is damaged for 10 (90)
157 |
158 | p2.punch
159 | # Shang Tsung punches
160 | # Liu Kang is damaged for 30 (70)
161 |
162 | p1.kick
163 | # Liu Kang kicks
164 | # Shang Tsung is damaged for 10 (80)
165 |
166 | p2.punch
167 | # Shang Tsung punches
168 | # Liu Kang is damaged for 30 (40)
169 |
170 | p1.punch
171 | # Liu Kang punches
172 | # Shang Tsung is damaged for 30 (50)
173 |
174 | p1.punch
175 | # Liu Kang punches
176 | # Shang Tsung is damaged for 30 (20)
177 |
178 | p2.kick
179 | # Shang Tsung kicks
180 | # Liu Kang is damaged for 10 (30)
181 |
182 | p1.punch
183 | # Liu Kang punches
184 | # Shang Tsung is damaged for 30 (-10)
185 | # Shang Tsung died
186 | # Liu Kang defeated Shang Tsung!
187 |
188 | p1.kick
189 | # Liu Kang kicks
190 | # ignoring move
191 |
--------------------------------------------------------------------------------
/behavioral/memento.cr:
--------------------------------------------------------------------------------
1 | # The memento pattern is a design pattern that permits the current state
2 | # of an object to be stored without breaking the rules of encapsulation.
3 | # The originating object can be modified as required but can be restored
4 | # to the saved state at any time.
5 |
6 | # The class whose state can be saved with a memento
7 | class Match
8 | def initialize(fighter1, fighter2, rounds, time)
9 | @state = State.new({fighter1, fighter2}, rounds, time)
10 | end
11 |
12 | def save_memento
13 | @state
14 | end
15 |
16 | def restore_memento(memento : Match::State)
17 | @state = memento
18 | end
19 |
20 | def start
21 | print "Starting fight between #{@state.fighters[0]} and #{@state.fighters[1]}!"
22 | puts " (#{@state.rounds} rounds of #{@state.time.total_seconds} seconds)"
23 | end
24 |
25 | def rounds=(rounds)
26 | @state.rounds = rounds
27 | end
28 |
29 | def fighters=(fighters)
30 | @state.fighters = fighters
31 | end
32 |
33 | # The memento class. This is an opaque class for the client.
34 | struct State
35 | getter fighters, rounds, time
36 | protected setter fighters, rounds, time
37 |
38 | def initialize(
39 | @fighters : Tuple(String, String),
40 | @rounds : Int32,
41 | @time : Time::Span)
42 | end
43 | end
44 | end
45 |
46 | match = Match.new("Liu Kang", "Kano", 3, 60.seconds)
47 | # Save the current state
48 | previous_match_state = match.save_memento
49 | # Change the state
50 | match.rounds = 5
51 | match.fighters = {"Sonya Blade", "Goro"}
52 | match.start
53 | # Restore the previous state
54 | match.restore_memento(previous_match_state)
55 | match.start
56 |
57 | # Starting fight between Sonya Blade and Goro! (5 rounds of 60.0 seconds)
58 | # Starting fight between Liu Kang and Kano! (3 rounds of 60.0 seconds)
59 |
--------------------------------------------------------------------------------
/behavioral/observer.cr:
--------------------------------------------------------------------------------
1 | # Defines a link between objects so that when one object's state
2 | # changes, all dependent objects are update automatically. Allows
3 | # communication between objects in a loosely coupled manner.
4 |
5 | abstract class Observer
6 | abstract def update(fighter)
7 | end
8 |
9 | module Observable(T)
10 | getter observers
11 |
12 | def add_observer(observer : Observer)
13 | @observers ||= [] of T
14 | @observers.not_nil! << observer
15 | end
16 |
17 | def delete_observer(observer)
18 | @observers.try &.delete(observer)
19 | end
20 |
21 | def notify_observers
22 | @observers.try &.each &.update self
23 | end
24 | end
25 |
26 | class Fighter
27 | include Observable(Observer)
28 |
29 | getter name, health
30 |
31 | def initialize(@name : String)
32 | @health = 100
33 | end
34 |
35 | def damage(rate : Int32)
36 | if @health > rate
37 | @health -= rate
38 | else
39 | @health = 0
40 | end
41 | notify_observers
42 | end
43 |
44 | def dead?
45 | @health <= 0
46 | end
47 | end
48 |
49 | class Stats < Observer
50 | def update(fighter)
51 | puts "Updating stats: #{fighter.name}'s health is #{fighter.health}"
52 | end
53 | end
54 |
55 | class DieAction < Observer
56 | def update(fighter)
57 | puts "#{fighter.name} is dead. Fight is over!" if fighter.dead?
58 | end
59 | end
60 |
61 | # Sample
62 | fighter = Fighter.new("Scorpion")
63 |
64 | fighter.add_observer(Stats.new)
65 | fighter.add_observer(DieAction.new)
66 |
67 | fighter.damage(10)
68 | # Updating stats: Scorpion's health is 90
69 |
70 | fighter.damage(30)
71 | # Updating stats: Scorpion's health is 60
72 |
73 | fighter.damage(75)
74 | # Updating stats: Scorpion's health is 0
75 | # Scorpion is dead. Fight is over!
76 |
--------------------------------------------------------------------------------
/behavioral/state.cr:
--------------------------------------------------------------------------------
1 | # Defines a manner for controlling communication between classes or entities.
2 | # The state pattern is used to alter the behaviour of an object as its internal
3 | # state changes.
4 |
5 | class GameContext
6 | property state : State
7 |
8 | def initialize(@state)
9 | end
10 |
11 | def toggle
12 | @state.handle(self)
13 | end
14 | end
15 |
16 | abstract class State
17 | abstract def handle(context : GameContext)
18 | end
19 |
20 | class PauseState < State
21 | def handle(context : GameContext)
22 | puts "Pause -> Play"
23 | context.state = PlayState.new
24 | end
25 | end
26 |
27 | class PlayState < State
28 | def handle(context : GameContext)
29 | puts "Play -> Pause"
30 | context.state = PauseState.new
31 | end
32 | end
33 |
34 | initial_state = PlayState.new
35 | context = GameContext.new initial_state
36 |
37 | context.toggle # Play -> Pause
38 | context.toggle # Pause -> Play
39 |
--------------------------------------------------------------------------------
/behavioral/strategy.cr:
--------------------------------------------------------------------------------
1 | # Allows a set of similar algorithms to be defined and encaplusated
2 | # in their own classes. The algorithm to be used for a particular
3 | # purpose may then be selected at run-time.
4 |
5 | class Fighter
6 | getter health, name
7 | setter health
8 |
9 | def initialize(@name : String, @fight_strategy : FightStrategy)
10 | @health = 100
11 | end
12 |
13 | def attack(opponent)
14 | @fight_strategy.attack self, opponent
15 | puts "#{opponent.name} is dead" if opponent.dead?
16 | end
17 |
18 | def dead?
19 | health <= 0
20 | end
21 |
22 | def damage(rate)
23 | if @health > rate
24 | @health -= rate
25 | else
26 | @health = 0
27 | end
28 | end
29 | end
30 |
31 | abstract class FightStrategy
32 | HITS = {:punch => 40, :kick => 12}
33 |
34 | abstract def attack(fighter, opponent)
35 | end
36 |
37 | class Puncher < FightStrategy
38 | def attack(fighter, opponent)
39 | puts "#{fighter.name} attacks #{opponent.name} with 1 punch."
40 | opponent.damage(HITS[:punch])
41 | end
42 | end
43 |
44 | class Combo < FightStrategy
45 | def attack(fighter, opponent)
46 | puts "#{fighter.name} attacks #{opponent.name} with 2 kicks and 1 punch."
47 |
48 | opponent.damage(HITS[:kick])
49 | opponent.damage(HITS[:kick])
50 | opponent.damage(HITS[:punch])
51 | end
52 | end
53 |
54 | # Sample
55 | scor = Fighter.new("Scorpion", Puncher.new)
56 | noob = Fighter.new("Noob", Combo.new)
57 |
58 | noob.attack scor
59 | # Noob attacks Scorpion with 2 kicks and 1 punch.
60 |
61 | scor.attack noob
62 | # Scorpion attacks Noob with 1 punch.
63 |
64 | noob.attack scor
65 | # Noob attacks Scorpion with 2 kicks and 1 punch.
66 | # Scorpion is dead
67 |
--------------------------------------------------------------------------------
/behavioral/template.cr:
--------------------------------------------------------------------------------
1 | # Defines and invariant part of the algorithm in a base class
2 | # and encapsulates the variable parts in methods that are
3 | # defined by a number of subclasses.
4 |
5 | abstract class Fighter
6 | abstract def damage_rate
7 | abstract def attack_message
8 |
9 | setter health
10 | getter health, name
11 |
12 | def initialize(@name : String)
13 | @damage_rate = damage_rate.as(Int32)
14 | @attack_message = attack_message.as(String)
15 | @health = 100
16 | end
17 |
18 | def attack(fighter)
19 | fighter.damage(@damage_rate)
20 | puts "#{@name} attacks #{fighter.name} saying '#{@attack_message}'"
21 | puts "#{fighter.name} is dead." if fighter.dead?
22 | end
23 |
24 | def dead?
25 | @health <= 0
26 | end
27 |
28 | def damage(rate)
29 | if @health > rate
30 | @health -= rate
31 | else
32 | @health = 0
33 | end
34 | end
35 | end
36 |
37 | class Scorpion < Fighter
38 | def initialize
39 | super("Scorpion")
40 | end
41 |
42 | def damage_rate
43 | 30
44 | end
45 |
46 | def attack_message
47 | "Vengeance will be mine."
48 | end
49 | end
50 |
51 | class Noob < Fighter
52 | def initialize
53 | super("Noob")
54 | end
55 |
56 | def damage_rate
57 | 50
58 | end
59 |
60 | def attack_message
61 | "Fear me!"
62 | end
63 | end
64 |
65 | # Sample
66 | scor = Scorpion.new
67 | noob = Noob.new
68 |
69 | noob.attack scor
70 | # Noob attacks Scorpion saying 'Fear me!'
71 |
72 | scor.attack noob
73 | # Scorpion attacks Noob saying 'Vengeance will be mine.'
74 |
75 | noob.attack scor
76 | # Noob attacks Scorpion saying 'Fear me!'
77 | # Scorpion is dead.
78 |
--------------------------------------------------------------------------------
/behavioral/visitor.cr:
--------------------------------------------------------------------------------
1 | # The visitor pattern is a design pattern that separates a set of
2 | # structured data from the functionality that may be performed upon it.
3 | # This promotes loose coupling and enables additional operations
4 | # to be added without modifying the data classes.
5 |
6 | # Base Visitor class. Defines abstract operations on all visitable classes
7 | abstract class Visitor
8 | abstract def visit(fighter : Fighter)
9 | abstract def visit(arena : Arena)
10 | end
11 |
12 | # Concrete Visitor implementations, performing different tasks on
13 | # visitable objects.
14 | class SetupVisitor < Visitor
15 | def visit(fighter : Fighter)
16 | puts "[Setup] Visiting Fighter"
17 | fighter.ready
18 | end
19 |
20 | def visit(arena : Arena)
21 | puts "[Setup] Visiting Arena"
22 | arena.prepare
23 | end
24 | end
25 |
26 | class UpdateVisitor < Visitor
27 | def visit(fighter : Fighter)
28 | puts "[Update] Visiting Fighter"
29 | fighter.is_ko = fighter.hp == 0
30 | end
31 |
32 | def visit(arena : Arena)
33 | puts "[Update] Visiting Arena"
34 | puts("Battle is over!") if arena.destroyed?
35 | end
36 | end
37 |
38 | # Base visitable class. Defines an abstract "accept" method.
39 | abstract class GameElement
40 | abstract def accept(visitor : Visitor)
41 | end
42 |
43 | # Visitable classes. These classes just pass themselves to the visitor
44 | # in their accept method.
45 | class Fighter < GameElement
46 | property hp, is_ko
47 |
48 | def initialize
49 | @ready_for_battle = false
50 | @hp = 100
51 | @is_ko = false
52 | end
53 |
54 | def ready
55 | @ready_for_battle = true
56 | puts "Fighter is ready!"
57 | end
58 |
59 | def accept(visitor : Visitor)
60 | visitor.visit(self)
61 | end
62 | end
63 |
64 | class Arena < GameElement
65 | def initialize
66 | @destroyed = false
67 | end
68 |
69 | def prepare
70 | puts "Preparing arena..."
71 | end
72 |
73 | def destroyed?
74 | @destroyed
75 | end
76 |
77 | def accept(visitor : Visitor)
78 | visitor.visit(self)
79 | end
80 | end
81 |
82 | arena = Arena.new
83 | fighter = Fighter.new
84 | setup_visitor = SetupVisitor.new
85 | update_visitor = UpdateVisitor.new
86 |
87 | # We can store different GameElements in a common Array(GameElements)
88 | # and have the Visitors call the proper overloaded method thanks to the
89 | # double dispatch provided by their `accept` methods.
90 | game_elements = [arena, fighter]
91 |
92 | game_elements.each { |elem| elem.accept(setup_visitor) }
93 | 3.times do
94 | game_elements.each { |elem| elem.accept(update_visitor) }
95 | end
96 |
97 | # [Setup] Visiting Arena
98 | # Preparing arena...
99 | # [Setup] Visiting Fighter
100 | # Fighter is ready!
101 | # [Update] Visiting Arena
102 | # [Update] Visiting Fighter
103 | # [Update] Visiting Arena
104 | # [Update] Visiting Fighter
105 | # [Update] Visiting Arena
106 | # [Update] Visiting Fighter
107 |
--------------------------------------------------------------------------------
/creational/abstract_factory.cr:
--------------------------------------------------------------------------------
1 | # Provides a way to encapsulate a group of individual factories that have a
2 | # common theme without specifying their concrete classes.
3 |
4 | abstract class Fighter
5 | getter name : String
6 |
7 | def initialize(@name)
8 | end
9 | end
10 |
11 | class Human < Fighter
12 | end
13 |
14 | class God < Fighter
15 | end
16 |
17 | abstract class Realm
18 | end
19 |
20 | class Earthrealm < Realm
21 | end
22 |
23 | class Haven < Realm
24 | end
25 |
26 | abstract class WorldFactory
27 | abstract def create_realm
28 | abstract def create_fighter(name)
29 | end
30 |
31 | class HavenFactory < WorldFactory
32 | def create_realm
33 | Haven.new
34 | end
35 |
36 | def create_fighter(name)
37 | God.new(name)
38 | end
39 | end
40 |
41 | class EarthrealmFactory < WorldFactory
42 | def create_realm
43 | Earthrealm.new
44 | end
45 |
46 | def create_fighter(name)
47 | Human.new(name)
48 | end
49 | end
50 |
51 | class World
52 | @realm : Realm
53 | @fighters = [] of Fighter
54 |
55 | def initialize(factory, number_of_fighters)
56 | @realm = factory.create_realm
57 |
58 | number_of_fighters.times do |i|
59 | @fighters << factory.create_fighter("Fighter ##{i + 1}")
60 | end
61 | end
62 |
63 | def all_fighters
64 | @fighters.map do |fighter|
65 | fighter.name + " from #{@realm.class}"
66 | end
67 | end
68 | end
69 |
70 | haven = World.new(HavenFactory.new, 2)
71 | puts haven.all_fighters
72 | # ["Fighter #1 from Haven", "Fighter #2 from Haven"]
73 |
74 | earthrealm = World.new(EarthrealmFactory.new, 3)
75 | puts earthrealm.all_fighters
76 | # ["Fighter #1 from Earthrealm", "Fighter #2 from Earthrealm", "Fighter #3 from Earthrealm"]
77 |
--------------------------------------------------------------------------------
/creational/builder.cr:
--------------------------------------------------------------------------------
1 | # Separates object construction from its representation.
2 | # Separate the construction of a complex object from its representation
3 | # so that the same construction processes can create different representations.
4 |
5 | class StageBuilder
6 | getter stage
7 |
8 | def initialize(name, width, height)
9 | @stage = Stage.new(name)
10 | @stage.width = width
11 | @stage.height = height
12 | @stage.brutalities = [] of Brutality
13 | end
14 |
15 | def set_background(background)
16 | @stage.background = background
17 | end
18 |
19 | def add_brutality(brutality)
20 | @stage.add_brutality brutality
21 | end
22 | end
23 |
24 | class Stage
25 | getter name : String
26 | property width, height
27 | property background : Background?
28 | property brutalities = [] of Brutality
29 |
30 | def initialize(@name, @width = 800, @height = 600)
31 | end
32 |
33 | def add_brutality(brutality : Brutality)
34 | brutalities << brutality
35 | end
36 | end
37 |
38 | class Brutality; end
39 |
40 | class Background; end
41 |
42 | builder = StageBuilder.new("Dead Pool", 800, 600)
43 | builder.set_background(Background.new)
44 | builder.add_brutality(Brutality.new)
45 | builder.add_brutality(Brutality.new)
46 |
47 | stage = builder.stage
48 | puts stage.name, stage.width, stage.height, stage.brutalities.size
49 | # Dead Pool
50 | # 800
51 | # 600
52 | # 2
53 |
--------------------------------------------------------------------------------
/creational/factory_method.cr:
--------------------------------------------------------------------------------
1 | # Used to replace class constructors, abstracting the process of object generation
2 | # so that the type of the object instantiated can be determined at run-time.
3 |
4 | abstract class Fighter
5 | getter name : String
6 |
7 | def initialize(@name)
8 | end
9 |
10 | def fullname
11 | "#{@name} (#{self.class})"
12 | end
13 | end
14 |
15 | class Human < Fighter
16 | end
17 |
18 | class God < Fighter
19 | end
20 |
21 | class Kiborg < Fighter
22 | end
23 |
24 | class Wampire < Fighter
25 | end
26 |
27 | class Tournament
28 | getter team1 = [] of Fighter
29 | getter team2 = [] of Fighter
30 |
31 | def initialize(number_of_fights = 0)
32 | number_of_fights.times do |i|
33 | @team1 << create_fighter(:team1, "Fighter ##{i + 1}")
34 | @team2 << create_fighter(:team2, "Fighter ##{i + 1}")
35 | end
36 | end
37 | end
38 |
39 | class HumansVSWampires < Tournament
40 | def create_fighter(team, name)
41 | case team
42 | when :team1
43 | Human.new(name)
44 | when :team2
45 | Wampire.new(name)
46 | else
47 | raise Exception.new "unknown team #{team}"
48 | end
49 | end
50 | end
51 |
52 | class KiborgsVSGods < Tournament
53 | def create_fighter(team, name)
54 | case team
55 | when :team1
56 | Kiborg.new(name)
57 | when :team2
58 | God.new(name)
59 | else
60 | raise Exception.new "unknown team #{team}"
61 | end
62 | end
63 | end
64 |
65 | t1 = HumansVSWampires.new(2)
66 | puts "Team1:\n #{t1.team1.map(&.fullname).join("\n ")}"
67 | puts "Team2:\n #{t1.team2.map(&.fullname).join("\n ")}"
68 |
69 | # Team1:
70 | # Fighter #1 (Human)
71 | # Fighter #2 (Human)
72 | # Team2:
73 | # Fighter #1 (Wampire)
74 | # Fighter #2 (Wampire)
75 |
76 | t2 = KiborgsVSGods.new(4)
77 | puts "Team1:\n #{t2.team1.map(&.fullname).join("\n ")}"
78 | puts "Team2:\n #{t2.team2.map(&.fullname).join("\n ")}"
79 |
80 | # Team1:
81 | # Fighter #1 (Kiborg)
82 | # Fighter #2 (Kiborg)
83 | # Fighter #3 (Kiborg)
84 | # Fighter #4 (Kiborg)
85 | # Team2:
86 | # Fighter #1 (God)
87 | # Fighter #2 (God)
88 | # Fighter #3 (God)
89 | # Fighter #4 (God)
90 |
--------------------------------------------------------------------------------
/creational/prototype.cr:
--------------------------------------------------------------------------------
1 | # Used to instantiate a new object by copying all of the properties of
2 | # an existing object, creating an independent clone
3 |
4 | class Trophy
5 | enum Type
6 | Gold
7 | Silver
8 | Bronse
9 | end
10 |
11 | property name, description, type, winner
12 |
13 | def initialize(@name : String, @description : String, @type : Type, @winner : String)
14 | end
15 |
16 | def_clone
17 | end
18 |
19 | trophy_1 = Trophy.new "Dark Future",
20 | "Perform 50 Brutalities.",
21 | Trophy::Type::Gold,
22 | "Noob"
23 |
24 | trophy_2 = trophy_1.clone
25 | trophy_2.winner = "Liu Kang"
26 |
27 | puts trophy_1.winner
28 | puts trophy_2.winner
29 |
30 | # Noob
31 | # Liu Kang
32 |
--------------------------------------------------------------------------------
/creational/singleton.cr:
--------------------------------------------------------------------------------
1 | # Class which can have only one instance.
2 | # Provides a global point to this instance.
3 |
4 | class Game
5 | getter name
6 |
7 | private def initialize
8 | @name = "Mortal Kombat"
9 | end
10 |
11 | def self.instance
12 | @@instance ||= new
13 | end
14 | end
15 |
16 | puts Game.instance.name
17 |
18 | # Mortal Kombat
19 |
--------------------------------------------------------------------------------
/rebuild.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | ret=0
4 |
5 | for src in creational/*.cr structural/*.cr behavioral/*.cr
6 | do
7 | (set -x; crystal build --no-codegen $src) || ret=$?
8 | done
9 |
10 | exit $ret
11 |
--------------------------------------------------------------------------------
/shard.yml:
--------------------------------------------------------------------------------
1 | name: crystal-patterns
2 | version: 0.1.0
3 |
4 | crystal: 0.24.2
5 | license: MIT
6 | authors:
7 | - Vitalii Elenhaupt
8 |
9 | development_dependencies:
10 | ameba:
11 | github: crystal-ameba/ameba
12 |
--------------------------------------------------------------------------------
/structural/adapter.cr:
--------------------------------------------------------------------------------
1 | # The adapter design pattern is used to provide a link between two
2 | # incompatible types by wrapping the "adaptee" with a class that supports
3 | # the interface required by the client.
4 |
5 | class GameServer
6 | def add_client(client)
7 | client.connect "Mortal Kombat Game Server"
8 | end
9 | end
10 |
11 | class DesktopClient
12 | def open_connection(server)
13 | puts "New TCP connection to '#{server}'"
14 | end
15 | end
16 |
17 | class WebClient
18 | def initialize(@server : String)
19 | end
20 |
21 | def websocket_connection
22 | puts "New Websocket connection to '#{@server}'"
23 | end
24 | end
25 |
26 | abstract class Client
27 | abstract def connect(server)
28 | end
29 |
30 | class DesktopClientAdapter < Client
31 | def initialize
32 | @client = DesktopClient.new
33 | end
34 |
35 | def connect(server)
36 | @client.open_connection(server)
37 | end
38 | end
39 |
40 | class WebClientAdapter < Client
41 | def connect(server)
42 | WebClient.new(server).websocket_connection
43 | end
44 | end
45 |
46 | server = GameServer.new
47 | server.add_client DesktopClientAdapter.new
48 | server.add_client WebClientAdapter.new
49 |
50 | # New TCP connection to 'Mortal Kombat Game Server'
51 | # New Websocket connection to 'Mortal Kombat Game Server'
52 |
--------------------------------------------------------------------------------
/structural/bridge.cr:
--------------------------------------------------------------------------------
1 | # Defines a manner for creating relationships between classes or entities.
2 | # The bridge pattern is used to separate the abstract elements of a class
3 | # from the implementation details.
4 |
5 | abstract class GraphicsBackend
6 | abstract def draw(scene)
7 | end
8 |
9 | class OpenGL < GraphicsBackend
10 | def draw(scene)
11 | puts "Drawing scene using OpenGL backend. Tick: #{scene.timer}"
12 | end
13 | end
14 |
15 | class Direct3D < GraphicsBackend
16 | def draw(scene)
17 | puts "Drawing scene using Direct3D backend. Tick: #{scene.timer}"
18 | end
19 | end
20 |
21 | abstract class Scene
22 | getter graphics : GraphicsBackend
23 |
24 | def initialize(@graphics)
25 | end
26 |
27 | abstract def repaint
28 | end
29 |
30 | class FightingScene < Scene
31 | getter timer
32 |
33 | def initialize(@graphics)
34 | super(@graphics)
35 |
36 | @timer = 0
37 | end
38 |
39 | def repaint
40 | @graphics.draw(self)
41 | end
42 |
43 | def refresh_fight_timer
44 | @timer += 1
45 | end
46 | end
47 |
48 | scenes = [] of Scene
49 | scenes << FightingScene.new(OpenGL.new)
50 | scenes << FightingScene.new(Direct3D.new)
51 |
52 | scenes.each do |scene|
53 | 3.times do
54 | scene.refresh_fight_timer
55 | scene.repaint
56 | end
57 | end
58 |
59 | # Drawing scene using OpenGL backend. Tick: 1
60 | # Drawing scene using OpenGL backend. Tick: 2
61 | # Drawing scene using OpenGL backend. Tick: 3
62 |
63 | # Drawing scene using Direct3D backend. Tick: 1
64 | # Drawing scene using Direct3D backend. Tick: 2
65 | # Drawing scene using Direct3D backend. Tick: 3
66 |
--------------------------------------------------------------------------------
/structural/composite.cr:
--------------------------------------------------------------------------------
1 | # The composite pattern is a design pattern that is used when
2 | # creating hierarchical object models. The pattern defines a
3 | # manner in which to design recursive tree structures of objects,
4 | # where individual objects and groups can be accessed in the same manner
5 |
6 | abstract class Strike
7 | abstract def damage
8 | abstract def attack
9 | end
10 |
11 | class Punch < Strike
12 | def attack
13 | puts "Hitting with punch"
14 | end
15 |
16 | def damage
17 | 5
18 | end
19 | end
20 |
21 | class Kick < Strike
22 | def attack
23 | puts "Hitting with kick"
24 | end
25 |
26 | def damage
27 | 8
28 | end
29 | end
30 |
31 | class Combo < Strike
32 | def initialize
33 | @sub_strikes = [] of Strike
34 | end
35 |
36 | def <<(strike)
37 | @sub_strikes << strike
38 | end
39 |
40 | def damage
41 | @sub_strikes.reduce(0) { |acc, x| acc + x.damage }
42 | end
43 |
44 | def attack
45 | @sub_strikes.each &.attack
46 | end
47 | end
48 |
49 | # Sample
50 | super_strike = Combo.new.tap do |s|
51 | s << Kick.new
52 | s << Kick.new
53 | s << Punch.new
54 | end
55 |
56 | super_strike.attack
57 | # Hitting with kick
58 | # Hitting with kick
59 | # Hitting with punch
60 |
61 | super_strike.damage
62 | # => 21
63 |
--------------------------------------------------------------------------------
/structural/decorator.cr:
--------------------------------------------------------------------------------
1 | # Defines a manner for creating relationships between classes or entities.
2 | # The decorator pattern is used to extend or alter the functionality of objects
3 | # at run-time by wrapping them in an object of a decorator class.
4 | # This provides a flexible alternative to using inheritance to modify behaviour.
5 |
6 | abstract class Fighter
7 | abstract def power
8 | abstract def abilities
9 | end
10 |
11 | class Scorpion < Fighter
12 | def power
13 | 25.0
14 | end
15 |
16 | def abilities
17 | %w(hellfire shuriken)
18 | end
19 | end
20 |
21 | class FighterAbility < Fighter
22 | getter fighter : Fighter
23 |
24 | def initialize(@fighter)
25 | end
26 |
27 | def power
28 | fighter.power
29 | end
30 |
31 | def abilities
32 | fighter.abilities
33 | end
34 | end
35 |
36 | class Spear < FighterAbility
37 | def power
38 | super + 10
39 | end
40 |
41 | def abilities
42 | super << "spear"
43 | end
44 | end
45 |
46 | class LegTakedown < FighterAbility
47 | def power
48 | super + 15
49 | end
50 |
51 | def abilities
52 | super << "leg takedown"
53 | end
54 | end
55 |
56 | class FireBall < FighterAbility
57 | def power
58 | super + 25
59 | end
60 |
61 | def abilities
62 | super << "fire ball"
63 | end
64 | end
65 |
66 | scorpion = Scorpion.new
67 | pp scorpion.power, scorpion.abilities
68 | # scorpion.power # => 25.0
69 | # scorpion.abilities # => ["hellfire", "shuriken"]
70 |
71 | scorpion = Spear.new(scorpion)
72 | pp scorpion.power, scorpion.abilities
73 | # scorpion.power # => 35.0
74 | # scorpion.abilities # => ["hellfire", "shuriken", "spear"]
75 |
76 | scorpion = LegTakedown.new(scorpion)
77 | pp scorpion.power, scorpion.abilities
78 | # scorpion.power # => 50.0
79 | # scorpion.abilities # => ["hellfire", "shuriken", "spear", "leg takedown"]
80 |
81 | scorpion = FireBall.new(scorpion)
82 | pp scorpion.power, scorpion.abilities
83 | # scorpion.power # => 75.0
84 | # scorpion.abilities # => ["hellfire", "shuriken", "spear", "leg takedown", "fire ball"]
85 |
--------------------------------------------------------------------------------
/structural/facade.cr:
--------------------------------------------------------------------------------
1 | # The facade pattern is a design pattern that is used to simplify access
2 | # to functionality in complex or poorly designed subsystems.
3 | # The facade class provides a simple, single-class interface that hides
4 | # the implementation details of the underlying code.
5 |
6 | # ----------------- Complex or poorly designed library -----------------------
7 | class Arena
8 | @round : Round?
9 |
10 | property round
11 |
12 | def initialize(@name : String)
13 | load_arena(@name)
14 | end
15 |
16 | def load_arena(name : String)
17 | puts "Loading arena #{name} from disk..."
18 | end
19 | end
20 |
21 | class RoundManager
22 | def define_round(arena : Arena, round_params : RoundParams)
23 | check_round_params(round_params)
24 | arena.round = Round.create_with_params(round_params)
25 | end
26 |
27 | def check_round_params(params : RoundParams)
28 | puts "Checking validity of round parameters..."
29 | end
30 | end
31 |
32 | class Round
33 | def initialize(@time : Int32, @matches : Int32)
34 | puts "Round initialized with duration #{@time}s and #{@matches} matches."
35 | end
36 |
37 | def self.create_with_params(params : RoundParams)
38 | Round.new(params.time, params.matches)
39 | end
40 | end
41 |
42 | struct RoundParams
43 | property time, matches
44 |
45 | def initialize(@time : Int32, @matches : Int32)
46 | end
47 | end
48 |
49 | # ----------------------------------------------------------------------------
50 |
51 | # Facade provides simplified access to the complex API
52 | class Facade
53 | DEFAULT_TIME = 60
54 | DEFAULT_MATCHES = 3
55 |
56 | def self.create_default_arena : Arena
57 | arena = Arena.new("default")
58 | rm = RoundManager.new
59 | params = RoundParams.new(DEFAULT_TIME, DEFAULT_MATCHES)
60 | rm.define_round(arena, params)
61 | arena
62 | end
63 | end
64 |
65 | Facade.create_default_arena
66 | # Loading arena default from disk...
67 | # Checking validity of round parameters...
68 | # Round initialized with duration 60s and 3 matches.
69 |
--------------------------------------------------------------------------------
/structural/flyweight.cr:
--------------------------------------------------------------------------------
1 | # The flyweight pattern is a design pattern that is used to minimise
2 | # resource usage when working with very large numbers of objects.
3 | # When creating many thousands of identical objects, stateless flyweights
4 | # can lower the memory used to a manageable level.
5 |
6 | alias Position = {Int32, Int32}
7 |
8 | # Contains the extrinsic actions that the object can do.
9 | abstract class FlyweightTree
10 | abstract def draw_at(pos : Position)
11 | end
12 |
13 | # Implements the Flyweight interface, optionally keeping an extrinsic state
14 | # which can be manipulated via that interface.
15 | class Tree < FlyweightTree
16 | property pos
17 |
18 | def initialize(species : String)
19 | # Intrinsic (stateless) properties. These are shared between all instances of this tree.
20 | @species = species
21 | # Estrinsic (stateful) properties. These are accessed via the abstract interface
22 | # provided by FlyweightTree
23 | @pos = {0, 0}
24 | end
25 |
26 | def draw_at(pos : Position)
27 | puts "Drawing #{@species} at #{pos}"
28 | end
29 | end
30 |
31 | # Factory class for the flyweight objects.
32 | class Forest
33 | def initialize
34 | @trees = {} of String => FlyweightTree
35 | end
36 |
37 | def get_tree(species : String)
38 | if @trees.has_key?(species)
39 | @trees[species]
40 | else
41 | Tree.new(species).tap { |tree| @trees[species] = tree }
42 | end
43 | end
44 |
45 | def tot_instances
46 | @trees.size
47 | end
48 | end
49 |
50 | # Client code
51 | forest = Forest.new
52 | forest.get_tree("birch").draw_at({5, 6})
53 | forest.get_tree("acacia").draw_at({3, 1})
54 | forest.get_tree("magnolia").draw_at({15, 86})
55 | forest.get_tree("birch").draw_at({8, 15})
56 | forest.get_tree("acacia").draw_at({18, 4})
57 | forest.get_tree("baobab").draw_at({1, 41})
58 | forest.get_tree("magnolia").draw_at({80, 50})
59 | forest.get_tree("acacia").draw_at({22, 3})
60 | forest.get_tree("birch").draw_at({1, 42})
61 | forest.get_tree("baobab").draw_at({15, 7})
62 | forest.get_tree("acacia").draw_at({33, 49})
63 | forest.get_tree("magnolia").draw_at({0, 0})
64 | puts "-----------------------"
65 | puts "Total instances created: #{forest.tot_instances}"
66 |
67 | # Drawing birch at {5, 6}
68 | # Drawing acacia at {3, 1}
69 | # Drawing magnolia at {15, 86}
70 | # Drawing birch at {8, 15}
71 | # Drawing acacia at {18, 4}
72 | # Drawing baobab at {1, 41}
73 | # Drawing magnolia at {80, 50}
74 | # Drawing acacia at {22, 3}
75 | # Drawing birch at {1, 42}
76 | # Drawing baobab at {15, 7}
77 | # Drawing acacia at {33, 49}
78 | # Drawing magnolia at {0, 0}
79 | # -----------------------
80 | # Total instances created: 4
81 |
--------------------------------------------------------------------------------
/structural/proxy.cr:
--------------------------------------------------------------------------------
1 | # The proxy pattern is a design pattern that creates a surrogate,
2 | # or placeholder class. Proxy instances accept requests from client objects,
3 | # pass them to the underlying object and return the results.
4 |
5 | # The class we're going to proxy
6 | class FighterGenerator
7 | def initialize(@ai_level : AILv)
8 | puts "Initializing FighterGenerator with AI level = #{@ai_level}"
9 | end
10 |
11 | def generate(fighter_name : String)
12 | Fighter.new(fighter_name, @ai_level)
13 | end
14 | end
15 |
16 | class Fighter
17 | def initialize(@name : String, @ai_level : AILv)
18 | puts "Creating new fighter #{@name} with ai level #{@ai_level}"
19 | end
20 | end
21 |
22 | enum AILv
23 | Easy
24 | Medium
25 | Hard
26 | end
27 |
28 | # The proxy class. There are several types of proxies: in this case, we use
29 | # a *cache proxy*, which memoizes the created fighters and returns
30 | # them without creating new ones if possible.
31 | class FighterGeneratorProxy
32 | def initialize(ai_level : AILv)
33 | @generator = FighterGenerator.new(ai_level)
34 | @cache = {} of String => Fighter
35 | end
36 |
37 | # The proxy's interface mimics that of the proxied class
38 | def generate(fighter_name : String)
39 | puts "Requested fighter #{fighter_name}"
40 | return @cache[fighter_name] if @cache.has_key?(fighter_name)
41 | @generator.generate(fighter_name).tap { |fighter| @cache[fighter_name] = fighter }
42 | end
43 | end
44 |
45 | generator = FighterGeneratorProxy.new(AILv::Hard)
46 | generator.generate("Sub-Zero")
47 | generator.generate("Scorpion")
48 | generator.generate("Johnny Cage")
49 | generator.generate("Sub-Zero")
50 | generator.generate("Kitana")
51 | generator.generate("Raiden")
52 | generator.generate("Scorpion")
53 | generator.generate("Raiden")
54 | generator.generate("Johnny Cage")
55 | generator.generate("Kitana")
56 |
57 | # Initializing FighterGenerator with AI level = Hard
58 | # Requested fighter Sub-Zero
59 | # Creating new fighter Sub-Zero with ai level Hard
60 | # Requested fighter Scorpion
61 | # Creating new fighter Scorpion with ai level Hard
62 | # Requested fighter Johnny Cage
63 | # Creating new fighter Johnny Cage with ai level Hard
64 | # Requested fighter Sub-Zero
65 | # Requested fighter Kitana
66 | # Creating new fighter Kitana with ai level Hard
67 | # Requested fighter Raiden
68 | # Creating new fighter Raiden with ai level Hard
69 | # Requested fighter Scorpion
70 | # Requested fighter Raiden
71 | # Requested fighter Johnny Cage
72 | # Requested fighter Kitana
73 |
--------------------------------------------------------------------------------