I've read in "Design Patterns in Ruby" by Russ Olsen how Observer pattern can be implemented in Ruby. I've noticed that Ruby implementation of this pattern is much simpler than C# implementation, e.g. implementation shown in "Programming .NET 3.5" by Jesse Liberty and Alex Horovitz.
So I've rewritten "Programming .NET 3.5" Observer pattern example (page 251 of pdf edition) using "Design Patterns in Ruby" algorithm, source code for both implementations can be downloaded from the mentioned websites.
Below is the rewritten example, tell me what do you think?
Do we really need to use events and delegates to use the Observer pattern
in C#?
Update
After reading comments I would like to ask this question:
Is there any other reason to use delegates and events besides that it makes code shorter?
And I don't talk about GUI programming.
Update2 I finally got it, delegate is simply a function pointer and event is safer version of delegate which only allows two operations += and -=.
My rewrite of "Programming .NET 3.5" example:
using System;
using System.Collections.Generic;
namespace MyObserverPattern
{
class Program
{
static void Main()
{
DateTime now = DateTime.Now;
// Create new flights with a departure time and add from and to destinations
CarrierSchedule jetBlue = new CarrierSchedule("JetBlue", now);
jetBlue.Attach(new AirTrafficControl("Boston"));
jetBlue.Attach(new AirTrafficControl("Seattle"));
// ATCs will be notified of delays in departure time
jetBlue.DepartureDateTime =
now.AddHours(1.25); // weather delay
jetBlue.DepartureDateTime =
now.AddHours(1.75); // weather got worse
jetBlue.DepartureDateTime =
now.AddHours(0.5); // security delay
jetBlue.DepartureDateTime =
now.AddHours(0.75); // Seattle puts a ground stop in place
// Wait for user
//Console.Read();
}
}
// Subject: This is the thing being watched by Air Traffic Control centers
abstract class AirlineSchedule
{
// properties
public string Name { get; set; }
public string DeparturnAirport { get; set; }
public string ArrivalAirport { get; set; }
private DateTime departureDateTime;
private List<IATC> observers = new List<IATC>();
public AirlineSchedule(string airline,
string outAirport,
string inAirport,
DateTime leaves )
{
this.Name = airline;
this.DeparturnAirport = outAirport;
this.ArrivalAirport = inAirport;
this.DepartureDateTime = leaves;
}
// Here is where we actually attach our observers (ATCs)
public void Attach(IATC atc)
{
observers.Add(atc);
}
public void Detach(IATC atc)
{
observers.Remove(atc);
}
public void OnChange(AirlineSchedule asched)
{
if (observers.Count != 0)
{
foreach (IATC o in observers)
o.Update(asched);
}
}
public DateTime DepartureDateTime
{
get { return departureDateTime; }
set
{
departureDateTime = value;
OnChange(this);
Console.WriteLine("");
}
}
}// class AirlineSchedule
// A Concrete Subject
class CarrierSchedule : AirlineSchedule
{
// Jesse and Alex only really ever need to fly to one place...
public CarrierSchedule(string name, DateTime departing) :
base(name, "Boston", "Seattle", departing)
{
}
}
// An Observer
interface IATC
{
void Update(AirlineSchedule sender);
}
// The Concrete Observer
class AirTrafficControl : IATC
{
public string Name { get; set; }
public AirTrafficControl(string name)
{
this.Name = name;
}
public void Update(AirlineSchedule sender)
{
Console.WriteLine(
"{0} Air Traffic Control Notified:\n {1}'s flight 497 from {2} " +
"to {3} new deprture time: {4:hh:mmtt}",
Name,
sender.Name,
sender.DeparturnAirport,
sender.ArrivalAirport,
sender.DepartureDateTime );
Console.WriteLine("---------");
}
}
}
Here is mentioned Ruby code:
module Subject
def initialize
@observers=[]
end
def add_observer(observer)
@observers << observer
end
def delete_observer(observer)
@observers.delete(observer)
end
def notify_observers
@observers.each do |observer|
observer.update(self)
end
end
end
class Employee
include Subject
attr_reader :name, :address
attr_reader :salary
def initialize( name, title, salary)
super()
@name = name
@title = title
@salary = salary
end
def salary=(new_salary)
@salary = new_salary
notify_observers
end
end
class TaxMan
def update( changed_employee )
puts("Send #{changed_employee.name} a new tax bill!")
end
end
fred = Employee.new('Fred', 'Crane Operator', 30000.0)
tax_man = TaxMan.new
fred.add_observer(tax_man)
Here is "Programming .NET 3.5" example that I rewrote:
using System;
namespace Observer
{
class Program
{
static void Main()
{
DateTime now = DateTime.Now;
// Create new flights with a departure time and add from and to destinations
CarrierSchedule jetBlue = new CarrierSchedule("JetBlue", now);
jetBlue.Attach(new AirTrafficControl("Boston"));
jetBlue.Attach(new AirTrafficControl("Seattle"));
// ATCs will be notified of delays in departure time
jetBlue.DepartureDateTime =
now.AddHours(1.25); // weather delay
jetBlue.DepartureDateTime =
now.AddHours(1.75); // weather got worse
jetBlue.DepartureDateTime =
now.AddHours(0.5); // security delay
jetBlue.DepartureDateTime =
now.AddHours(0.75); // Seattle puts a ground stop in place
// Wait for user
Console.Read();
}
}
// Generic delegate type for hooking up flight schedule requests
public delegate void ChangeEventHandler<T,U>
(T sender, U eventArgs);
// Customize event arguments to fit the activity
public class ChangeEventArgs : EventArgs
{
public ChangeEventArgs(string name, string outAirport, string inAirport, DateTime leaves)
{
this.Airline = name;
this.DeparturnAirport = outAirport;
this.ArrivalAirport = inAirport;
this.DepartureDateTime = leaves;
}
// Our Properties
public string Airline { get; set; }
public string DeparturnAirport { get; set; }
public string ArrivalAirport { get; set; }
public DateTime DepartureDateTime { get; set; }
}
// Subject: This is the thing being watched by Air Traffic Control centers
abstract class AirlineSchedule
{
// properties
public string Name { get; set; }
public string DeparturnAirport { get; set; }
public string ArrivalAirport { get; set; }
private DateTime departureDateTime;
public AirlineSchedule(string airline, string outAirport, string inAirport, DateTime leaves)
{
this.Name = airline;
this.DeparturnAirport = outAirport;
this.ArrivalAirport = inAirport;
this.DepartureDateTime = leaves;
}
// Event
public event ChangeEventHandler<AirlineSchedule, ChangeEventArgs> Change;
// Invoke the Change event
public virtual void OnChange(ChangeEventArgs e)
{
if (Change != null)
{
Change(this, e);
}
}
// Here is where we actually attach our observers (ATCs)
public void Attach(AirTrafficControl airTrafficControl)
{
Change +=
new ChangeEventHandler<AirlineSchedule, ChangeEventArgs>
(airTrafficControl.Update);
}
public void Detach(AirTrafficControl airTrafficControl)
{
Change -= new ChangeEventHandler<AirlineSchedule, ChangeEventArgs>
(airTrafficControl.Update);
}
public DateTime DepartureDateTime
{
get { return departureDateTime; }
set
{
departureDateTime = value;
OnChange(new ChangeEventArgs(
this.Name,
this.DeparturnAirport,
this.ArrivalAirport,
this.departureDateTime));
Console.WriteLine("");
}
}
}
// A Concrete Subject
class CarrierSchedule : AirlineSchedule
{
// Jesse and Alex only really ever need to fly to one place...
public CarrierSchedule(string name, DateTime departing):
base(name,"Boston", "Seattle", departing)
{
}
}
// An Observer
interface IATC
{
void Update(AirlineSchedule sender, ChangeEventArgs e);
}
// The Concrete Observer
class AirTrafficControl : IATC
{
public string Name { get; set; }
// Constructor
public AirTrafficControl(string name)
{
this.Name = name;
}
public void Update(AirlineSchedule sender, ChangeEventArgs e)
{
Console.WriteLine(
"{0} Air Traffic Control Notified:\n {1}'s flight 497 from {2} " +
"to {3} new deprture time: {4:hh:mmtt}",
Name,
e.Airline,
e.DeparturnAirport,
e.ArrivalAirport,
e.DepartureDateTime);
Console.WriteLine("---------");
}
public CarrierSchedule CarrierSchedule { get; set; }
}
}
Design patterns express ideas in a general sense and not a specific class hierarchy that should be used to implement the pattern. In C#, you wouldn't implement the idea using classes and interfaces (as for example in Java), because it provides a more straightforward solution. You can use events and delegates instead. Here is a nice article that you may want to check out:
Note that observer isn't the only pattern that can be encoded far more elegantly in C#. For example the Strategy pattern can be implemented using (single-line) lambda expression in C#:
That said, I'm quite sceptical about design patterns in many ways, but they may be useful as a reference. However they shouldn't be used blindly. Some authors maybe think that following the pattern strictly is the only way to write quality "enterprise" software, but that's not the case!
EDIT Here is a succinct version of your Ruby code. I didn't read the C# version, because it is too complex (and I'd even say obfuscated):
class Employee {
public Employee(string name, string address, int salary) {
Name = name; Address = address; this.salary = salary;
}
private int salary;
public event Action<Employee> SalaryChanged;
public string Name { get; set; }
public string Address { get; set; }
public int Salary {
get { return salary; }
set {
salary = value;
if (SalaryChanged != null) SalaryChanged(this);
}
}
var fred = new Employee(...);
fred.SalaryChanged += (changed_employee) =>
Console.WriteLine("Send {0} a new tax bill!", changed_employee.Name);
This is a perfectly fine use of events & delegates. C# 3.0 lambda functions make your example even simpler than in Ruby :-).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With