├── .formatter.exs ├── .gitignore ├── README.md ├── behavioral ├── chain │ ├── README.md │ └── run.exs ├── command │ ├── README.md │ ├── commands.exs │ ├── run.exs │ ├── run.sh │ └── runner.exs ├── interpreter │ ├── README.md │ └── run.exs ├── iterator │ ├── README.md │ └── run.exs ├── mediator │ ├── README.md │ └── run.exs ├── memento │ ├── README.md │ └── run.exs ├── observer │ ├── README.md │ ├── employee.exs │ ├── hr.exs │ ├── payroll.exs │ ├── run.exs │ └── tax_man.exs ├── state │ ├── README.md │ └── run.exs ├── strategy │ ├── README.md │ ├── formatter.exs │ ├── html_formatter.exs │ ├── plain_text_formatter.exs │ ├── report.exs │ ├── run.exs │ └── using_fn │ │ ├── README.md │ │ ├── report.exs │ │ └── run.exs ├── template_method │ ├── README.md │ ├── html_report.exs │ ├── plain_text_report.exs │ ├── report.exs │ └── run.exs └── visitor │ ├── README.md │ └── run.exs ├── creational ├── builder │ ├── README.md │ ├── builder.exs │ ├── computer.exs │ ├── run.exs │ └── run.sh ├── factory │ ├── README.md │ ├── graphics.exs │ ├── run.exs │ ├── run.sh │ └── shape_factory.exs ├── factory_method │ ├── README.md │ └── run.exs ├── prototype │ ├── README.md │ └── run.exs └── singleton │ ├── README.md │ └── run.exs └── structural ├── adapter ├── README.md └── run.exs ├── bridge ├── README.md └── run.exs ├── composite ├── README.md ├── cooking.exs ├── run.exs └── run.sh ├── decorator ├── README.md └── run.exs ├── delegation ├── README.md └── run.exs ├── facade ├── README.md ├── run.exs └── run.sh ├── flyweight ├── README.md └── run.exs └── proxy ├── README.md └── run.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: ["**/*.{ex,exs}"] 3 | ] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZXZs/elixir-design-patterns/b5ec2a519c911a6af45b284c9cc281235527d099/.gitignore -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Common design patterns in Elixir 2 | 3 | * [**Behavioral**](https://github.com/ZXZs/elixir-design-patterns/tree/master/behavioral) 4 | * * [Chain of responsibility](https://github.com/ZXZs/elixir-design-patterns/tree/master/behavioral/chain) 5 | * * [Command](https://github.com/ZXZs/elixir-design-patterns/tree/master/behavioral/command) 6 | * * [Interpreter](https://github.com/ZXZs/elixir-design-patterns/tree/master/behavioral/interpreter) 7 | * * [Iterator](https://github.com/ZXZs/elixir-design-patterns/tree/master/behavioral/iterator) 8 | * * [Mediator](https://github.com/ZXZs/elixir-design-patterns/tree/master/behavioral/mediator) 9 | * * [Memento](https://github.com/ZXZs/elixir-design-patterns/tree/master/behavioral/memento) 10 | * * [Observer](https://github.com/ZXZs/elixir-design-patterns/tree/master/behavioral/observer) 11 | * * [State](https://github.com/ZXZs/elixir-design-patterns/tree/master/behavioral/state) 12 | * * [Strategy](https://github.com/ZXZs/elixir-design-patterns/tree/master/behavioral/strategy) 13 | * * [Template method](https://github.com/ZXZs/elixir-design-patterns/tree/master/behavioral/template_method) 14 | * * [Visitor](https://github.com/ZXZs/elixir-design-patterns/tree/master/behavioral/visitor) 15 | 16 | * [**Creational**](https://github.com/ZXZs/elixir-design-patterns/tree/master/creational) 17 | * * [Builder](https://github.com/ZXZs/elixir-design-patterns/tree/master/creational/builder) 18 | * * [Factory](https://github.com/ZXZs/elixir-design-patterns/tree/master/creational/factory) 19 | * * [Factory method](https://github.com/ZXZs/elixir-design-patterns/tree/master/creational/factory_method) 20 | * * [Prototype](https://github.com/ZXZs/elixir-design-patterns/tree/master/creational/prototype) 21 | * * [Singleton](https://github.com/ZXZs/elixir-design-patterns/tree/master/creational/singleton) 22 | 23 | * [**Structural**](https://github.com/ZXZs/elixir-design-patterns/tree/master/structural) 24 | * * [Adapter](https://github.com/ZXZs/elixir-design-patterns/tree/master/structural/adapter) 25 | * * [Bridge](https://github.com/ZXZs/elixir-design-patterns/tree/master/structural/bridge) 26 | * * [Composite](https://github.com/ZXZs/elixir-design-patterns/tree/master/structural/composite) 27 | * * [Decorator](https://github.com/ZXZs/elixir-design-patterns/tree/master/structural/decorator) 28 | * * [Delegation](https://github.com/ZXZs/elixir-design-patterns/tree/master/structural/delegation) 29 | * * [Facade](https://github.com/ZXZs/elixir-design-patterns/tree/master/structural/facade) 30 | * * [Flyweight](https://github.com/ZXZs/elixir-design-patterns/tree/master/structural/flyweight) 31 | * * [Proxy](https://github.com/ZXZs/elixir-design-patterns/tree/master/structural/proxy) 32 | -------------------------------------------------------------------------------- /behavioral/chain/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ``` 4 | $ iex -pr _run.exs 5 | Interactive Elixir (1.9.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help) 6 | 7 | iex(1)> {:ok, file} = FileLogger.start_link() 8 | {:ok, #PID<0.111.0>} 9 | 10 | iex(2)> {:ok, email} = EmailLogger.start_link() 11 | {:ok, #PID<0.113.0>} 12 | 13 | iex(3)> {:ok, console} = ConsoleLogger.start_link(file, email) 14 | {:ok, #PID<0.115.0>} 15 | 16 | iex(4)> console |> ConsoleLogger.log("Just a message") 17 | Logging to console: Just a message 18 | :ok 19 | 20 | iex(5)> console |> ConsoleLogger.warn("Oh shit, something is wrong") 21 | Logging to console: Oh shit, something is wrong 22 | :ok 23 | Logging to file: Oh shit, something is wrong 24 | 25 | iex(6)> console |> ConsoleLogger.error("HERE WE GO AGAIN! SHUTTING Down.......") 26 | Logging to console: HERE WE GO AGAIN! SHUTTING Down....... 27 | :ok 28 | Logging to email: HERE WE GO AGAIN! SHUTTING Down....... 29 | ``` -------------------------------------------------------------------------------- /behavioral/chain/run.exs: -------------------------------------------------------------------------------- 1 | defmodule FileLogger do 2 | def init(:ok) do 3 | {:ok, nil} 4 | end 5 | 6 | def handle_cast({:wrn, msg}, nil) do 7 | IO.puts "Logging to file: #{msg}" 8 | {:noreply, nil} 9 | end 10 | 11 | ########################################################################### 12 | 13 | def start_link do 14 | GenServer.start_link __MODULE__, :ok 15 | end 16 | 17 | def warn(pid, msg) do 18 | GenServer.cast(pid, {:wrn, msg}) 19 | end 20 | end 21 | 22 | defmodule EmailLogger do 23 | def init(:ok) do 24 | {:ok, nil} 25 | end 26 | 27 | def handle_cast({:err, msg}, nil) do 28 | IO.puts "Logging to email: #{msg}" 29 | {:noreply, nil} 30 | end 31 | 32 | ########################################################################### 33 | 34 | def start_link do 35 | GenServer.start_link __MODULE__, :ok 36 | end 37 | 38 | def error(pid, msg) do 39 | GenServer.cast(pid, {:err, msg}) 40 | end 41 | end 42 | 43 | defmodule ConsoleLogger do 44 | use GenServer 45 | 46 | def init([file: file, email: email]) do 47 | {:ok, [file: file, email: email]} 48 | end 49 | 50 | def handle_cast({:log, msg}, settings) do 51 | IO.puts "Logging to console: #{msg}" 52 | {:noreply, settings} 53 | end 54 | 55 | def handle_cast({:wrn, msg}, settings) do 56 | IO.puts "Logging to console: #{msg}" 57 | settings[:file] |> FileLogger.warn(msg) 58 | {:noreply, settings} 59 | end 60 | 61 | def handle_cast({:err, msg}, settings) do 62 | IO.puts "Logging to console: #{msg}" 63 | settings[:email] |> EmailLogger.error(msg) 64 | {:noreply, settings} 65 | end 66 | 67 | ########################################################################### 68 | 69 | def start_link(file, email) do 70 | GenServer.start_link __MODULE__, [file: file, email: email] 71 | end 72 | 73 | def log(pid, msg) do 74 | GenServer.cast(pid, {:log, msg}) 75 | end 76 | 77 | def warn(pid, msg) do 78 | GenServer.cast(pid, {:wrn, msg}) 79 | end 80 | 81 | def error(pid, msg) do 82 | GenServer.cast(pid, {:err, msg}) 83 | end 84 | end -------------------------------------------------------------------------------- /behavioral/command/README.md: -------------------------------------------------------------------------------- 1 | # Run 2 | 3 | ``` 4 | elixir -r commands.exs \ 5 | -r runner.exs \ 6 | run.exs 7 | ``` 8 | -------------------------------------------------------------------------------- /behavioral/command/commands.exs: -------------------------------------------------------------------------------- 1 | # Each command is a module with an `execute` and `unexecute` function 2 | # 3 | # `execute` runs the command in forward direction 4 | # `unexecute` runs the command in reverse direction 5 | # 6 | # All the state is passed in parameters to `execute`/`unexecute` 7 | defmodule CopyFile do 8 | def execute(source, destination) do 9 | IO.puts("cp #{source} #{destination}") 10 | end 11 | 12 | def unexecute(_source, destination) do 13 | IO.puts("rm #{destination}") 14 | end 15 | end 16 | 17 | defmodule CreateFile do 18 | def execute(path, data) do 19 | IO.puts(~s|echo "#{data}" > #{path}|) 20 | end 21 | 22 | def unexecute(path, _data) do 23 | IO.puts("mv #{path} /path/to/trash") 24 | end 25 | end 26 | 27 | defmodule DeleteFile do 28 | def execute(path) do 29 | IO.puts("mv #{path} /path/to/trash") 30 | end 31 | 32 | def unexecute(path) do 33 | IO.puts("mv /path/to/trash/#{Path.basename(path)} #{path}") 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /behavioral/command/run.exs: -------------------------------------------------------------------------------- 1 | # Start the runner 2 | Runner.start_link() 3 | 4 | # Make a list of commands 5 | # Each is a tuple of command module and arg list 6 | commands = [ 7 | {CreateFile, ["file1.txt", "hello world"]}, 8 | {CopyFile, ["file1.txt", "file2.txt"]}, 9 | {DeleteFile, "file1.txt"} 10 | ] 11 | 12 | # Execute the commands 13 | Runner.execute(commands) 14 | 15 | # Sleep a bit to simulate work 16 | Process.sleep(2000) 17 | 18 | IO.puts("....undoing....") 19 | 20 | # Undo all 3 commands 21 | Runner.undo() 22 | Runner.undo() 23 | Runner.undo() 24 | 25 | # Sleep a bit to simulate work 26 | Process.sleep(2000) 27 | 28 | IO.puts("....redoing....") 29 | 30 | # Redo all 3 commands 31 | Runner.redo() 32 | Runner.redo() 33 | Runner.redo() 34 | -------------------------------------------------------------------------------- /behavioral/command/run.sh: -------------------------------------------------------------------------------- 1 | elixir -r commands.exs \ 2 | -r runner.exs \ 3 | run.exs 4 | -------------------------------------------------------------------------------- /behavioral/command/runner.exs: -------------------------------------------------------------------------------- 1 | # A GenServer is used to hold a stack of each command 2 | # 3 | # There are 2 stacks/lists: 4 | # - The `done` stack tracks commands that have been executed 5 | # - The `undone` stack tracks command that have been undone. 6 | # This allows to undo and later redo commands. 7 | # 8 | # We can undo or redo any command 9 | defmodule Runner do 10 | # Using the GenServer DSL 11 | use GenServer 12 | 13 | # Start the GenServer 14 | def start_link do 15 | # start with an empty `done` & `undone` stack 16 | state = %{done: [], undone: []} 17 | 18 | # Start 'er up 19 | GenServer.start_link(__MODULE__, state, name: __MODULE__) 20 | end 21 | 22 | # Not doing anything during initialization, just return the state 23 | def init(state) do 24 | {:ok, state} 25 | end 26 | 27 | # `execute` calls the GenServer and passes the list of `commands` to execute 28 | def execute(commands), 29 | do: GenServer.call(__MODULE__, {:execute, commands}) 30 | 31 | # `undo` calls the GenServer, which will undo one command 32 | def undo(), 33 | do: GenServer.call(__MODULE__, :undo) 34 | 35 | # `redo` calls the GenServer, which will redo one command 36 | def redo(), 37 | do: GenServer.call(__MODULE__, :redo) 38 | 39 | # handles the `execute` message 40 | def handle_call({:execute, commands}, _from, state) do 41 | # wrap the commands into a list 42 | # in case a single command was passed 43 | commands = List.wrap(commands) 44 | 45 | # iterate the list of commands 46 | # each command is a tuple with module (ie `CopyFile`) and arg list (ie `["foo.txt"]`) 47 | Enum.each(commands, fn {mod, args} -> 48 | apply(mod, :execute, List.wrap(args)) 49 | end) 50 | 51 | # add the commands to the `done` stack 52 | new_state = %{state | done: Enum.reverse(commands) ++ state.done} 53 | 54 | # reply `:ok` and update the state to `new_state` 55 | {:reply, :ok, new_state} 56 | end 57 | 58 | # handles the `undo` message 59 | # pattern matches the `done` list, extracting the first command and the tail 60 | def handle_call(:undo, _from, state = %{done: [command = {mod, args} | t]}) do 61 | # dynamically call `unexecute` on the commmand module 62 | apply(mod, :unexecute, List.wrap(args)) 63 | 64 | # update the `done` stack to the tail 65 | # move the command to the `undone` list, in case we want to redo the command later 66 | new_state = %{state | done: t, undone: [command | state.undone]} 67 | 68 | # reply `:ok` and update the state to `new_state` 69 | {:reply, :ok, new_state} 70 | end 71 | 72 | # handles the `redo` message 73 | # pattern matches the `undone` list, extracting the first command and the tail 74 | def handle_call(:redo, _from, state = %{undone: [command = {mod, args} | t]}) do 75 | # dynamically call `execute` on the commmand module 76 | apply(mod, :execute, List.wrap(args)) 77 | 78 | # update the `undone` stack to the tail 79 | # move the command to the `done` list, in case we want to undo the command later 80 | new_state = %{state | undone: t, done: [command | state.done]} 81 | 82 | # reply `:ok` and update the state to `new_state` 83 | {:reply, :ok, new_state} 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /behavioral/interpreter/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ``` 4 | $ iex -pr run.exs 5 | Interactive Elixir (1.9.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help) 6 | 7 | iex(1)> import Interpreter 8 | Interpreter 9 | 10 | iex(2)> interpret :+, [5, 5] 11 | 10 12 | 13 | iex(3)> interpret :*, [5, 5] 14 | 25 15 | 16 | iex(4)> interpret :-, [5, 5] 17 | 0 18 | 19 | iex(5)> interpret :/, [5, 5] 20 | 1 21 | ``` -------------------------------------------------------------------------------- /behavioral/interpreter/run.exs: -------------------------------------------------------------------------------- 1 | defmodule Interpreter do 2 | def interpret(operator, args) do 3 | case operator do 4 | :+ -> 5 | args |> 6 | Enum.sum 7 | 8 | :* -> 9 | args |> 10 | Enum.reduce(fn x, acc -> acc * x end) 11 | 12 | :- -> 13 | args |> 14 | Enum.reduce(fn x, acc -> acc - x end) 15 | 16 | :/ -> 17 | args |> 18 | Enum.reduce(fn x, acc -> acc / x end) |> 19 | round 20 | end 21 | end 22 | end -------------------------------------------------------------------------------- /behavioral/iterator/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ``` 4 | $ iex -pr run.exs 5 | Interactive Elixir (1.9.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help) 6 | 7 | iex(1)> {:ok, iterator} = Iterator.new [1, 2, 3, 4, 5] 8 | {:ok, #PID<0.107.0>} 9 | 10 | iex(2)> if (iterator |> Iterator.next?), do: (iterator |> Iterator.next), else: (nil) 11 | 1 12 | 13 | iex(3)> if (iterator |> Iterator.next?), do: (iterator |> Iterator.next), else: (nil) 14 | 2 15 | 16 | iex(4)> if (iterator |> Iterator.next?), do: (iterator |> Iterator.next), else: (nil) 17 | 3 18 | 19 | iex(5)> if (iterator |> Iterator.next?), do: (iterator |> Iterator.next), else: (nil) 20 | 4 21 | 22 | iex(6)> if (iterator |> Iterator.next?), do: (iterator |> Iterator.next), else: (nil) 23 | 5 24 | 25 | iex(7)> if (iterator |> Iterator.next?), do: (iterator |> Iterator.next), else: (nil) 26 | nil 27 | ``` -------------------------------------------------------------------------------- /behavioral/iterator/run.exs: -------------------------------------------------------------------------------- 1 | defmodule Iterator do 2 | def init({aggregate, index}), do: 3 | {:ok, {aggregate, index}} 4 | 5 | def new(aggregate, index \\ -1), do: 6 | GenServer.start_link __MODULE__, {aggregate, index} 7 | 8 | 9 | 10 | def handle_call(:next?, _from, {aggregate, index}), do: 11 | if not (aggregate |> Enum.at(index + 1) |> is_nil), do: 12 | {:reply, true, {aggregate, index}}, 13 | else: 14 | {:reply, false, {aggregate, index}} 15 | 16 | def next?(pid), do: 17 | GenServer.call(pid, :next?) 18 | 19 | 20 | 21 | def handle_call(:next, _from, {aggregate, index}) do 22 | {:reply, Enum.at(aggregate, index + 1), {aggregate, index + 1}} 23 | end 24 | 25 | def next(pid) do 26 | GenServer.call(pid, :next) 27 | end 28 | end -------------------------------------------------------------------------------- /behavioral/mediator/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ``` 4 | $ iex -pr run.exs 5 | Interactive Elixir (1.9.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help) 6 | 7 | iex(1)> {:ok, mediator} = Mediator.start_link 8 | {:ok, #PID<0.109.0>} 9 | 10 | iex(2)> {:ok, user1} = User.start_link(mediator) 11 | {:ok, #PID<0.111.0>} 12 | 13 | iex(3)> {:ok, user2} = User.start_link(mediator) 14 | {:ok, #PID<0.113.0>} 15 | 16 | iex(4)> user1 |> User.send(["Hello, user2!", to: user2]) 17 | :ok 18 | ``` -------------------------------------------------------------------------------- /behavioral/mediator/run.exs: -------------------------------------------------------------------------------- 1 | defmodule User do 2 | use GenServer 3 | 4 | def init(mediator) do 5 | {:ok, mediator} 6 | end 7 | 8 | def handle_cast({:send, [message, to: to]}, mediator) do 9 | mediator |> Mediator.send([message, from: self(), to: to]) 10 | {:noreply, mediator} 11 | end 12 | 13 | def handle_cast({:receive, [message, from: from]}, mediator) do 14 | IO.inspect [message, from: from] 15 | {:noreply, mediator} 16 | end 17 | 18 | ########################################################################### 19 | 20 | def start_link(mediator) do 21 | GenServer.start_link(__MODULE__, :ok, [mediator]) 22 | end 23 | 24 | def send(user, [message, to: to]) do 25 | GenServer.cast(user, {:send, [message, to: to]}) 26 | end 27 | 28 | def receive(user, [message, from: from]) do 29 | GenServer.cast(user, {:receive, [message, from: from]}) 30 | end 31 | end 32 | 33 | defmodule Mediator do 34 | use GenServer 35 | 36 | def init(:ok) do 37 | {:ok, true} 38 | end 39 | 40 | def handle_cast({:send, [message, from: from, to: to]}) do 41 | to |> User.receive([message, from: from]) 42 | :noreply 43 | end 44 | 45 | ########################################################################### 46 | 47 | def start_link do 48 | GenServer.start_link(__MODULE__, :ok, []) 49 | end 50 | 51 | def send(mediator, [message, from: from, to: to]) do 52 | GenServer.cast mediator, {:send, [message, from: from, to: to]} 53 | end 54 | end -------------------------------------------------------------------------------- /behavioral/memento/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | $ iex -pr run.exs 3 | Interactive Elixir (1.9.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help) 4 | 5 | iex(1)> {:ok, game} = Game.init() 6 | {:ok, #PID<0.111.0>} 7 | 8 | iex(2)> game |> Game.set("LEVEL 1", 30_000) 9 | :ok 10 | 11 | iex(3)> game |> Game.get() 12 | %{level: "LEVEL 1", ms: 30000} 13 | 14 | iex(4)> {:ok, file} = App.File.init() 15 | {:ok, #PID<0.115.0>} 16 | 17 | iex(5)> {:ok, save} = Save.init(Game.get(game)[:level], Game.get(game)[:ms]) 18 | {:ok, #PID<0.117.0>} 19 | 20 | iex(6)> file |> App.File.set(save) 21 | :ok 22 | 23 | iex(7)> game |> Game.set("LEVEL 2", 55_000) 24 | :ok 25 | 26 | iex(8)> game |> Game.get() 27 | %{level: "LEVEL 2", ms: 55000} 28 | 29 | iex(9)> game |> Game.load(save) 30 | :ok 31 | 32 | iex(10)> game |> Game.get() 33 | %{level: "LEVEL 1", ms: 30000} 34 | ``` -------------------------------------------------------------------------------- /behavioral/memento/run.exs: -------------------------------------------------------------------------------- 1 | defmodule Save do 2 | use Agent 3 | 4 | def init(level, ms) do 5 | Agent.start_link (fn -> %{ 6 | level: level, 7 | ms: ms , 8 | } 9 | end) 10 | end 11 | 12 | def get(save) do 13 | Agent.get(save, fn x -> x end) 14 | end 15 | end 16 | 17 | defmodule Game do 18 | use Agent 19 | 20 | def init(level \\ nil, ms \\ nil) do 21 | Agent.start_link (fn -> %{ 22 | level: level, 23 | ms: ms , 24 | } end) 25 | end 26 | 27 | def set(game, level, ms) do 28 | game |> Agent.update(fn x -> 29 | x 30 | |> Map.put(:level, level) 31 | |> Map.put(:ms , ms ) 32 | end) 33 | end 34 | 35 | def get(game) do 36 | Agent.get(game, fn x -> x end) 37 | end 38 | 39 | def load(game, save) do 40 | level = (save |> Save.get())[:level] 41 | ms = (save |> Save.get())[:ms ] 42 | 43 | game |> set(level, ms) 44 | end 45 | 46 | def save(game) do 47 | level = get(game)[:level] 48 | ms = get(game)[:ms ] 49 | 50 | { :ok, save } = Save.init(level, ms); save 51 | end 52 | end 53 | 54 | defmodule App.File do 55 | use Agent 56 | 57 | def init(save \\ nil) do 58 | Agent.start_link fn -> %{ save: save } end 59 | end 60 | 61 | def get(file) do 62 | file |> Agent.get(fn x -> x end) 63 | end 64 | 65 | def set(file, save) do 66 | file |> Agent.update(fn x -> 67 | x |> Map.put(:save, save) 68 | end) 69 | end 70 | end -------------------------------------------------------------------------------- /behavioral/observer/README.md: -------------------------------------------------------------------------------- 1 | # Run 2 | 3 | ``` 4 | elixir -r observer/employee.exs \ 5 | -r observer/payroll.exs \ 6 | -r observer/tax_man.exs \ 7 | -r observer/hr.exs \ 8 | observer/run.exs 9 | ``` 10 | -------------------------------------------------------------------------------- /behavioral/observer/employee.exs: -------------------------------------------------------------------------------- 1 | defmodule Employee do 2 | defstruct name: nil, title: nil, salary: 0 3 | end 4 | -------------------------------------------------------------------------------- /behavioral/observer/hr.exs: -------------------------------------------------------------------------------- 1 | defmodule HR do 2 | use GenServer 3 | 4 | def start_link(employee, events) do 5 | state = %{events: events, employee: employee} 6 | 7 | GenServer.start_link(__MODULE__, state, name: __MODULE__) 8 | end 9 | 10 | def handle_call({:update, changes}, _from, state) do 11 | updated = Map.merge(state.employee, changes) 12 | 13 | GenEvent.notify(state.events, {:changed, updated}) 14 | 15 | {:reply, :ok, %{state | employee: updated}} 16 | end 17 | 18 | def update(changes), 19 | do: GenServer.call(__MODULE__, {:update, changes}) 20 | end 21 | -------------------------------------------------------------------------------- /behavioral/observer/payroll.exs: -------------------------------------------------------------------------------- 1 | defmodule Payroll do 2 | use GenEvent 3 | 4 | def handle_event({:changed, employee}, state) do 5 | IO.puts("Cut a new check for #{employee.name}!") 6 | IO.puts("His salary is now #{employee.salary}!") 7 | 8 | {:ok, state} 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /behavioral/observer/run.exs: -------------------------------------------------------------------------------- 1 | fred = %Employee{name: "Fred Flinstone", title: "Crane Operator", salary: 30000} 2 | 3 | {:ok, pid} = GenEvent.start_link() 4 | 5 | HR.start_link(fred, pid) 6 | 7 | GenEvent.add_handler(pid, Payroll, []) 8 | GenEvent.add_handler(pid, TaxMan, []) 9 | 10 | HR.update(%{salary: 35000}) 11 | -------------------------------------------------------------------------------- /behavioral/observer/tax_man.exs: -------------------------------------------------------------------------------- 1 | defmodule TaxMan do 2 | use GenEvent 3 | 4 | def handle_event({:changed, employee}, state) do 5 | IO.puts("Send #{employee.name} a new tax bill!") 6 | 7 | {:ok, state} 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /behavioral/state/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ``` 4 | $ iex -pr run.exs 5 | Interactive Elixir (1.9.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help) 6 | 7 | iex(1)> {:ok, editor} = TextEditor.start_link 8 | {:ok, #PID<0.113.0>} 9 | 10 | iex(2)> editor |> TextEditor.set(UpperCaseState) 11 | :ok 12 | 13 | iex(3)> editor |> TextEditor.edit("hello world!") 14 | "HELLO WORLD!" 15 | ``` -------------------------------------------------------------------------------- /behavioral/state/run.exs: -------------------------------------------------------------------------------- 1 | defmodule NormalCaseState do 2 | def use(text) do 3 | text |> String.capitalize 4 | end 5 | end 6 | 7 | defmodule UpperCaseState do 8 | def use(text) do 9 | text |> String.upcase 10 | end 11 | end 12 | 13 | defmodule LowerCaseState do 14 | def use(text) do 15 | text |> String.downcase 16 | end 17 | end 18 | 19 | ############################################################################### 20 | 21 | defmodule TextEditor do 22 | use GenServer 23 | 24 | def init(:ok) do 25 | {:ok, nil} 26 | end 27 | 28 | def handle_cast({:state, new_state}, _old_state) do 29 | {:noreply, new_state} 30 | end 31 | 32 | def handle_call({:text, text}, _from, state) do 33 | {:reply, state.use(text), state} 34 | end 35 | 36 | ############################################################################# 37 | 38 | def start_link do 39 | GenServer.start_link(__MODULE__, :ok) 40 | end 41 | 42 | def set(server, state) do 43 | GenServer.cast(server, {:state, state}) 44 | end 45 | 46 | def edit(server, text) do 47 | GenServer.call(server, {:text, text}) 48 | end 49 | end -------------------------------------------------------------------------------- /behavioral/strategy/README.md: -------------------------------------------------------------------------------- 1 | # Run 2 | 3 | ``` 4 | elixir -r strategy/report.exs \ 5 | -r strategy/formatter.exs \ 6 | -r strategy/html_formatter.exs \ 7 | -r strategy/plain_text_formatter.exs \ 8 | strategy/run.exs 9 | ``` 10 | -------------------------------------------------------------------------------- /behavioral/strategy/formatter.exs: -------------------------------------------------------------------------------- 1 | defmodule Formatter do 2 | @callback output_report(context :: map) :: nil 3 | end 4 | -------------------------------------------------------------------------------- /behavioral/strategy/html_formatter.exs: -------------------------------------------------------------------------------- 1 | defmodule HTMLFormatter do 2 | @behaviour Formatter 3 | 4 | def output_report(context) do 5 | IO.puts("") 6 | IO.puts(" ") 7 | IO.puts(" #{context.title}") 8 | IO.puts(" ") 9 | IO.puts(" ") 10 | 11 | Enum.each(context.text, fn line -> 12 | IO.puts("

#{line}

") 13 | end) 14 | 15 | IO.puts(" ") 16 | IO.puts("") 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /behavioral/strategy/plain_text_formatter.exs: -------------------------------------------------------------------------------- 1 | defmodule PlainTextFormatter do 2 | @behaviour Formatter 3 | 4 | def output_report(context) do 5 | IO.puts("***** #{context.title} *****") 6 | 7 | Enum.each(context.text, &IO.puts/1) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /behavioral/strategy/report.exs: -------------------------------------------------------------------------------- 1 | defmodule Report do 2 | use GenServer 3 | 4 | def start_link(formatter) do 5 | GenServer.start_link(__MODULE__, formatter, name: __MODULE__) 6 | end 7 | 8 | def init(formatter), 9 | do: {:ok, formatter} 10 | 11 | def handle_call({:output_report, context}, _from, formatter) do 12 | formatter.output_report(context) 13 | 14 | {:reply, :ok, formatter} 15 | end 16 | 17 | def handle_call({:change_format, new_formatter}, _from, _state), 18 | do: {:reply, :ok, new_formatter} 19 | 20 | # Public APIs 21 | def output_report(context), 22 | do: GenServer.call(__MODULE__, {:output_report, context}) 23 | 24 | def change_format(formatter), 25 | do: GenServer.call(__MODULE__, {:change_format, formatter}) 26 | end 27 | -------------------------------------------------------------------------------- /behavioral/strategy/run.exs: -------------------------------------------------------------------------------- 1 | # start server with default formatter 2 | {:ok, _} = Report.start_link(HTMLFormatter) 3 | 4 | context = %{ 5 | title: "Monthly Report", 6 | text: ["Things are going", "really, really well"] 7 | } 8 | 9 | Report.output_report(context) 10 | 11 | # Change the formatter at runtime 12 | Report.change_format(PlainTextFormatter) 13 | Report.output_report(context) 14 | -------------------------------------------------------------------------------- /behavioral/strategy/using_fn/README.md: -------------------------------------------------------------------------------- 1 | # Run 2 | 3 | ``` 4 | elixir -r strategy/using_fn/report.exs \ 5 | strategy/using_fn/run.exs 6 | ``` 7 | -------------------------------------------------------------------------------- /behavioral/strategy/using_fn/report.exs: -------------------------------------------------------------------------------- 1 | defmodule Report do 2 | use GenServer 3 | 4 | def start_link(formatter) do 5 | GenServer.start_link(__MODULE__, formatter, name: __MODULE__) 6 | end 7 | 8 | def init(formatter), 9 | do: {:ok, formatter} 10 | 11 | def handle_call({:output_report, context}, _from, formatter) do 12 | formatter.(context) 13 | 14 | {:reply, :ok, formatter} 15 | end 16 | 17 | def handle_call({:change_format, new_formatter}, _from, _state), 18 | do: {:reply, :ok, new_formatter} 19 | 20 | # Public APIs 21 | def output_report(context), 22 | do: GenServer.call(__MODULE__, {:output_report, context}) 23 | 24 | def change_format(formatter), 25 | do: GenServer.call(__MODULE__, {:change_format, formatter}) 26 | end 27 | -------------------------------------------------------------------------------- /behavioral/strategy/using_fn/run.exs: -------------------------------------------------------------------------------- 1 | # start server with default formatter 2 | html = fn context -> 3 | IO.puts("") 4 | IO.puts(" ") 5 | IO.puts(" #{context.title}") 6 | IO.puts(" ") 7 | IO.puts(" ") 8 | 9 | Enum.each(context.text, fn line -> 10 | IO.puts("

#{line}

") 11 | end) 12 | 13 | IO.puts(" ") 14 | IO.puts("") 15 | end 16 | 17 | plain_text = fn context -> 18 | IO.puts("***** #{context.title} *****") 19 | Enum.each(context.text, &IO.puts/1) 20 | end 21 | 22 | context = %{ 23 | title: "Monthly Report", 24 | text: ["Things are going", "really, really well"] 25 | } 26 | 27 | {:ok, _} = Report.start_link(html) 28 | 29 | Report.output_report(context) 30 | 31 | # Change the formatter at runtime 32 | Report.change_format(plain_text) 33 | Report.output_report(context) 34 | -------------------------------------------------------------------------------- /behavioral/template_method/README.md: -------------------------------------------------------------------------------- 1 | # Run 2 | 3 | ``` 4 | elixir -r template_method/report.exs \ 5 | -r template_method/html_report.exs \ 6 | -r template_method/plain_text_report.exs \ 7 | template_method/run.exs 8 | ``` 9 | -------------------------------------------------------------------------------- /behavioral/template_method/html_report.exs: -------------------------------------------------------------------------------- 1 | defmodule HTMLReport do 2 | use Report 3 | 4 | def output_start, 5 | do: IO.puts("") 6 | 7 | def output_head do 8 | IO.puts(" ") 9 | IO.puts(" #{@title}") 10 | IO.puts(" ") 11 | end 12 | 13 | def output_body_start, 14 | do: IO.puts("") 15 | 16 | def output_line(line), 17 | do: IO.puts("

#{line}

") 18 | 19 | def output_body_end, 20 | do: IO.puts("") 21 | 22 | def output_end, 23 | do: IO.puts("") 24 | end 25 | -------------------------------------------------------------------------------- /behavioral/template_method/plain_text_report.exs: -------------------------------------------------------------------------------- 1 | defmodule PlainTextReport do 2 | use Report 3 | 4 | def output_head, 5 | do: IO.puts("**** #{@title} ****\n") 6 | 7 | def output_line(line), 8 | do: IO.puts(line) 9 | end 10 | -------------------------------------------------------------------------------- /behavioral/template_method/report.exs: -------------------------------------------------------------------------------- 1 | defmodule Report do 2 | defmacro __using__(_) do 3 | quote do 4 | @title "Monthly Report" 5 | @text ["Things are going", "really really well"] 6 | 7 | def output_report do 8 | output_start 9 | output_head 10 | output_body_start 11 | output_lines 12 | output_body_end 13 | output_end 14 | end 15 | 16 | def output_start, do: nil 17 | def output_head, do: nil 18 | def output_body_start, do: nil 19 | 20 | def output_lines, 21 | do: Enum.each(@text, &output_line/1) 22 | 23 | def output_line(line), 24 | do: raise(ArgumentError, "not implemented") 25 | 26 | def output_body_end, do: nil 27 | def output_end, do: nil 28 | 29 | defoverridable output_start: 0, 30 | output_head: 0, 31 | output_body_start: 0, 32 | output_line: 1, 33 | output_body_end: 0, 34 | output_end: 0 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /behavioral/template_method/run.exs: -------------------------------------------------------------------------------- 1 | report = HTMLReport 2 | report.output_report 3 | 4 | report = PlainTextReport 5 | report.output_report 6 | -------------------------------------------------------------------------------- /behavioral/visitor/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ``` 4 | $ iex -pr run.exs 5 | Interactive Elixir (1.9.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help) 6 | 7 | iex(1)> {:ok, engine} = Engine.new 8 | {:ok, #PID<0.121.0>} 9 | 10 | iex(2)> {:ok, body} = Body.new 11 | {:ok, #PID<0.123.0>} 12 | 13 | iex(3)> {:ok, gopnik} = Gopnik.new 14 | {:ok, #PID<0.125.0>} 15 | 16 | iex(4)> {:ok, mechanic} = Mechanic.new 17 | {:ok, #PID<0.127.0>} 18 | 19 | iex(5)> engine |> Engine.accept(gopnik) 20 | Starting engine... 21 | :ok 22 | 23 | iex(6)> engine |> Engine.accept(mechanic) 24 | Checking engine... 25 | :ok 26 | 27 | iex(7)> body |> Body.accept(mechanic) 28 | Clearing body... 29 | :ok 30 | 31 | iex(8)> body |> Body.accept(gopnik) 32 | Kicking body... 33 | :ok 34 | ``` -------------------------------------------------------------------------------- /behavioral/visitor/run.exs: -------------------------------------------------------------------------------- 1 | defmodule Visitor do 2 | defmacro __using__(_opts) do 3 | quote do 4 | def init(nil) do 5 | {:ok, nil} 6 | end 7 | 8 | def new do 9 | GenServer.start_link __MODULE__, nil 10 | end 11 | 12 | def visit(pid, type, element) do 13 | GenServer.cast(pid, {:visit, type, element}) 14 | end 15 | end 16 | end 17 | end 18 | 19 | defmodule Gopnik do 20 | use Visitor 21 | 22 | def handle_cast({:visit, :engine, _engine}, nil) do 23 | IO.puts "Starting engine..." 24 | {:noreply, nil} 25 | end 26 | 27 | def handle_cast({:visit, :body, _body}, nil) do 28 | IO.puts "Kicking body..." 29 | {:noreply, nil} 30 | end 31 | end 32 | 33 | defmodule Mechanic do 34 | use Visitor 35 | 36 | def handle_cast({:visit, :engine, _engine}, nil) do 37 | IO.puts "Checking engine..." 38 | {:noreply, nil} 39 | end 40 | 41 | def handle_cast({:visit, :body, _body}, nil) do 42 | IO.puts "Clearing body..." 43 | {:noreply, nil} 44 | end 45 | end 46 | 47 | defmodule Element do 48 | defmacro __using__(_opts) do 49 | quote do 50 | def init(nil) do 51 | {:ok, nil} 52 | end 53 | 54 | def new do 55 | GenServer.start_link __MODULE__, nil 56 | end 57 | 58 | defmodule AbstractVisitor do 59 | use Visitor 60 | end 61 | end 62 | end 63 | end 64 | 65 | defmodule Engine do 66 | use Element 67 | 68 | def accept(pid, visitor) do 69 | visitor |> AbstractVisitor.visit(:engine, pid) 70 | end 71 | end 72 | 73 | defmodule Body do 74 | use Element 75 | 76 | def accept(pid, visitor) do 77 | visitor |> AbstractVisitor.visit(:body, pid) 78 | end 79 | end -------------------------------------------------------------------------------- /creational/builder/README.md: -------------------------------------------------------------------------------- 1 | # Run 2 | 3 | ``` 4 | elixir -r builder/computer.exs \ 5 | -r builder/builder.exs \ 6 | builder/run.exs 7 | ``` 8 | -------------------------------------------------------------------------------- /creational/builder/builder.exs: -------------------------------------------------------------------------------- 1 | # Define a module with a list of build actions 2 | defmodule ComputerBuilder do 3 | # Start with a blank computer 4 | def base_model, 5 | do: %Computer{} 6 | 7 | # Define some methods to configure the CPU 8 | def intel(computer), 9 | do: %{computer | cpu: :intel} 10 | 11 | # We can use pattern matching 12 | def amd(computer = %Computer{cpu: nil}), 13 | do: %{computer | cpu: :amd} 14 | 15 | # We can use guards 16 | def display(computer, type) when type in ~w(lcd hd wxga)a, 17 | do: %{computer | display: type} 18 | 19 | # We can overwrite values 20 | def set_memory(computer, size), 21 | do: %{computer | memory: size} 22 | 23 | # Or we can append values to arrays, using the `[head|tail]` syntax 24 | def add_disk(computer, drive), 25 | do: %{computer | drives: [drive | computer.drives]} 26 | 27 | def add_hard_disk(computer, size), 28 | do: add_disk(computer, size) 29 | 30 | # Actions can rely on other actions. Here `add_cd` is based on `add_disk` 31 | def add_cd(computer), 32 | do: add_disk(computer, :cd) 33 | 34 | def add_dvd(computer), 35 | do: add_disk(computer, :dvd) 36 | end 37 | -------------------------------------------------------------------------------- /creational/builder/computer.exs: -------------------------------------------------------------------------------- 1 | # Define a struct to hold the data with some defaults 2 | defmodule Computer do 3 | defstruct display: nil, 4 | memory: 0, 5 | cpu: nil, 6 | drives: [] 7 | end 8 | -------------------------------------------------------------------------------- /creational/builder/run.exs: -------------------------------------------------------------------------------- 1 | # Import the builder 2 | import ComputerBuilder 3 | 4 | # Building the computer is done in a sequence of immutable steps 5 | # 6 | # - Each function takes a `Computer` as the first parameter 7 | # - Each function returns a new `Computer` struct 8 | # 9 | # It allows us to chain the steps together using pipes `|>` and save the last value 10 | computer = 11 | base_model() 12 | |> intel 13 | |> display(:lcd) 14 | |> set_memory(12_000_000_000) 15 | |> add_cd 16 | |> add_dvd 17 | |> add_hard_disk(256_000_000_000) 18 | 19 | # Print it out 20 | IO.inspect(computer, label: "Your brand new computer") 21 | -------------------------------------------------------------------------------- /creational/builder/run.sh: -------------------------------------------------------------------------------- 1 | elixir -r computer.exs \ 2 | -r builder.exs \ 3 | run.exs 4 | -------------------------------------------------------------------------------- /creational/factory/README.md: -------------------------------------------------------------------------------- 1 | # Run 2 | 3 | ``` 4 | elixir -r factory/shape_factory.exs \ 5 | -r factory/graphics.exs \ 6 | factory/run.exs 7 | ``` 8 | -------------------------------------------------------------------------------- /creational/factory/graphics.exs: -------------------------------------------------------------------------------- 1 | defmodule Graphics do 2 | def draw({:circle, d}), 3 | do: IO.puts("Drawing circle, diameter: #{d}") 4 | 5 | def draw({:rectangle, w, h}), 6 | do: IO.puts("Drawing rectangle, dimensions: #{w}*#{h}") 7 | end 8 | -------------------------------------------------------------------------------- /creational/factory/run.exs: -------------------------------------------------------------------------------- 1 | factory = ShapeFactory 2 | 3 | circle = factory.create(:circle, %{diameter: 10}) 4 | Graphics.draw(circle) 5 | 6 | square = factory.create(:square, %{size: 20}) 7 | Graphics.draw(square) 8 | 9 | rectangle = factory.create(:rectangle, %{width: 5, height: 15}) 10 | Graphics.draw(rectangle) 11 | -------------------------------------------------------------------------------- /creational/factory/run.sh: -------------------------------------------------------------------------------- 1 | elixir -r shape_factory.exs \ 2 | -r graphics.exs \ 3 | run.exs 4 | -------------------------------------------------------------------------------- /creational/factory/shape_factory.exs: -------------------------------------------------------------------------------- 1 | # Factories are responsible for constructing things 2 | # 3 | # They are useful when you construct similar things in many places 4 | # or when you don't want to make the caller responsible for understanding how to construct (if it's complicated) 5 | defmodule ShapeFactory do 6 | # The factory pattern matches to create different things 7 | def create(:circle, %{diameter: diameter}), do: {:circle, diameter} 8 | def create(:circle, %{radius: radius}), do: {:circle, radius * 2} 9 | 10 | # We can construct rectangles in 2 different ways 11 | def create(:rectangle, %{width: width, height: height}), do: {:rectangle, width, height} 12 | def create(:rectangle, %{x1: x1, x2: x2, y1: y1, y2: y2}), do: {:rectangle, x2 - x1, y2 - y1} 13 | 14 | # We create rectangles when asked for a square (mathimatically wrong, but whatevs) 15 | def create(:square, %{size: size}), do: {:rectangle, size, size} 16 | end 17 | -------------------------------------------------------------------------------- /creational/factory_method/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | $ iex -pr run.exs 3 | Interactive Elixir (1.9.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help) 4 | 5 | iex(1)> {:ok, dev} = PanelDeveloper.new("Kirpich Corporation") 6 | {:ok, #PID<0.117.0>} 7 | 8 | iex(2)> house2 = dev |> PanelDeveloper.create() 9 | Panel house built 10 | #PID<0.119.0> 11 | 12 | iex(3)> {:ok, dev} = WoodDeveloper.new("NoName Corporation") 13 | {:ok, #PID<0.121.0>} 14 | 15 | iex(4)> house = dev |> WoodDeveloper.create() 16 | Wood house built 17 | #PID<0.123.0> 18 | 19 | iex(5)> IO.inspect [dev: dev, houses: {house, house2}] 20 | [dev: #PID<0.121.0>, houses: {#PID<0.123.0>, #PID<0.119.0>}] 21 | [dev: #PID<0.121.0>, houses: {#PID<0.123.0>, #PID<0.119.0>}] 22 | ``` -------------------------------------------------------------------------------- /creational/factory_method/run.exs: -------------------------------------------------------------------------------- 1 | defmodule House do 2 | defmacro __using__(_opts) do 3 | quote do 4 | def init(:ok) do 5 | {:ok, true} 6 | end 7 | 8 | defoverridable init: 1 9 | 10 | def build do 11 | GenServer.start_link __MODULE__, :ok 12 | end 13 | end 14 | end 15 | end 16 | 17 | defmodule PanelHouse do 18 | use House 19 | 20 | def init(:ok) do 21 | IO.puts "Panel house built" 22 | super(:ok) 23 | end 24 | end 25 | 26 | defmodule WoodHouse do 27 | use House 28 | 29 | def init(:ok) do 30 | IO.puts "Wood house built" 31 | super(:ok) 32 | end 33 | end 34 | 35 | ############################################################################### 36 | 37 | defmodule Developer do 38 | defmacro __using__(_opts) do 39 | quote do 40 | def init(name) do 41 | {:ok, name} 42 | end 43 | 44 | def new(name) do 45 | GenServer.start_link __MODULE__, name 46 | end 47 | 48 | def create(pid) do 49 | raise "#{__MODULE__}.create is undefined" 50 | end 51 | 52 | defoverridable create: 1 53 | end 54 | end 55 | end 56 | 57 | defmodule PanelDeveloper do 58 | use Developer 59 | 60 | def handle_call(:create, _from, name) do 61 | {:ok, house} = PanelHouse.build() 62 | {:reply, house, name} 63 | end 64 | 65 | def create(pid) do 66 | GenServer.call(pid, :create) 67 | end 68 | end 69 | 70 | defmodule WoodDeveloper do 71 | use Developer 72 | 73 | def handle_call(:create, _from, name) do 74 | {:ok, house} = WoodHouse.build() 75 | {:reply, house, name} 76 | end 77 | 78 | def create(pid) do 79 | GenServer.call(pid, :create) 80 | end 81 | end -------------------------------------------------------------------------------- /creational/prototype/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ``` 4 | $ iex -pr run.exs 5 | Interactive Elixir (1.9.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help) 6 | 7 | iex(1)> original = Human.new("Vasya", 22) 8 | #PID<0.109.0> 9 | 10 | iex(2)> copy = original |> Human.copy() 11 | #PID<0.111.0> 12 | 13 | iex(3)> original |> Human.get() 14 | [name: "Vasya", age: 22] 15 | 16 | iex(4)> copy |> Human.get() 17 | [name: "Vasya", age: 22] 18 | 19 | ``` -------------------------------------------------------------------------------- /creational/prototype/run.exs: -------------------------------------------------------------------------------- 1 | defmodule Copyable do 2 | defmacro __using__(_opts) do 3 | quote do 4 | def copy(_pid) do 5 | raise "#{__MODULE__}.copy is undefined" 6 | end 7 | 8 | defoverridable copy: 1 9 | end 10 | end 11 | end 12 | 13 | defmodule Human do 14 | use Copyable 15 | 16 | def init([name: name, age: age]) do 17 | {:ok, [name: name, age: age]} 18 | end 19 | 20 | def new(name, age) do 21 | (GenServer.start_link __MODULE__, [name: name, age: age]) 22 | |> elem(1) 23 | end 24 | 25 | def handle_call(:get, _from, fields) do 26 | {:reply, fields, fields} 27 | end 28 | 29 | def get(pid) do 30 | GenServer.call(pid, :get) 31 | end 32 | 33 | def handle_call(:copy, _from, fields) do 34 | name = fields[:name] 35 | age = fields[:age ] 36 | {:reply, new(name, age), fields} 37 | end 38 | 39 | def copy(pid) do 40 | GenServer.call(pid, :copy) 41 | end 42 | end -------------------------------------------------------------------------------- /creational/singleton/README.md: -------------------------------------------------------------------------------- 1 | # Run 2 | 3 | ``` 4 | elixir singleton/run.exs 5 | ``` 6 | -------------------------------------------------------------------------------- /creational/singleton/run.exs: -------------------------------------------------------------------------------- 1 | defmodule Singleton do 2 | use GenServer 3 | 4 | # for a singleton specific to this node 5 | @name :the_singleton_name 6 | 7 | # for a singleton across all nodes 8 | # @name {:global, :the_singleton_name} 9 | 10 | @initial_value "starting value" 11 | 12 | def start_link, 13 | do: GenServer.start_link(__MODULE__, @initial_value, name: @name) 14 | 15 | def value, 16 | do: GenServer.call(@name, :read) 17 | 18 | def update(value), 19 | do: GenServer.call(@name, {:write, value}) 20 | 21 | def handle_call(:read, _from, value), 22 | do: {:reply, value, value} 23 | 24 | def handle_call({:write, value}, _from, _old_value), 25 | do: {:reply, :ok, value} 26 | end 27 | 28 | Singleton.start_link() 29 | 30 | Singleton.value() |> IO.puts() 31 | Singleton.update("new value") 32 | Singleton.value() |> IO.puts() 33 | -------------------------------------------------------------------------------- /structural/adapter/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ``` 4 | elixir run.exs 5 | ``` -------------------------------------------------------------------------------- /structural/adapter/run.exs: -------------------------------------------------------------------------------- 1 | defmodule VectorGraphics do 2 | @callback line() :: no_return 3 | @callback square() :: no_return 4 | end 5 | 6 | defmodule RasterGraphics do 7 | def raster_line(), do: 'line' 8 | def raster_square(), do: 'square' 9 | end 10 | 11 | defmodule Adapter do 12 | defdelegate line, 13 | to: RasterGraphics, 14 | as: :raster_line 15 | 16 | defdelegate square, 17 | to: RasterGraphics, 18 | as: :raster_square 19 | end 20 | 21 | defmodule App do 22 | def main() do 23 | {Adapter.line(), Adapter.square()} 24 | end 25 | end 26 | 27 | App.main() |> IO.inspect() -------------------------------------------------------------------------------- /structural/bridge/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ``` 4 | $ iex -pr run.exs 5 | Interactive Elixir (1.9.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help) 6 | 7 | iex(1)> skoda_sedan = {Type.new(:Sedan), Make.new(:Skoda)} 8 | {%Type{details: :Sedan}, %Make{details: :Skoda}} 9 | 10 | iex(2)> kia_hatchback = {Type.new(:Hatchback), Make.new(:Kia)} 11 | {%Type{details: :Hatchback}, %Make{details: :Kia}} 12 | 13 | iex(3)> {:ok, skoda_sedan} = Car.new(skoda_sedan) 14 | {:ok, #PID<0.113.0>} 15 | 16 | iex(4)> {:ok, kia_hatchback} = Car.new(kia_hatchback) 17 | {:ok, #PID<0.115.0>} 18 | 19 | iex(5)> skoda_sedan |> Car.type_details 20 | :Sedan 21 | 22 | iex(6)> kia_hatchback |> Car.make_details 23 | :Kia 24 | ``` -------------------------------------------------------------------------------- /structural/bridge/run.exs: -------------------------------------------------------------------------------- 1 | defmodule Type do 2 | defstruct [:details] 3 | 4 | def new(details), do: % __MODULE__ { details: details } 5 | end 6 | 7 | defmodule Make do 8 | defstruct [:details] 9 | 10 | def new(details), do: % __MODULE__ { details: details } 11 | end 12 | 13 | defmodule Car do 14 | def init({type, make}), do: 15 | {:ok, {type, make}} 16 | 17 | def new({type, make}), do: 18 | GenServer.start_link __MODULE__, {type, make} 19 | 20 | 21 | 22 | def handle_call(:make_details, _from, {type, make}), do: 23 | {:reply, make.details, {type, make}} 24 | 25 | def handle_call(:type_details, _from, {type, make}), do: 26 | {:reply, type.details, {type, make}} 27 | 28 | 29 | 30 | def make_details(pid), do: 31 | GenServer.call(pid, :make_details) 32 | 33 | def type_details(pid), do: 34 | GenServer.call(pid, :type_details) 35 | end -------------------------------------------------------------------------------- /structural/composite/README.md: -------------------------------------------------------------------------------- 1 | # Run 2 | 3 | ``` 4 | elixir -r composite/cooking.exs composite/run.exs 5 | ``` 6 | -------------------------------------------------------------------------------- /structural/composite/cooking.exs: -------------------------------------------------------------------------------- 1 | # A composite is a tree data structure with branches and leaves 2 | # You can form them using tuples or structs. 3 | # Here we use tuples 4 | defmodule Cooking do 5 | # A `part` is "branch", with an array of ingredients (aka leaves or children) 6 | def part(name, ingredients), 7 | do: {:branch, name, ingredients} 8 | 9 | # An `ingredient` is "leaf", it does not have any children elements 10 | def ingredient(name, quantity, unit \\ :units), 11 | do: {:leaf, name, quantity, unit} 12 | end 13 | -------------------------------------------------------------------------------- /structural/composite/run.exs: -------------------------------------------------------------------------------- 1 | # Import the `part/2` and `ingredient/3` functions 2 | import Cooking 3 | 4 | # Define the cake, the root is a branch 5 | cake = 6 | part(:cake, [ 7 | # First part is the batter which is a "branch" 8 | # it has several ingredients, which are "leaves" 9 | part(:batter, [ 10 | ingredient(:flour, 1, :cups), 11 | ingredient(:sugar, 0.5, :cups), 12 | ingredient(:eggs, 2) 13 | ]), 14 | 15 | # Second branch is the frosting, with 2 leaves 16 | part(:frosting, [ 17 | ingredient(:sugar, 100, :grams), 18 | ingredient(:lemon_juice, 2, :tbl) 19 | ]) 20 | ]) 21 | 22 | IO.inspect(cake, label: "Your birthday cake") 23 | -------------------------------------------------------------------------------- /structural/composite/run.sh: -------------------------------------------------------------------------------- 1 | elixir -r cooking.exs run.exs 2 | -------------------------------------------------------------------------------- /structural/decorator/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ``` 4 | $ elixir run.exs 5 | ``` -------------------------------------------------------------------------------- /structural/decorator/run.exs: -------------------------------------------------------------------------------- 1 | defmodule App do 2 | def normal_text(text) do 3 | text 4 | end 5 | 6 | def text_with_quotes(text) do 7 | "\"" <> normal_text(text) <> "\"" 8 | end 9 | end 10 | 11 | App.normal_text ("HELLO WORLD!") |> IO.puts 12 | App.text_with_quotes("HELLO WORLD!") |> IO.puts -------------------------------------------------------------------------------- /structural/delegation/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ``` 4 | $ elixir run.exs 5 | ``` -------------------------------------------------------------------------------- /structural/delegation/run.exs: -------------------------------------------------------------------------------- 1 | defmodule Interface do 2 | @callback test() :: atom() 3 | end 4 | 5 | defmodule First do 6 | @behaviour Interface 7 | 8 | def test(), do: :"I am FIRST!!!" 9 | end 10 | 11 | defmodule Second do 12 | @behaviour Interface 13 | 14 | defdelegate test(), to: First 15 | end 16 | 17 | First.test |> IO.inspect # :"I AM FIRST!!!" 18 | Second.test |> IO.inspect # :"I AM FIRST!!!" -------------------------------------------------------------------------------- /structural/facade/README.md: -------------------------------------------------------------------------------- 1 | # Attribution 2 | 3 | From the article by Pete Corey 4 | http://www.petecorey.com/blog/2018/09/03/using-facades-to-simplify-elixir-modules 5 | 6 | # Run 7 | 8 | ``` 9 | elixir facade/run.exs 10 | ``` 11 | -------------------------------------------------------------------------------- /structural/facade/run.exs: -------------------------------------------------------------------------------- 1 | # A facade is a portal into a sub-system 2 | # It encapsulated a sub system 3 | # It hides implementation details from consumers 4 | # It acts as a contract between subsystems 5 | 6 | # Private implementation details 7 | defmodule Shipping.Packages do 8 | def options do 9 | [envelope: 1, box: 2] 10 | end 11 | end 12 | 13 | # More private implementation details 14 | defmodule Shipping.Rates do 15 | def compute do 16 | [fedex: 1, ups: 2, usps: 0, dhl: 1] 17 | end 18 | end 19 | 20 | # Facade delegate to underlying components, that means consumers 21 | # don't need to know about the underlying components 22 | defmodule Shipping do 23 | defdelegate package_options, to: Shipping.Packages, as: :options 24 | defdelegate compute_rates, to: Shipping.Rates, as: :compute 25 | end 26 | 27 | # Call facade and print it 28 | Shipping.package_options() |> IO.inspect(label: "Packages") 29 | Shipping.compute_rates() |> IO.inspect(label: "Rates") 30 | -------------------------------------------------------------------------------- /structural/facade/run.sh: -------------------------------------------------------------------------------- 1 | elixir run.exs 2 | -------------------------------------------------------------------------------- /structural/flyweight/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ``` 4 | $ iex run.exs 5 | Interactive Elixir (1.9.0-rc.0) - press Ctrl+C to exit (type h() ENTER for help) 6 | 7 | iex(1)> {:ok, factory} = UserFactory.new 8 | {:ok, #PID<0.107.0>} 9 | 10 | iex(2)> a_user = factory |> UserFactory.it([login: "A", email: "A", password: "A"]) 11 | #PID<0.109.0> 12 | 13 | iex(3)> b_user = factory |> UserFactory.it([login: "B", email: "B", password: "B"]) 14 | #PID<0.111.0> 15 | 16 | iex(4)> again_a_user = factory |> UserFactory.it([login: "A", email: "A", password: "A"]) 17 | #PID<0.109.0> 18 | 19 | iex(5)> again_b_user = factory |> UserFactory.it([login: "B", email: "B", password: "B"]) 20 | #PID<0.111.0> 21 | ``` -------------------------------------------------------------------------------- /structural/flyweight/run.exs: -------------------------------------------------------------------------------- 1 | defmodule User do # flyweight 2 | def init([login: login, email: email, password: password]), do: 3 | {:ok, [login: login, email: email, password: password]} 4 | 5 | def new(settings), do: 6 | GenServer.start_link __MODULE__, settings 7 | 8 | 9 | 10 | def handle_call(:get, _from, settings), do: 11 | {:reply, settings, settings} 12 | 13 | def get(pid), do: 14 | GenServer.call(pid, :get) 15 | end 16 | 17 | defmodule UserFactory do 18 | def init(pool), do: 19 | {:ok, pool} 20 | 21 | def new(pool \\ []), do: 22 | GenServer.start_link __MODULE__, pool 23 | 24 | 25 | 26 | def handle_call(settings, _from, pool) do 27 | return = pool |> Enum.find(fn user -> user |> User.get == settings end) 28 | 29 | if is_nil(return) do 30 | {:ok, this} = User.new(settings) 31 | {:reply, this, pool ++ [this]} 32 | else 33 | {:reply, return, pool} 34 | end 35 | end 36 | 37 | def it(pid, settings), do: 38 | GenServer.call(pid, settings) 39 | end -------------------------------------------------------------------------------- /structural/proxy/README.md: -------------------------------------------------------------------------------- 1 | # Run 2 | 3 | ``` 4 | elixir proxy/run.exs 5 | ``` 6 | -------------------------------------------------------------------------------- /structural/proxy/run.exs: -------------------------------------------------------------------------------- 1 | defmodule BankAccount do 2 | use GenServer 3 | 4 | @initial_state %{ 5 | balance: 0, 6 | transactions: [] 7 | } 8 | 9 | def start_link do 10 | GenServer.start_link(__MODULE__, @initial_state) 11 | end 12 | 13 | def init(state) do 14 | {:ok, state} 15 | end 16 | 17 | def deposit(account, money) do 18 | GenServer.call(account, {:deposit, money}) 19 | end 20 | 21 | def withdraw(account, money) do 22 | GenServer.call(account, {:withdraw, money}) 23 | end 24 | 25 | def balance(account) do 26 | GenServer.call(account, :balance) 27 | end 28 | 29 | def handle_call({:deposit, money}, _from, state) when money > 0 do 30 | new_state = %{ 31 | balance: state.balance + money, 32 | transactions: [{:deposit, money} | state.transactions] 33 | } 34 | 35 | {:reply, {:ok, new_state.balance}, new_state} 36 | end 37 | 38 | def handle_call({:withdraw, money}, _from, state = %{balance: balance}) when money < balance do 39 | new_state = %{ 40 | balance: state.balance - money, 41 | transactions: [{:withdraw, money} | state.transactions] 42 | } 43 | 44 | {:reply, {:ok, new_state.balance}, new_state} 45 | end 46 | 47 | def handle_call(:balance, _from, state) do 48 | {:reply, {:ok, state.balance}, state} 49 | end 50 | end 51 | 52 | # hide balance: whenever the message :balance is sent, return {:ok, :hidden} 53 | defmodule PrivacyProxy do 54 | def intercept(account) do 55 | receive do 56 | {:"$gen_call", from, :balance} -> 57 | GenServer.reply(from, {:ok, :hidden}) 58 | 59 | {:"$gen_call", from, message} -> 60 | # forward everything else 61 | result = GenServer.call(account, message) 62 | GenServer.reply(from, result) 63 | end 64 | 65 | # continue intercepting 66 | intercept(account) 67 | end 68 | end 69 | 70 | {:ok, account} = BankAccount.start_link() 71 | 72 | # without interceptor 73 | BankAccount.deposit(account, 100) |> IO.inspect() 74 | BankAccount.withdraw(account, 10) |> IO.inspect() 75 | 76 | # spawn a proxy to intercept 77 | proxy = spawn(PrivacyProxy, :intercept, [account]) 78 | 79 | # calls to balance are now intercepted 80 | BankAccount.balance(proxy) |> IO.inspect() 81 | BankAccount.deposit(proxy, 10) |> IO.inspect() 82 | --------------------------------------------------------------------------------