Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Observer pattern is much more complicated in C# than in Ruby?

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; }
    }
}
like image 357
robert_d Avatar asked Nov 27 '22 12:11

robert_d


1 Answers

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:

  • Observer pattern in C# = Events + delegates

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#:

  • Strategy Pattern: Today, Tomorrow, and C# 3.0

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 :-).

like image 169
Tomas Petricek Avatar answered Dec 05 '22 23:12

Tomas Petricek