为什么Observer模式在C#中比在Ruby中复杂得多?

rob*_*t_d 4 c# ruby observer-pattern

我读过Russ Olsen的"Ruby模式设计", 如何在Ruby中实现Observer模式.我注意到这种模式的Ruby实现比C#实现简单得多,例如Jesse Liberty和Alex Horovitz"Programming .NET 3.5"中显示的实现.

因此,我使用"Ruby中的设计模式"算法重写了"Programming .NET 3.5"Observer模式示例(pdf版本的第251页),两种实现的源代码都可以从上述网站下载.

下面是重写的例子,告诉我你的想法是什么?
我们真的需要使用事件和委托来使用C#中的Observer模式吗?


更新 阅读评论后,我想问一下这个问题:
除了使代码缩短之外,还有其他理由使用委托和事件吗?我不谈论GUI编程.

Update2 我终于明白了,委托只是一个函数指针,事件是委托的更安全版本,它只允许两个操作+ =和 - =.

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

}
Run Code Online (Sandbox Code Playgroud)

这里提到Ruby代码:

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)
Run Code Online (Sandbox Code Playgroud)

这里是我编写的"Programming .NET 3.5"示例:

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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

Tom*_*cek 13

设计模式表达一般意义上的想法,而不是应该用于实现模式的特定类层次结构.在C#中,您不会使用类和接口(例如在Java中)实现该想法,因为它提供了更直接的解决方案.您可以改用事件代理.这是一篇很好的文章,您可以查看:

请注意,观察者不是唯一可以在C#中更优雅地编码的模式.例如,策略模式可以使用C#中的(单行)lambda表达式实现:

也就是说,我对设计模式在许多方面持怀疑态度,但它们可能有用作参考.但是,不应盲目使用它们.一些作者可能认为严格遵循模式是编写高质量"企业"软件的唯一方法,但事实并非如此!

编辑 这是您的Ruby代码的简洁版本.我没有阅读C#版本,因为它太复杂了(我甚至会说混淆):

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);
Run Code Online (Sandbox Code Playgroud)

这是对活动和代表的完美使用.C#3.0 lambda函数使你的例子比Ruby更简单:-).

  • Deisgn模式作为一种语言可用于向其他程序员传达常见解决方案.在提供预先制作的解决方案时,它们并不那么有用. (3认同)
  • @robert_d:它只是更难理解,因为你没有完全掌握C#和.NET功能.您关于"什么是行动<员工>?"的问题 这是一个很好的例子,因为这对于理解C#3和.NET 3.5的人来说是众所周知的.Action <>是.NET框架的一部分,是一个通用委托,可以使用参数来指示委托参数并返回void.它简化了编写您曾经包含大量样板的代码. (3认同)
  • "他们使代码更难理解":这是非常主观的.事件和代表是惯用的C#.我发现Tomas的代码很容易理解,就像你的Ruby代码易于理解一样. (2认同)

Joh*_*lla 9

我没有这本书,所以我无法证实这一点,但是这个例子使用事件和委托的原因很可能是因为它们是C#语言中的第一类构造.从本质上讲,C#已经为您实现了Observer模式,因此您可以在任何地方使用它.

此外,我怀疑C#示例笨重的部分原因是因为杰西自由并没有让我成为一个非常娴熟的作家.他的一些书有点太公式化和死记硬背(例如"在Y小时学习编程语言X!").结果就是你结束了尴尬的,有些匆忙的例子,看起来就像没有编译错误就从他的IDE中复制粘贴了.

  • 我同意你的断言,你不需要"事件"和委托来解决这个问题 - 但你也不需要"结构"来获取值类型,或者"抽象"来获取抽象基类,或者int64具有64位整数类型.关键是(1)C#已经提供了这些作为工具和一流的结构; (2)其他开发人员可能会期望您使用它们; (3)在大多数情况下,它会简化事情. (3认同)