├── .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 | [![Build Status](https://travis-ci.org/crystal-community/crystal-patterns.svg?branch=master)](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 | --------------------------------------------------------------------------------