Press release
Observer pattern in 3 languages – Ruby, C# and Elixir
The goal of this article is to show the differences in thinking that arise when you develop your application design using different languages and language paradigms. We will have a dynamic object-oriented language (Ruby), static object-oriented language (C#) and functional actor model-based dynamic language (Elixir). I may speculate that this might be interesting to people who know one or two of these languages.Observer can be a really useful pattern when you need to notify other parts of your system about a state change or an event. The pattern is also known as publish/subscribe (pub/sub for short). Almost every GUI framework uses this pattern to receive notifications about button click or some other interaction with GUI. It is less common in server-side apps, but none the less, I’ve seen it be used in certain scenarios where it is a great fit.
Please, note that all examples will be canonical and by no means perfect or suitable for production use.
Also, I would like to mention that I will not implement the unsubscribing part here, because it will make my examples longer and won’t contribute to the understanding of the idea.
With that in mind, let’s begin!
OBSERVER PATTERN IN RUBY
In Ruby, there is an Observable module in the standard library to achieve publish/subscribe constructs, but we will write our own module to demonstrate what is happening under the hood.
Let’s start by building our own ObservableImplementation module that will be included in our observable class:
module ObservableImplementation
def observers
@observers || @observers = []
end
def notify_observers(*args)
observers.each do |observer|
observer.update(*args)
end
end
def add_observer(object)
observers observable = Observable.new
irb(main):006:0> observer = Observer.new(observable)
irb(main):007:0> observer = Observer.new(observable)
irb(main):008:0> observable.tick
Count has increased by: 1
Count has increased by: 1
irb(main):009:0> observable.tick
Count has increased by: 2
Count has increased by: 2
We instantiate an observable object, instantiate two observers, and call .tick on observable. You can see that our observers receive notifications and everything works fine here.
Now let’s move to the observer implementation in C#.
OBSERVER PATTERN IN C#
In C# world we need to think about our code a little bit differently than in Ruby. If we have different instances of a different class of listener, we need to define observers interface. Let’s implement our abstract Observable class first and carry on with everything else as we go.
abstract class Observable {
private List observers = new List();
public void AddObserver(IObserver observer) {
observers.Add(observer);
}
public void Notify() {
observers.ForEach(o => o.Update());
}
}
We’ve created this class to have the behaviour that would be similar to what we’ve had in Ruby. This is an abstract class so it will need to have the implementation. We will have private property observers of type List that will contain IObserver type objects, or in other words, objects that have implemented IObserver interface. Let’s go ahead and implement it.
interface IObserver {
void Update();
}
It is an interface that has to implement a single method Update. Now, this code should be able to compile. Next thing we need to do is to implement ConcreteObservable class that will be able to leverage our abstract Observable class.
class ConcreteObservable : Observable {
public int SubjectState {get; set; }
public ConcreteObservable() {
this.SubjectState = 0;
}
public void AddToCounter() {
this.SubjectState ++;
}
}
We will have a SujectState property and in our constructor, we set it to 0. Also, we’ve added a state modifier method AddToCounter(). Its only job is to increase our SubjectState property by 1. Of course, it inherits from our Observable class.
So the next thing we need to think about is the concrete observer itself. Let’s define it.
class ConcreteObserver : IObserver
{
private ConcreteObservable Observable;
public ConcreteObserver(ConcreteObservable observable)
{
this.Observable = observable;
}
public void Update()
{
Console.WriteLine("Counter has increased: {0}", Observable.SubjectState);
}
}
This class will take the observable object in the constructor. In order to access its state, it will have Update() method, that will print our state, or in our case counter, from the Observable. And we are done. the only thing left to do is to bring it all together:
class MainClass {
static void Main() {
ConcreteObservable observable = new ConcreteObservable();
observable.AddObserver(new ConcreteObserver(observable));
observable.AddObserver(new ConcreteObserver(observable));
observable.AddToCounter();
observable.Notify();
observable.AddToCounter();
observable.Notify();
observable.AddToCounter();
observable.Notify();
}
}
We are instantiating new ConcreteObservable instance and then adding two observers to it. Also, please, note that we are passing observable itself as a reference to the observer. This is needed in order to access state of observable. Then we call our AddToCounter() method and notify our observers three times. And now let’s compile and launch our code by typing mcs observer.cs && mono observer.exe and we should get the following output:
Counter has increased: 1
Counter has increased: 1
Counter has increased: 2
Counter has increased: 2
Counter has increased: 3
Counter has increased: 3
In case of C#, there are consequences of static typing. It’s not as easy to do as it is in Ruby, but none the less, it’s doable and it should not bother you that much if you are a seasoned C# developer. There are also several ways we can do “sort of duck typing” in C# with Generics. But this is not a topic of this blog post.
Now it is time to move to the sweet spot of this post – Elixir.
OBSERVER PATTERN IN ELIXIR
Elixir is where things get interesting. Let’s start by creating our observable process.
defmodule Observable do
def start do
spawn(__MODULE__, :listen, [[], 0])
end
def listen(subscribers, count) do
receive do
{:add_subscriber, subscriber_pid} ->
add_subscriber(subscribers, subscriber_pid) |>
listen(count)
{:add_to_counter} ->
new_count = count + 1
notify_subscribers(subscribers, new_count)
listen(subscribers, new_count)
end
end
def add_subscriber(subscribers, subscriber_pid) do
[subscriber_pid | subscribers]
end
def notify_subscribers(subscribers, count) do
Enum.each(subscribers, fn(subscriber_pid)->
send(subscriber_pid, count)
end)
end
end
Let’s discuss what we have written here – First of all, we’ve defined the module Observable. It has a function start that spawns a process (Elixir process, not OS process) and calls listen/2 the function on Observable module (__MODULE__ corresponds to the name of the module we are in) with an empty list. Why do we pass [[], 0] as a second argument? We do that because that is the way you can pass multiple arguments to function dynamically. Imagine that we needed to pass two symbols as two arguments to our listen function. It that case we would do it like this: [:first_arg, :second_arg]. Okay, we’ve spawned a process, now what? listen/2 the function is now executed and has arguments of the empty list as subscribers and count as 0 in separate isolated process and receive do part is listening for the incoming messages. Right now it can receive two messages – {:add_subscriber, subscriber_pid} and {:add_to_counter}. Elixir helps us to distinguish messages by pattern matching against them.
So, when we receive a message with tuple {:add_susbscriber, subscriber_pid} we add a new subscriber to the existing list and call listen/2 again with the new state. At this point, if you are not familiar with recursions, your brain should start to hurt. This functionality is achieved via tail call. There is another interesting operator |>. It is similar to pipe operator in *NIX systems. a(b()) essentially means the same as b |> a. With that in mind, we call a function add_subscriber/2 and this function appends pid (Process identifier – don’t worry, we will talk about it bit later) to the list of the existing pids and the result of that function is passed to the recursive listen/2 function as the first argument. The count has not changed in this case, so we just pass what we’ve had.
Let’s pick apart the second case when we receive a {:add_to_counter} message. As soon as we get this message, we call notify_receivers/2 function which goes through the list of our subscribed pids and sends them a message with the new incremented count. Then we just call our recursive listen/2 function with the new count and pid list of our subscribers.
Okay, next thing that we are going to do is adding subscribers (observers) to our main process.
defmodule Observer do
def start do
spawn(__MODULE__, :listen, [])
end
def listen do
receive do
event ->
IO.puts("Received #{event}")
end
listen
end
end
So, when we start this process it will listen for the incoming events and print them on the screen. Again, after receiving a message, it will just call itself and listen for another message until the world ends. Let’s try to launch this in our console and see what happens.
iex(1)> observable = Observable.start
#PID
iex(2)> send(observable, {:add_subscriber, Observer.start})
{:add_subscriber, #PID}
iex(3)> send(observable, {:add_subscriber, Observer.start})
{:add_subscriber, #PID}
iex(4)> send(observable, {:add_to_counter})
Counter has increased: 1
Counter has increased: 1
iex(5)> send(observable, {:add_to_counter})
Counter has increased: 2
Counter has increased: 2
So, as you can see, we’ve started the observable process and it has returned us a pid. So what is a pid?. Pid is a process identifier in the BEAM virtual machine and the way you interact with erlang/elixir processes is by sending them messages. So observable value is holding pid of our observable process that we launched. Next thing to do is to send the observable {:add_subscriber, Observer.start}message. We are starting another Observer process and the function start/0 returns us a pid of our Observer process. So, we’ve spawned a process and sent its pid to our Observable process. We’ve done that four times. Afterwards, we just send {:add_to_count} to our observable process and it notifies its four subscribers that the count has increased. There are few consequences to what we’ve done here: all these launched processes work in parallel, they are isolated and that means that we can use our two/four/eight cores without even thinking too much about it.
CONCLUSION
You might say that this comparison is not really fair, but in sense it actually is. Mostly, we are dealing with natural constructs of languages and the way you think about your application is expressed in those primitives. In case of .NET there is a library AKKA.NET that will allow building actor based systems on CLR and Mono environments so I would guess that the industry is starting to shift towards more concurrent systems and mechanisms that can achieve this concurrency. Also, Matz (Creator of Ruby) has tweeted that he wants to build actors into the Ruby and disable GIL, which is great. Also for Ruby ecosystem, there is Celluloid.
Visit us https://diatomenterprises.com/observer-pattern-in-3-languages-ruby-c-and-elixir/ for details.
Diatom Enterprises
Straupes 5, k-1, LV1073, Riga, Latvia
Laura Valtere
info@diatomenterprises.com
https://diatomenterprises.com/
Diatom Enterprises is a Canada founded and Latvia based Software Development Company producing custom software solutions for a wide variety of industries all around the Globe.
This release was published on openPR.
Permanent link to this press release:
Copy
Please set a link in the press area of your homepage to this press release on openPR. openPR disclaims liability for any content contained in this release.
You can edit or delete your press release Observer pattern in 3 languages – Ruby, C# and Elixir here
News-ID: 2086046 • Views: …
More Releases for Observable
Global Stealth warfare market Report Insights and Growth Outlook to 2034 - Strat …
According to OG Analysis, a renowned market research firm, the Global Stealth warfare market was valued at USD 9.8 billion in 2024. The market is projected to grow at a compound annual growth rate (CAGR) of 11.4%, rising from USD 11.1 billion in 2025 to an estimated USD 29.4 billion by 2034.
Get a Free Sample: https://www.oganalysis.com/industry-reports/stealth-warfare-market
Stealth Warfare Market Overview
The global stealth warfare market is experiencing transformative growth driven by escalating…
From Research to Reality: An exclusive interview with Sara Močnik, Medical Doct …
Sara Močnik, Medical Doctor, Child and Adolescent Psychiatry Resident, and Researcher at the University of Maribor, Faculty of Electrical Engineering and Computer Science -a partner in the SMILE project- discusses her recent scientific publication and its implications for SMILE. Her latest study, titled "Beyond clinical observations: a scoping review of AI detectable observable cues in borderline personality disorder", authored by Sara Močnik, Urška Smrke, Izidor Mlakar, Grega Močnik, Hojka Gregorič…
Unveiling the Power of Applied Behavior Analysis: Quality Behavior Solutions, In …
In the intricate tapestry of behavioral sciences, one methodology shines with unparalleled efficacy: Applied Behavior Analysis (ABA). At its core, ABA embodies a systematic approach to understanding and modifying behavior, offering a beacon of hope for individuals and businesses alike. In this landscape of innovation, Quality Behavior Solutions, Inc. (QBS) [https://qbssocal.com/] emerges as a guiding light, dedicated to harnessing the transformative power of ABA to reshape lives and organizations. With…
Stealth Drones Market to Expand at a 15.2% CAGR through 2033
The global stealth drone market is expected to be valued at $5,532.5 million in 2023 and to grow at a CAGR of 15.2% to reach $22,774.3 million by the end of 2033.
Stealth drones, also known as unmanned aerial vehicles (UAVs), have emerged as a game-changer in modern military and surveillance operations. These advanced aircraft are designed to operate discreetly and avoid detection by radar, making them invaluable tools for various…
SentryPC review : is the best for Business ?
Free SentryPC Understood - Reasonably Flexible Manual Verification Programming for Private Businesses
In the powerful world of manual observable programming, SentryPC stands out as a blacksmith-friendly, natural results generator, designed for the private business with a conservative spending plan. Debunking the myth that censorship degrades quality, SentryPC displays the core highlights without unnecessary ringtones and whispers. As a veil-based stage, it offers simple implementation and maintenance, making it an…
Observable Growth Study on Food Enzymes Market 2026 Gaining & Analysis by Reveal …
Global Food Enzymes Market 2019-2026
A new market research report, titled “Global Food Enzymes Market” analyzes the market by stating its current value, size, and market performance and statistics. The report is an in-depth study of the key dynamics of the global shipbuilding market. An overview of the types, the process, and value chain has been included in the report for the benefit of the readers.
Food Enzymes Market is one of…