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

I read in “Designing Templates in Ruby” by Russ Olsen how an Observer pattern can be implemented in Ruby. I noticed that the Ruby implementation of this template is much simpler than the C # implementation, for example, the implementation is shown in ".NET 3.5 Programming" by Jesse Liberty and Alex Horowitz .

So, I rewrote an example of the “Programming .NET 3.5” template (p. 251 in pdf format) using the “Design Patterns in Ruby” algorithm, the source code for both implementations can be downloaded from these sites.

Below is a rewritten example, tell me what you think?
Do we really need to use events and delegates to use the Observer pattern in C #?


Update After reading the comments, I would like to ask this question:
Is there any other reason to use delegates and events besides the fact that it makes the code shorter? And I'm not talking about GUI programming.

Update2 I finally got it, the delegate is just a pointer to a function, and the event is a safer version of the delegate that allows only two operations: + = and - =.

My version of "Programming .NET 3.5":

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} flight 497 from {2} " + "to {3} new deprture time: {4:hh:mmtt}", Name, sender.Name, sender.DeparturnAirport, sender.ArrivalAirport, sender.DepartureDateTime ); Console.WriteLine("---------"); } } } 

Ruby code is mentioned here:

 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 an example of "Programming.NET 3.5" 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} 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; } } } 
+4
source share
5 answers

Design patterns express ideas in a general sense, rather than the specific hierarchy of classes that should be used to implement the pattern. In C #, you will not implement the idea using classes and interfaces (for example, in Java), as it provides a simpler solution. You can use events and delegates . Here is a good article you can check:

Note that observer is not the only template that can be more coded in C #. For example, a strategy template can be implemented using a (single-line) lambda expression in C #:

However, I'm pretty skeptical about design patterns in many ways, but they can be useful as a reference. However, they cannot be used blindly. Some authors might think that strict adherence to the pattern is the only way to write quality "corporate" software, but it is not!

EDIT Here is a short version of your Ruby code. I did not read the C # version because it is too complicated (and I even said obfuscation):

 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 great use of events and delegates. With # 3.0, lambda functions make your example even easier than in Ruby :-).

+13
source

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

Also, I suspect the reason is that the C # example is awkward because Jesse Liberty doesn't consider me a terribly skilled writer. Some of his books are too complex and characteristic (for example, "Learn the programming language X in Y Clock!"). As a result, you come across awkward, slightly hasty examples that look like they were copied from its IDE as soon as there were no compiler errors.

+9
source

Why is the observer pattern much more complex in C # than in Ruby?

A few reasons for this:

1) Ruby duck typing means you do not need to declare and implement an interface.

2) The C # example does much more than the Ruby example.

3) The C # example is poorly written. You rarely applied the canonical observer pattern manually, since the events and the delegate are baked. To keep everything in order, let me reimplement 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 as simple as Ruby.

+3
source

In my C # version, I don't see much difference.

I think that the author of the mentioned C # book may try to make his example look like the original Observer template, where there are Subject , ConcreteSubject , Observer and ConcreteObserver classes. In many cases, they are not needed. Many times just subscribing to an event using the method is enough.

Using the event and the delegate provided by C #, you can eliminate the need to maintain a "list of watchers" and the associated connect / disconnect methods yourself. They also provide an easy way to notify subscribers of new events.

Update: Just saw the @Tomas implementation. It makes good use of C # 3. However, if you want to see a direct comparison with Ruby code, my example below may 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); } } } 
+1
source

Is there any other reason for using delegates and events besides making the code shorter?

Yes. Most programming languages ​​these days have some "closure" capabilities. This combines anonymous functions, as well as the ability of these functions to refer to variables declared outside of them.

In Java, which is often criticized for not having this feature, it actually exists. To take advantage of this, you must write a whole anonymous class (and not just one method), and you can only refer to final variables (i.e. Non-Variables). So it's a little detailed and limited, but it works. You can write an abstract class or interface to represent a callback (such as a listener), and then you can implement this interface with an anonymous class to provide a callback.

In C #, you cannot write anonymous classes, but you can write individual anonymous methods. You can save 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 this part of your question, one of the main reasons for using delegates instead of abstract classes / interfaces in C # is that it allows anonymous methods that can form a closure on variables. It doesn’t just “make the code shorter” - it allows a whole new way of thinking about your programs.

+1
source

Source: https://habr.com/ru/post/1300511/


All Articles