tags:

views:

546

answers:

5

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; }
    }
}
+8  A: 

I don't have the book, so I can't confirm this, but it's likely that the reason the example uses events and delegates is because these are first-class constructs in the C# language. Essentially, C# has already implemented the Observer pattern for you, so that you can use it wherever you want.

Also, I suspect part of the reason that the C# example is clunky is because Jesse Liberty doesn't strike me as a terribly adept author. Some of his books are a little too formulaic and rote (e.g. "Learn Programming Language X in Y Hours!"). The result is that you wind up with awkward, somewhat rushed examples that look like they were copy-pasted from his IDE as soon as there were no compiler errors.

John Feminella
@m3rLinEz The problem is that I don't like more complex code than is needed. Here we don't need events and delegates. I have no objections to use events where they are needed, like in GUI elements.
robert_d
@robert_d You're not going to get the simplicity and flexibility of Ruby when using C#. If you value these things most, go back to using Ruby. If you value type safety and the benefits that it brings, keep using C#. Events offer a simple solution within the constraints of the C# language. The key, though, is "within the constraints."
Michael Meadows
I agree with your assertion that you don't "need" events and delegates to pull this off -- but you also don't "need" `structs` to get value types, or `abstract` to get abstract base classes, or int64 to have a 64-bit integer type. The point is that (1) C# already provides these as tools and first-class constructs; (2) other developers will probably expect you to use them; (3) in most cases it _does_ simplify things.
John Feminella
@John I think you hit the nail on the head. You use the assets of a language. Ruby's asset is that it's dynamic, and that everything is an object. Its drawbacks is that any type violations are runtime errors. C# gives you type safety, but it comes at a cost. Built-in language features like events, delegates, lambdas, and expressions help reduce the pain, but it will never be Ruby. As long as @robert_d tries to view it through the lens of "why can't this be like Ruby" C# will always fall short. He should be trying to take advantage of its strengths instead of dwelling on its weaknesses.
Michael Meadows
+12  A: 

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

Tomas Petricek
Deisgn patterns are useful as a language to communicate common solutions to other programmers. They're not as useful when it comes to providing pre-canned solutions.
Michael Meadows
Concerning your first article (http://spellcoder.com/blogs/bashmohandes/archive/2007/03/10/6212.aspx)I still don't see the reason to use delegates and events, they make code more difficult to understand.
robert_d
[Extreme sceptic note:] Are they really useful for communicating common solutions? Doesn't this SO question show that they fail to do that? I do agree in principle, but it gets complicated when more and more languages have much better ways for encoding the pattern. Then design patterns can become counterproductive, because they take you away from the straightforward solution. Design patterns would be great if _everyone_ who is writing about them would be aware of this issue.
Tomas Petricek
@robert_d: Check out the editted version of the answer (with source code).
Tomas Petricek
"they make code more difficult to understand": this is very subjective. Events and delegates are idiomatic C#. I find Tomas' code very *easy* to understand, just as your Ruby code is easy to understand.
itowlson
Yes your c# rewrite of my ruby code is shorter but it is more difficult to understand, e.g. what is Action<Employee>?And my question is, is there any other reason to use delegates and events besides that it makes code shorter?
robert_d
The problem is that we have gotten used to thinking of patterns in terms of their implementations. This is not what we should be doing. If you think of the observer pattern as an abstract concept instead, it becomes far less confusing.
Michael Meadows
@robert_d: it's only more difficult to understand because you don't have a full grasp of C# and .NET features. Your question about "What is Action<Employee>?" is a prime example, as this is well known to anyone that understands C# 3 and .NET 3.5. Action<> is part of the .NET framework and is a generic delegate which can take parameters to indicate delegate parameters and returns void. It simplifies writing code you used to have include lots of boilerplate for.
Mystere Man
@Michael: I agree with that. Design patterns as abstract concepts are great. The original authors were probably aware of that when they first defined a _pattern language_ to describe them. Unfortunatelly, everybody is just showing the code nowadays...
Tomas Petricek
RE "is there any other reason to use delegates and events" - The primary reason for using them in C# is that they are _idiomatic_. It is the right way to solve this problem (and it is a solution that was designed for this problem by C# language designers). You could ask whether there is any reason to use `for` loop to encode looping with iteration (and complain that using `if` and `goto` makes code ugly).
Tomas Petricek
Asking why you would use delegates in C# is like asking why you would use blocks in Ruby.
Isaac Cambron
A: 

I don't see much difference here in my C# version.

I think the author of mentioned C# book might try to make his example looks like the original Observer pattern, where there are Subject, ConcreteSubject, Observer, and ConcreteObserver classes. These are actually unnecessary in many conditions. Many times, just subscribe to the event with method is enough.

By using event and delegate C# has provided, you can eliminate the need to maintain "observer list" and its related attach/detach methods yourself. They also provide an easy way to notify the subscribed clients of new event.

Update: Just saw @Tomas's implementation. He has good use of C# 3 there. However, if you want to see direct mapping from Ruby code my example below could help.

using System;

namespace Observer
{
    class Program
    {
        static void Main()
        {
            Employee fred = new Employee()
            {
                Name = "Fred",
                Title = "Crane Operator",
                Salary = 40000.0
            };

            TaxMan tax_man = new TaxMan();
            fred.Update += tax_man.OnUpdate;
            fred.Salary = 50000.0;
        }
    }

    public class Subject
    {
        public delegate void UpdateHandler(Subject s);
        public virtual event UpdateHandler Update;
    }

    public class Employee : Subject
    {
        public string Name { get; set; }
        public string Title { get; set; }
        private double _salary;
        public double Salary
        {
            get { return _salary; }
            set
            {
                _salary = value;
                if (Update != null)
                    Update(this);
            }
        }
        public override event UpdateHandler Update;
    }

    public class TaxMan
    {
        public void OnUpdate(Subject s)
        {
            if (s is Employee)
                Console.WriteLine("Send {0} a new tax bill!",
                    (s as Employee).Name);
        }
    }

}
m3rLinEz
+2  A: 

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

A few reasons for that:

1) Ruby's duck typing means you don't need to declare and implement an interface.

2) The C# example is doing a lot more than the Ruby example.

3) The C# example is badly written. You would rarely implement the canonical observer pattern by hand since events and delegate are baked in. To keep things fair, let's reimplement the Ruby code in C# using C# idioms:

using System;
using System.Linq;
using System.Collections.Generic;

namespace Juliet
{
    class Employee
    {
        public event Action<Employee> OnSalaryChanged;

        public string Name { get; set; }
        public string Title { get; set; }

        private decimal _salary;
        public decimal Salary
        {
            get { return _salary; }
            set
            {
                _salary = value;
                if (OnSalaryChanged != null)
                    OnSalaryChanged(this);
            }
        }

        public Employee(string name, string title, decimal salary)
        {
            this.Name = name;
            this.Title = title;
            this.Salary = salary;
        }
    }

    class TaxMan
    {
        public void Update(Employee e)
        {
            Console.WriteLine("Send {0} a new tax bill!", e.Name);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var fred = new Employee("Fred", "Crane operator", 30000.0M);
            var taxMan = new TaxMan();
            fred.OnSalaryChanged += taxMan.Update;

            fred.Salary = 40000.0M;
        }
    }
}

Now the code is just as simple as Ruby.

Juliet
A: 

Is there any other reason to use delegates and events besides that it makes code shorter?

Yes. Most programming languages these days have some "closure" capability. This combines anonymous functions, plus the ability for those functions to refer to variables declared outside them.

In Java, which is often criticised for lacking this feature, it actually does exist. To take advantage of it, you have to write an entire anonymous class (not just one method), and you can only refer to final variables (i.e. non-variables). So it's a bit verbose and limited, but it does work. You can write an abstract class or interface to represent a callback (such as a listener), and then you can implement that interface with an anonymous class to supply the callback.

In C#, you can't write anonymous classes, but you can write individual anonymous methods. You can store them in a variable of some compatible delegate type. And the anonymous method can refer to any variables in the context where the anonymous method lives:

int counter = 0;

Action<int> increase; // a delegate variable

increase = by => counter += by; // anonymous method modifies outer variable

increase(2); // counter == 2
increase(3); // counter == 5

So to answer that part of your question, one major reason for using delegates instead of abstract classes/interfaces in C# is that it enables anonymous methods that can form a closure over variables. This doesn't just "make code shorter" - it enables a whole new way of thinking about your programs.

Daniel Earwicker