4

Russ Olsen による「Ruby のデザイン パターン」で、 Observer パターンを Ruby で実装する方法を読みました。このパターンの Ruby 実装は、C# 実装よりもはるかに単純であることに気付きました。たとえば、Jesse Liberty と Alex Horovitz による「Programming .NET 3.5」に示されている実装です。

そこで、「プログラミング .NET 3.5」オブザーバー パターンの例 (pdf 版の 251 ページ) を「Ruby のデザイン パターン」アルゴリズムを使用して書き直しました。両方の実装のソース コードは、前述の Web サイトからダウンロードできます。

以下は書き直した例ですが、どう思いますか?
C# で Observer パターンを使用するために、イベントとデリゲートを使用する必要は本当にあるのでしょうか?


更新 コメントを読んだ後、この質問をしたいと思います:
コードを短くする以外に、デリゲートとイベントを使用する理由はありますか? また、GUI プログラミングについては触れません。

Update2 やっと手に入れました。delegate は単なる関数ポインタであり、event は、+= と -= の 2 つの操作のみを許可するより安全なバージョンのデリゲートです。

「プログラミング.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("---------");
        }
    }

}

ここに言及されている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)

これは、私が書き直した「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; }
    }
}
4

5 に答える 5

15

デザインパターンは、パターンを実装するために使用する必要がある特定のクラス階層ではなく、一般的な意味でアイデアを表現します。C#では、クラスとインターフェイスを使用してアイデアを実装することはありません(たとえば、Javaの場合など)。これは、より簡単なソリューションを提供するためです。代わりに、イベントデリゲートを使用できます。これがあなたがチェックしたいと思うかもしれない素晴らしい記事です:

C#ではるかにエレガントにエンコードできるパターンはオブザーバーだけではないことに注意してください。たとえば、ストラテジーパターンは、C#で(単一行の)ラムダ式を使用して実装できます。

そうは言っても、私は多くの点でデザインパターンにかなり懐疑的ですが、それらは参考として役立つかもしれません。ただし、盲目的に使用しないでください。パターンに厳密に従うことが高品質の「エンタープライズ」ソフトウェアを作成する唯一の方法であると考える作者もいるかもしれませんが、そうではありません。

編集 これはあなたの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);
 

これは、イベントとデリゲートの完全に優れた使用法です。C#3.0ラムダ関数を使用すると、Rubyよりも例がさらに簡単になります:-)。

于 2010-02-06T19:01:44.057 に答える
3

Ruby よりも C# の方が Observer パターンがはるかに複雑なのはなぜですか?

その理由はいくつかあります:

1) Ruby のダックタイピングは、インターフェイスを宣言して実装する必要がないことを意味します。

2) C# の例は、Ruby の例よりも多くのことを行っています。

3) C# の例の書き方が悪い。イベントとデリゲートが組み込まれているため、カノニカル オブザーバー パターンを手動で実装することはほとんどありません。公平を期すために、C# のイディオムを使用して C# で Ruby コードを再実装しましょう。

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;
        }
    }
}

これで、コードは Ruby と同じくらい単純になりました。

于 2010-02-06T19:23:47.003 に答える
1

私の C# バージョンでは、大きな違いは見られません。

Subject前述の C# の本の著者は、ConcreteSubject、 、Observer、およびConcreteObserverクラスがある元の Observer パターンのように自分の例を作ろうとするかもしれないと思います。これらは実際には多くの状況で不要です。多くの場合、メソッドを使用してイベントをサブスクライブするだけで十分です。

C# が提供するイベントとデリゲートを使用することで、「オブザーバー リスト」とそれに関連するアタッチ/デタッチ メソッドを自分で管理する必要がなくなります。また、購読しているクライアントに新しいイベントを簡単に通知する方法も提供します。

更新: @Tomas の実装を見たところです。彼はそこで C# 3 をうまく使っています。ただし、Ruby コードからの直接マッピングを確認したい場合は、以下の例が役立ちます。

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);
        }
    }

}
于 2010-02-06T19:14:59.583 に答える
1

コードを短くする以外に、デリゲートとイベントを使用する理由はありますか?

はい。最近のほとんどのプログラミング言語には、何らかの「クロージャー」機能があります。これは、匿名関数と、これらの関数がそれらの外部で宣言された変数を参照する機能を組み合わせたものです。

この機能がないことでしばしば批判される Java では、実際に存在します。これを利用するには、無名クラス全体 (1 つのメソッドだけでなく) を作成する必要があり、final変数 (つまり、非変数) のみを参照できます。したがって、少し冗長で制限がありますが、機能します。コールバック (リスナーなど) を表す抽象クラスまたはインターフェイスを作成し、そのインターフェイスを匿名クラスで実装して、コールバックを提供できます。

C# では、匿名クラスを作成することはできませんが、個別の匿名メソッドを作成することはできます。それらは、互換性のあるデリゲート型の変数に格納できます。また、匿名メソッドは、匿名メソッドが存在するコンテキスト内の任意の変数を参照できます。

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

したがって、質問のその部分に答えるために、C# で抽象クラス/インターフェイスの代わりにデリゲートを使用する主な理由の 1 つは、変数のクロージャーを形成できる匿名メソッドを有効にすることです。これは「コードを短くする」だけでなく、プログラムについてまったく新しい考え方を可能にします。

于 2010-02-09T17:14:09.940 に答える