Design Patterns in Elixir
Design patterns are fundamental solutions to common software design problems. While design patterns are often associated with object-oriented languages, they can also be applied in functional programming languages like Elixir. In this post, we’ll explore some design patterns that can be used effectively in Elixir, along with practical examples to illustrate their usage.
- Singleton Pattern:
The Singleton pattern ensures that a class has only one instance and provides a global access point to that instance. In Elixir, we can achieve this using modules and ETS (Erlang Term Storage). Here’s a simple example of a Singleton pattern in Elixir:
defmodule Singleton do
use GenServer
def start_link do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def get_instance do
{:ok, pid} = GenServer.whereis(__MODULE__)
pid
end
# Implement GenServer callbacks here (handle_call, handle_cast, etc.)
end
By calling Singleton.get_instance
, we can always get the same instance of the Singleton
module throughout the application's lifecycle.
- Factory Pattern:
The Factory pattern provides an interface for creating objects without specifying their concrete classes. In Elixir, we can implement the Factory pattern using functions that create and initialize specific structs or modules. Here’s an example:
defmodule ShapeFactory do
def create_shape(:circle) do
%Circle{}
end
def create_shape(:rectangle) do
%Rectangle{}
end
end
defstruct radius: 0, width: 0, height: 0
defmodule Circle do
def new(radius) do
%__MODULE__{radius: radius}
end
end
defmodule Rectangle do
def new(width, height) do
%__MODULE__{width: width, height: height}
end
end
Using ShapeFactory.create_shape
, we can easily create instances of circles or rectangles without exposing the concrete implementations.
- Observer Pattern:
The Observer pattern establishes a dependency between objects, so that when one object changes state, its dependents are notified and updated automatically. In Elixir, we can use Phoenix PubSub to implement the Observer pattern. Here’s an example:
defmodule StockMarket do
use Phoenix.PubSub
defmodule StockWatcher do
def start_link do
Phoenix.PubSub.subscribe(StockMarket, "stocks")
end
def handle_info({:stock_update, symbol, price}, state) do
IO.puts("Stock #{symbol} updated: #{price}")
{:noreply, state}
end
end
end
By subscribing StockWatcher
to the "stocks" topic, it will be notified of any stock updates and react accordingly.
Design patterns offer elegant solutions to common design challenges, regardless of the programming paradigm. In Elixir, we can leverage patterns like Singleton, Factory, and Observer to improve code organization, promote reusability, and simplify complex designs.
By incorporating these design patterns into your Elixir projects, you can create more maintainable and flexible software solutions. Happy coding! 🚀💻