您如何用初学者语言描述观察者模式?

时间:2009-04-20 18:05:10

标签: c# design-patterns observer-pattern

目前,我的理解水平低于网络上有关观察者模式的所有编码示例。我理解它只是一个订阅,当代理注册更改时更新所有其他事件。但是,在我对其益处和用途的真正理解中,我非常不稳定。我做了一些谷歌搜索,但大多数都高于我的理解水平。

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

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

16 个答案:

答案 0 :(得分:27)

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

您,观察员,订阅邮件列表并观察列表。如果您不再对该列表感兴趣,请取消订阅。

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

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

的问候,
弗兰克

答案 1 :(得分:5)

查看"Head First: Design Patterns",了解一些非常简单的,你的前额易于理解的主要模式描述。

对于Observer,重要的是要了解它描述了一对多关系,并使用订阅模型在发生更改时告知其他类。 RSS,Atom和Twitter都沿着这些方向发展。

答案 2 :(得分:3)

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

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

答案 3 :(得分: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);
    }
}

输出如下。

Observer X: The value of Notifier A changed from 0 to 123.
Observer Y: The value of Notifier A changed from 0 to 123.
Observer Y: The value of Notifier B changed from 0 to 999.
Observer X: The value of Notifier A changed from 123 to 1.
Observer Y: The value of Notifier A changed from 123 to 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()

答案 4 :(得分:1)

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

这是某些物体观察物体,观察物体变化的手段。

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

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

答案 5 :(得分:1)

观察者就像一条直接的沟通渠道。而不是让你所有的亲戚打电话给你,了解你是怎样的,当你生病时写一张卡片,所有感兴趣的人都会得到它(或副本)。当你变得更好时,你会发一张卡片。当你踩脚趾时,你发出一张卡片。当你拿到A时,你会发一张卡片。

任何关心的人都可以访问您的群发邮件列表,并且可以在他们认为合适的情况下做出回应。

此依赖关系非常适合UI。如果我的进程很慢(例如),即使进行了进程也可以触发。进度条元素可以观察并更新其覆盖范围。一个OK按钮可以观察到并以100%激活。光标可以观察到一个动画,直到进度为100%。这些观察者都不需要彼此了解。此外,这些元素都不需要知道是什么驱使它们。

答案 6 :(得分:1)

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

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

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

答案 7 :(得分:1)

alt text
(来源:headfirstlabs.com
如上所述,结帐“Head First: Design Patterns”他们还有一些forums regarding the book和一个design meditation

观察者模式遵循好莱坞原则“不要打电话给我们,我们称呼你”

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

答案 8 :(得分: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的多个副本。例如,您可以同时拥有12个。

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

然后对于Add按钮'Button3',我从文本框中拉出文本将它存储在我的DataList中,然后刷新所有观察者。

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

使用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 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接口的刷新方法略有不同。一个是列表框,另一个是组合框。接口的使用是这里的关键要素。

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

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

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

答案 9 :(得分:0)

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

在外部,观察者需要一种方法来添加(注册)和删除观察者 在内部,观察者需要一份注册观察员名单。

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

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

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

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

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

答案 10 :(得分:0)

想象一下,你有一个你想要观察其行为(或状态)的对象。例如,当字段A达到值10时,您希望获得有关该事件的信息,而无需实际结合您要观察的此复杂对象的实现细节。 你定义一个接口,称之为Observable并让你的目标实现这个接口,它应该至少有两个方法来注册和取消注册一个Observer,而后者又是当Field A命中10时Observer调用的对象。你的观察者只需调用Observable进行注册(并在完成时取消注册)。 Observable通常会保留一份观察员名单并立即通知他们,或者您可以随意通知他们。它也可以同步或异步完成,由您自己决定。这是非常简单的解释,无需编写代码。一旦理解了它,实现可以在细节上有所不同,以满足您的特定需求。

答案 11 :(得分:0)

观察员(发布/订阅)

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

答案 12 :(得分:0)

用一句话:

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

实际例子:

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

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

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

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

超出您想知道的方式:

我最近这样做了,所以我会更深入地举例 - 如果你需要你的观察者实现一个特殊的接口,让我们说IPluginObserver,你可以使用反射来遍历你的程序集中的类型并实例化插件苍蝇

然后你可以允许用户注册他们自己的程序集(你必须在某个地方存储一个程序集名称列表,然后再行走它),而bam,你有可扩展性!

答案 13 :(得分:0)

Observer是一种解耦方法,即松开两个对象之间的连接。你想要它,因为它使你的代码更整洁,更容易维护。这几乎是所有设计模式的目标:更易于阅读,更易于维护代码。

在此模式中,您有两个类,两个对象:publisher和observer。 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!");
    }
}

对于它是如何工作的,这是一个非常准确的想法。现在,为什么这有价值?看起来很好耦合了吧?其中一个原因是因为我不使用接口,这将允许我设置许多具有Observer功能的类,而Publisher除了可以接收Alert()调用之外,不需要了解更多关于它们的信息。另请注意,Publisher将尝试在其拥有的任何和所有观察者上调用警报,即使它没有。

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

答案 14 :(得分:0)

实时很少示例:

  1. 报纸/杂志/邮件列表 订阅或任何一般订阅
  2. 在MS Office中标记同事 通信器
  3. 微博

答案 15 :(得分:0)

那些建议.NET中的事件确实是Observer模式的实现的人并没有拉动你的链条;这是真的。至于这实际上是如何工作的,无论是从高层次的角度还是从更多特定于实现的细节,我都会用一个类比。

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

这是您观察(即阅读)报纸的方式:您订阅了它。您可以在该论文的订阅者列表中获取您的姓名,然后发布者知道每天早上都会在您的家门口发送一个。如果您不想再观察(阅读)它,您可以取消订阅;你得到你的名字 off 那个名单。

现在,这似乎是一个抽象的类比;但它实际上与.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>委托类型,它描述了一种类似于上面的方法,但是某个类型的<{1}}参数派生自 {{1} }。

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

无论如何,希望这有助于你在某种程度上了解Observer模式。当然,这种模式还有许多其他类型的实现,但.NET事件是大多数.NET开发人员熟悉的范例,因此我认为这是开发理解的良好起点。