15

目前,我的理解水平低于网络上所有关于观察者模式的编码示例。我将其理解为几乎是一个订阅,当委托注册的更改发生时,它会更新所有其他事件。但是,我对好处和用途的真正理解非常不稳定。我做了一些谷歌搜索,但大多数都超出了我的理解水平。

我正在尝试用我目前的家庭作业来实现这种模式,并且要真正理解我的项目需要更好地理解模式本身,也许还需要一个例子来了解它的用途。我不想将这种模式强加于提交,我需要了解其目的并相应地开发我的方法,以便它实际上服务于一个好的目的。我的文字并没有真正深入,只是一句话提到它。MSDN 对我来说很难理解,因为我是这方面的初学者,而且它似乎更像是一个高级主题。

您如何向初学者描述这种观察者模式及其在 C# 中的用途? 例如,请保持代码非常简单,这样我就可以比复杂的代码片段更能理解其目的。我试图通过一些简单的文本框字符串操作和使用委托来有效地使用它,所以指针会有所帮助!

4

16 回答 16

27

我能想到的最好的例子是邮件列表(例如)。

你,观察者,订阅一个邮件列表,然后你观察这个列表。当您不再对列表感兴趣时,您取消订阅。

这个概念就是观察者模式。涉及两个或更多类。一个或多个类订阅发布者类(有不同的名称),然后第一个类(以及每个订阅类)将在发布者需要时得到通知。

这就是我向我妻子解释的方式,她经常听我对编程和设计理论的咆哮。这对她来说很有意义。我意识到这对你来说可能太简单了,但这是一个好的开始......

问候,
弗兰克

于 2009-04-20T18:10:13.340 回答
5

查看“Head First: Design Patterns”,了解一些非常容易理解的主要模式描述。

对于 Observer 来说,重要的是要了解它描述了一对多的关系,并使用订阅模型在发生更改时通知其他类。RSS、Atom 和 Twitter 沿着这些思路工作。

于 2009-04-20T18:19:49.293 回答
3

观察者想知道什么时候发生了变化,所以它订阅了主题。主题不认识观察者。这是重要的部分。Subject 只是定义了 Observer 需要提供的接口(或委托),并允许注册。

简而言之:观察者模式允许你的观察者被一个主题调用,它不关心观察者是谁以及它是否存在。

于 2009-04-20T18:14:04.377 回答
2

有两个对象NOTIFIER 和OBSERVER。NOTIFIER 对 OBSERVER 一无所知,而 OBSERVER 知道 NOTIFER 实现了一个事件。

OBSERVER 使用该事件通知其他对象发生了什么事。简单地说,一个事件就是一个方法列表。因为 OBSERVER 希望在发生某事时得到通知,所以 OBSERVER 为 NOTIFER 事件添加了一个在发生某事时应该调用的方法。

所以如果事情发生了,NOTIFIER 发布了这个事件,NOTIFIER 只是遍历方法列表并调用它们。当调用 OBSERVER 添加的方法时,OBSERVER 就知道事情发生了,并且可以在这种情况下做任何需要的事情。

这是一个带有ValueChanged()事件的示例通知程序类。

// Declare how a method must look in order to be used as an event handler.
public delegate void ValueChangedHandler(Notifier sender, Int32 oldValue, Int32 newValue);

public class Notifier
{
    // Constructor with an instance name.
    public Notifier(String name)
    {
        this.Name = name;
    }
    public String Name { get; private set; }

    // The event that is raised when ChangeValue() changes the
    // private field value.
    public event ValueChangedHandler ValueChanged;

    // A method that modifies the private field value and
    // notifies observers by raising the ValueChanged event.
    public void ChangeValue(Int32 newValue)
    {
        // Check if value really changes.
        if (this.value != newValue)
        {
            // Safe the old value.
            Int32 oldValue = this.value;

            // Change the value.
            this.value = newValue;

            // Raise the ValueChanged event.
            this.OnValueChanged(oldValue, newValue);
        }
    }

    private Int32 value = 0;

    // Raises the ValueChanged event.
    private void OnValueChanged(Int32 oldValue, Int32 newValue)
    {
        // Copy the event handlers - this is for thread safty to
        // avoid that somebody changes the handler to null after
        // we checked that it is not null but before we called
        // the handler.
        ValueChangedHandler valueChangedHandler = this.ValueChanged;

        // Check if we must notify anybody.
        if (valueChangedHandler != null)
        {
            // Call all methods added to this event.
            valueChangedHandler(this, oldValue, newValue);
        }
    }
}

这是一个示例观察者类。

public class Observer
{
    // Constructor with an instance name.
    public Observer(String name)
    {
        this.Name = name;
    }
    public String Name { get; private set; }

    // The method to be registered as event handler.
    public void NotifierValueChanged(Notifier sender, Int32 oldValue, Int32 newValue)
    {
        Console.WriteLine(String.Format("{0}: The value of {1} changed from {2} to {3}.", this.Name, sender.Name, oldValue, newValue));
    }
}

一个小型测试应用程序。

class Program
{
    static void Main(string[] args)
    {
        // Create two notifiers - Notifier A and Notifier B.
        Notifier notifierA = new Notifier("Notifier A");
        Notifier notifierB = new Notifier("Notifier B");

        // Create two observers - Observer X and Observer Y.
        Observer observerX = new Observer("Observer X");
        Observer observerY = new Observer("Observer Y");

        // Observer X subscribes the ValueChanged() event of Notifier A.
        notifierA.ValueChanged += observerX.NotifierValueChanged;

        // Observer Y subscribes the ValueChanged() event of Notifier A and B.
        notifierA.ValueChanged += observerY.NotifierValueChanged;
        notifierB.ValueChanged += observerY.NotifierValueChanged;

        // Change the value of Notifier A - this will notify Observer X and Y.
        notifierA.ChangeValue(123);

        // Change the value of Notifier B - this will only notify Observer Y.
        notifierB.ChangeValue(999);

        // This will not notify anybody because the value is already 123.
        notifierA.ChangeValue(123);

        // This will not notify Observer X and Y again.
        notifierA.ChangeValue(1);
    }
}

输出将如下所示。

观察者 X:Notifier A 的值从 0 变为 123。
观察者 Y:Notifier A 的值从 0 变为 123。
观察者 Y:Notifier B 的值从 0 变为 999。
观察者 X:Notifier A 的值从 123 变为 1。
观察者 Y:Notifier A 的值从 123 变为 1。

为了理解委托类型,我将把它们与类类型进行比较。

public class Example
{
   public void DoSomething(String text)
   {
      Console.WriteLine(
         "Doing something with '" + text + "'.");
   }

   public void DoSomethingElse(Int32 number)
   {
      Console.WriteLine(
         "Doing something with '" + number.ToString() + "'.");
   }
}

Example我们用两个方法定义了一个简单的类。现在我们可以使用这个类类型了。

Example example = new Example();

虽然这可行,但以下内容不起作用,因为类型不匹配。你得到一个编译器错误。

Example example = new List<String>();

我们可以使用变量example.

example.DoSomething("some text");

现在与委托类型相同。首先我们定义一个委托类型——这只是一个类型定义,就像之前的类定义一样。

public delegate void MyDelegate(String text);

现在我们可以使用委托类型,但是我们不能将普通数据存储在委托类型变量中,而是在方法中。

MyDelegate method = example.DoSomething;

我们现在已经存储DoSomething()了对象的方法example。以下内容不起作用,因为我们将MyDelegate委托定义为采用一个字符串参数并返回 void。DoSomethingElse返回 void 但采用整数参数,因此您会收到编译器错误。

MyDelegate method = example.DoSomethingElse;

最后你可以使用变量method. 您不能执行数据操作,因为变量不存储数据而是存储方法。但是您可以调用存储在变量中的方法。

method("Doing stuff with delegates.");

这会调用我们存储在变量 - 中的方法example.DoSomething()

于 2009-04-20T18:23:04.153 回答
1

观察者模式就像听起来一样 -

这是一些对象观察对象的一种手段,观察它的变化。

在 C# 中,这变得有些简单,因为事件基本上是一种特定于语言的实现观察者模式的方法。如果您曾经使用过事件,那么您就使用过观察者模式。

在其他语言中,这不是内置的,因此已经有很多尝试正式化处理这个问题的方法。

于 2009-04-20T18:11:58.783 回答
1

观察者就像一条直接的通信线路。与其让你所有的亲戚都打电话给你来了解你的情况,不如在你生病时写一张卡片,每个有兴趣的人都会得到它(或副本)。当你好转时,你会发出一张卡片。当你踮起脚尖时,你会发出一张卡片。当您获得 A 时,您会发出一张卡片。

任何关心的人都会进入您的群发邮件列表,并且可以按照他们认为合适的方式进行回复。

这种依赖关系非常适合 UI。如果我有一个缓慢的进程(例如),即使取得了进展,它也可以触发。进度条元素可以观察并更新其覆盖范围。OK 按钮可以观察到这一点并在 100% 时变为活动状态。光标可以观察到动画,直到进度为 100%。这些观察者都不需要相互了解。此外,这些元素中的任何一个都不需要知道是什么在驱动它们。

于 2009-04-20T18:17:31.937 回答
1

这种模式可能是最基本的模式之一,如果不是最基本的模式的话。

涉及两个“人”;发布者和订阅者/观察者

观察者只是要求发布者在有“新闻”时通知他。新闻在这里可以是任何重要的东西。它可以是空气的温度,可以是网站上的新帖子,也可以是一天中的时间。

于 2009-04-20T18:19:01.497 回答
1

替代文字
(来源:headfirstlabs.com
正如查看“ Head First:Design Patterns ”所说,他们也有一些关于这本书设计冥想的论坛。

观察者模式遵循好莱坞原则“不要叫我们我们叫你”

模式的好网站 http://www.dofactory.com/Patterns/PatternObserver.aspx

于 2009-04-20T19:35:26.833 回答
1

您可能遇到的问题是定义正确的接口。该接口定义了订阅者和发布者之间的交互。

首先制作一个 C# WinForms 应用程序

像这样设置 Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            Application.Run(new Form1());
        }
    }

    interface IObserver
    {
        void Refresh(List<string> DisplayList);
    }

    class ObserverList : List<IObserver>
    {
        public void Refresh(List<String> DisplayList)
        {
            foreach (IObserver tItem in this)
            {
                tItem.Refresh(DisplayList);
            }
        }

    }
}

我们在这里做两件事。第一个是订阅者将实现的接口。然后是发布者的列表以保存所有订阅者。

然后用两个按钮制作一个表格,一个标记为表格 2,另一个标记为表格 3。然后添加一个文本框,然后添加另一个按钮标记为添加

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        private List<string> DataList= new List<string>();
        private ObserverList MyObservers = new ObserverList();
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 frmNewForm = new Form2();
            MyObservers.Add(frmNewForm);
            frmNewForm.Show();
            MyObservers.Refresh(DataList);
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Form3 frmNewForm = new Form3();
            MyObservers.Add(frmNewForm);
            frmNewForm.Show();
            MyObservers.Refresh(DataList);

        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void button3_Click(object sender, EventArgs e)
        {
            DataList.Add(textBox1.Text);
            MyObservers.Refresh(DataList);
            textBox1.Text = "";
        }

    }
}

我特意设置了 Form2 按钮和 FOrm3 按钮,以便为每种类型的 Form 制作多个副本。例如,您可以同时拥有十二个人。

您会注意到,在创建每个表单后,我将其放入 Observers 列表中。我能够这样做是因为 Form2 和 Form3 都实现了 IObserver。显示表单后,我在观察者列表上调用刷新,以便使用最新数据更新新表单。请注意,我可以将其转换为 IObserver 的变量并仅更新该表单。我试图尽可能简短。

然后对于添加按钮“Button3”,我从文本框中提取文本并将其存储在我的 DataList 中,然后刷新所有观察者。

然后制作Form2。添加一个列表框和以下代码。

使用系统;

using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form2 : Form,IObserver
    {
        public Form2()
        {
            InitializeComponent();
        }

        private void Form2_Load(object sender, EventArgs e)
        {

        }

        void IObserver.Refresh(List<string> DisplayList)
        {
            this.listBox1.Items.Clear();
            foreach (string s in DisplayList)
            {
                this.listBox1.Items.Add(s);
            }
            this.listBox1.Refresh();
        }

    }
}

然后添加 Form3,一个组合框并添加以下代码。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form3 : Form,IObserver
    {
        public Form3()
        {
            InitializeComponent();
        }

        private void Form3_Load(object sender, EventArgs e)
        {

        }
        void IObserver.Refresh(List<string> DisplayList)
        {
            this.comboBox1.Items.Clear();
            foreach (string s in DisplayList)
            {
                this.comboBox1.Items.Add(s);
            }
            this.comboBox1.Refresh();
        }
    }
}

你会注意到每个表单实现的 IObserver 接口的刷新方法略有不同。一个用于列表框,另一个用于组合框。接口的使用是这里的关键元素。

在现实世界的应用中,这个例子会更复杂。例如,而不是在刷新接口中传递字符串列表。它不会有任何参数。相反,Publisher(本例中为 Form1)将实现一个发布者接口,并在 Observers 被初始化时向它们注册自己。每个观察者都可以在其初始化例程中接受发布者。然后,当它刷新时,它将通过接口公开的方法将字符串列表从发布者中拉出。

对于具有多种数据类型的更复杂的应用程序,这允许您自定义实现 IObserver 的表单从发布者中提取的数据。

当然,如果您只希望观察者能够显示字符串列表或特定数据。然后将其作为参数的一部分传递。接口明确说明了每一层要做什么。这样 5 年后,您可以查看代码和代码“哦,它在做什么”。

于 2009-04-20T20:15:08.143 回答
0

用最简单的术语来说,有两个组件:Observer 和 Observed。

在外部,Observed 需要一种添加(注册)和删除 Observer 的方法。
在内部,被观察者需要一个已注册观察者的列表。

观察者需要一个公共方法,例如 Notify() 或 Notify(params)。

每当 Observed 发生特定事情时,它都会遍历列表并在每个注册的观察者上调用 Notify()。

在最简单的情况下,它是一个简单的通知,上面写着“嘿,观察者,我的数据已更改,过来刷新一下自己”。在更复杂的版本中,参数可以过去让观察者知道发生了什么变化。

在 Model-View-Controller 中,Observed 通常是一个实体对象——保存数据的东西。控制器是观察者。它监视模型中的更改,并告诉视图在对更改感兴趣时自行更新。

Java 事件侦听器是这种模式的真实实现。

于 2009-04-20T18:19:35.893 回答
0

想象一下,您有一个您想要观察其行为(或状态)的对象。例如,当字段 A 的值达到 10 时,您希望获得有关该事件的通知,而无需真正了解您要观察的这个复杂对象的实现细节。你定义一个接口,调用它 Observable 并让你的目标实现这个接口,它应该至少有两个方法来注册和取消注册一个 Observer,这反过来是当字段 A 达到 10 时 Observer 将调用的对象。你的 Observer只需调用 Observable 进行注册(完成后取消注册)。Observable 通常会维护一个观察者列表并立即通知他们,或者根据您的需要通知他们。它也可以同步或异步完成,这取决于你。这是非常简单的解释,无需编写代码。一旦你明白了,

于 2009-04-20T18:19:52.430 回答
0

观察者(发布/订阅)

当一个对象改变状态时,它会通知其他在运行时已注册其兴趣的对象。通知对象(发布者)向其所有观察者(订阅者)发送事件(发布)。

于 2009-04-20T18:50:47.737 回答
0

一句话:

一个对象(主体)允许其他对象(观察者)注册通知。

实际例子:

假设您有一个应用程序,并且您想让其他开发人员构建插件。

您可以创建一个 PluginSubject 类,并在其上放置一个名为 NotifyOrderCreated 的方法。每当在您的订单屏幕上创建新订单时,它都会调用 PluginSubject.NotifyOrderCreated。

发生这种情况时,PluginSubject 会获取 PluginObservers 列表并在每个上调用 PluginObserver.Notify,并传入描述事件的消息。

这可以实现一些非常简洁的功能。

比你想知道的多得多:

我最近确实这样做了,所以我将更深入地举这个例子——如果你要求你的观察者实现一个特殊的接口,比如 IPluginObserver,你可以使用反射来遍历你的程序集中的类型并即时实例化插件。

然后,您可以允许用户注册他们自己的程序集(您必须在某处存储程序集名称列表,然后遍历它),并且 bam,您获得了可扩展性!

于 2009-04-20T20:11:40.430 回答
0

观察者是一种解耦的手段,即松开两个对象之间的联系。您想要这样做是因为它使您的代码更整洁且更易于维护。这几乎是所有设计模式的目标:更易于阅读,更易于维护代码。

在这个模式中,你有两个类,两个对象:发布者和观察者。Publisher 是一个实际上做了一些工作的类,然后它会不时地调用任何观察者的方法来告诉他们这件事。它知道要调用哪些类,因为它保留了订阅的观察者列表。

所以你的 Publisher 可能看起来像这样:

class Publisher
{
    List<Observer> observers = new List<Observer>();

public Add(Observer o)
{
    observers.Add(o);
}

private AlertObservers()
{
    foreach(Observer o in observers)
    {
        o.Alert();
    }
}

Publisher 确实完成了大部分工作。Observer 需要做的就是添加到列表中并实现被调用的方法。像这样:

class Observer
{
    public Observer(Publisher pub)
    {
        pub.Add(this);
    }

    public Alert()
    {
        System.Console.WriteLine("Oh no, stuff is happening!");
    }
}

这是一个关于它如何工作的非常简单的想法。现在,为什么它很有价值?看起来还不错吧?一个原因是因为我不使用接口,这将允许我设置许多具有观察者功能的类,并且发布者不需要了解更多关于它们的信息,除非它们可以接收 Alert() 调用。另请注意,Publisher 将尝试在它拥有的任何和所有 Observers 上调用 Alert,即使它没有。

现在,在 C# 世界中,该语言通过其 Event 对象具有此模式的内置版本。事件非常强大,它使用委托,这是一种在另一个方法调用中将方法作为参数传递的方式。它们允许一些严重的脱钩,但我会把它留到一个新问题上。

于 2009-04-20T21:29:49.150 回答
0

很少有实时示例:

  1. 报纸/杂志/邮件列表订阅或任何一般订阅
  2. 在 MS Office Communicator 中标记同事
  3. 推特
于 2010-05-02T04:42:31.987 回答
0

那些认为 .NET 中的事件确实是观察者模式的实现的人并不是在拉扯你的链条。这是真的。至于这实际上是如何工作的,无论是从高层次的角度还是在更多特定于实现的细节方面,我都将使用一个类比。

想想报纸出版商。在 OOP 术语中,我们可能将报纸视为可观察的事物。但它是如何工作的?显然,报纸本身的实施细节(即在这个类比中,记者、作家、编辑等都回到办公室把报纸放在一起)没有公开。人们(观察者)不会聚集在一起观看报纸出版商的员工做他们的工作。他们不能仅仅这样做,因为没有协议(或接口)可以做到这一点。

这就是你观察(即阅读)报纸的方式:你订阅它。你把你的名字列在该论文的订阅者名单上,然后出版商知道每天早上都会在你家门口送上一份。如果您不想再观察(阅读)它,请退订;你把你的名字名单上删除了。

现在,这似乎是一个抽象的类比;但它实际上与 .NET 事件的工作方式几乎是完美的平行

给定一些旨在可观察的类,它的实现通常不需要为公众所知。但是,它将向公众公开一种特定类型的接口,并且该接口是一个事件。想要观察此事件的代码实际上将自己注册为订阅者:

// please deliver this event to my doorstep
myObject.SomeEvent += myEventHandler;

当相同的代码决定它不想再收到此事件的通知时,它会取消订阅:

// cancel my subscription
myObject.SomeEvent -= myEventHandler;

现在快速讨论一下代表以及这段代码是如何工作的。您可能知道也可能不知道,委托本质上是一个存储方法地址的变量。通常,此变量具有类型——就像声明为、、等的变量一样,都有类型。对于委托类型,此类型由方法的签名定义;也就是它的参数和它的返回值。特定类型的委托可以指向执行任何操作的任何方法,只要该方法具有适当的签名即可。intdoublestring

所以回到报纸的类比:为了成功订阅一份报纸,你实际上必须遵循一个特定的模式。具体来说,您需要提供一个有效的地址,您希望将报纸投递到该地址。你不能只是说,“是的,把它发给丹。” 你不能说,“我要一个培根芝士汉堡”。您必须向发布者提供他们可以有意义地使用的信息。在 .NET 事件的世界中,这意味着需要提供正确签名的事件处理程序。

在大多数情况下,这个签名最终会是这样的一些方法:

public void SomeEventHandler(object sender, EventArgs e) {
    // anything could go in here
}

以上是可以存储在类型委托变量中的方法EventHandler。对于更具体的情况,有泛型EventHandler<TEventArgs>委托类型,它描述了与上述类似的方法,但具有.e EventArgs

请记住,委托实际上是指向方法的变量,在 .NET 事件和报纸订阅之间建立最终联系并不难。事件的实现方式是通过委托列表,可以添加和删除项目。这实际上就像报纸出版商的订阅者列表,每天早上分发报纸时,每个订阅者都会收到一份。

无论如何,希望这可以帮助您在一定程度上了解观察者模式。当然,这种模式还有许多其他类型的实现,但 .NET 事件是大多数 .NET 开发人员都熟悉的范例,所以我认为这是一个很好的起点,可以从中加深理解。

于 2010-05-02T05:42:15.453 回答