Как бы вы описали шаблон Observer на языке начинающих? - PullRequest
14 голосов
/ 20 апреля 2009

В настоящее время мой уровень понимания ниже всех примеров кодирования в Интернете о шаблоне наблюдателя. Я понимаю это просто как почти подписку, которая обновляет все другие события, когда вносится изменение, которое регистрирует делегат. Тем не менее, я очень нестабилен в своем истинном понимании преимуществ и использования. Я немного погуглил, но большинство выше моего уровня понимания.

Я пытаюсь реализовать этот шаблон с моим текущим домашним заданием, и для того, чтобы по-настоящему разобраться в моем проекте, нужно лучше понять сам шаблон и, возможно, пример, чтобы увидеть, как его использовать. Я не хочу принуждать этот шаблон к чему-то простому, я должен понять цель и разработать соответствующие методы, чтобы он действительно служил хорошей цели. Мой текст на самом деле не входит в это, просто упоминает об этом в одном предложении. Мне было трудно понять MSDN, так как я новичок в этом, и это кажется более сложной темой.

Как бы вы описали этот шаблон Observer и его использование в C # для новичка? Для примера, пожалуйста, держите код очень простым, чтобы я мог понять цель больше, чем сложные фрагменты кода. Я пытаюсь использовать его эффективно с некоторыми простыми манипуляциями со строками текстовых полей и использованием делегатов для моего назначения, так что указатель поможет!

Ответы [ 16 ]

27 голосов
/ 20 апреля 2009

Лучший пример, который я могу привести, это список рассылки (в качестве примера).

Вы, наблюдатель, подписываетесь на список рассылки, и вы наблюдаете этот список. Когда вас больше не интересует список, вы отписываетесь.

Эта концепция является моделью наблюдателя. Два или более классов участвуют. Один или несколько классов подписываются на класс издателя (существуют разные имена), а затем первый класс (и каждый подписывающийся класс) получит уведомление, когда издатель пожелает.

Вот как я объяснил это моей жене, которая часто слушает мои разговоры о программировании и теории дизайна. Это имело смысл для нее. Я понимаю, что это может быть слишком просто для вас, но это хорошее начало ...

С уважением,
Frank

5 голосов
/ 20 апреля 2009

Проверьте "Head First: Design Patterns" для некоторых действительно, чмокать в лоб, легко следовать описаниям основных шаблонов.

Для Observer важно понимать, что оно описывает отношение один-ко-многим и использует модель подписки для сообщения другим классам, когда произошли изменения. RSS, Atom и Twitter работают в этом направлении.

3 голосов
/ 20 апреля 2009

Обозреватель хочет знать, когда что-то меняется, поэтому он подписывается на Субъект. Субъект не знает Наблюдателя. Это важная часть. Субъект просто определяет интерфейс (или делегат), который должен предоставить наблюдатель, и разрешает регистрацию.

Короче говоря: шаблон Observer позволяет вашему наблюдателю вызываться от субъекта, которому все равно, кто он и существует ли он.

2 голосов
/ 20 апреля 2009

Есть два объекта 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().

1 голос
/ 21 апреля 2009

Возможно, у вас проблемы с определением правильных интерфейсов. Интерфейс определяет взаимодействие между подписчиком и издателем.

Сначала создайте приложение 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, чтобы сделать несколько копий каждого типа формы. Например, у вас может быть двенадцать сразу.

Вы заметите, что после создания каждой формы я помещаю ее в список наблюдателей. Я могу сделать это, потому что как Form2, так и Form3 реализуют IObserver. После того, как я показываю форму, я вызываю команду refresh в списке наблюдателей, поэтому новая форма обновляется с использованием самых последних данных. Обратите внимание, что я мог бы привести его к переменной IObserver и обновить только эту форму. Я стараюсь быть максимально кратким.

Затем для кнопки «Добавить» Button3 я извлекаю текст из текстового поля, сохраняю его в своем списке данных и затем обновляю всех наблюдателей.

Тогда сделайте 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, немного отличающийся. Один для списка, другой для поля со списком. Использование интерфейсов является ключевым элементом здесь.

В реальном приложении этот пример будет более сложным. Например, вместо передачи списка строк в интерфейсе обновления. Это не будет иметь никаких параметров. Вместо этого издатель (в данном примере Form1) реализует интерфейс издателя и регистрируется у наблюдателей по мере их инициализации. Каждый наблюдатель сможет принять издателя в своей процедуре инициализации. Затем, когда он обновляется, он вытягивает список строк из Publisher с помощью метода, предоставляемого через интерфейс.

Для более сложных приложений с несколькими типами данных это позволяет настроить, какие данные форма, реализующая IObserver, извлекает из издателя.

Конечно, если вы ТОЛЬКО хотите, чтобы наблюдатель мог отображать список строк или конкретные данные. Затем передайте его как часть параметров. Интерфейс делает явным то, что хочет сделать каждый слой. Таким образом, через 5 лет вы можете посмотреть на код и код «Ой, что он делает».

1 голос
/ 20 апреля 2009

alt text
(источник: headfirstlabs.com )
Как было сказано в разделе « Head First: Design Patterns », у них также есть несколько форумов , касающихся книги и дизайнерской медитации .

Образец наблюдателя следует голливудскому принципу «Не звоните нам, мы вам звоним»

Хороший сайт для шаблонов http://www.dofactory.com/Patterns/PatternObserver.aspx

1 голос
/ 20 апреля 2009

Этот шаблон, вероятно, один из самых основных, если не самый базовый шаблон.

Участвуют два "человека"; издатель и подписчик / наблюдатель .

Наблюдатель просто просит издателя уведомить его о появлении «новостей». Здесь новости могут быть важны Это может быть температура воздуха, это может быть новый пост на сайте, это может быть время суток.

1 голос
/ 20 апреля 2009

Наблюдатель - это прямая линия связи. Вместо того, чтобы все ваши родственники звонили вам, чтобы узнать, как вы себя чувствуете, когда вы заболели, напишите открытку, и все заинтересованные лица получат ее (или копию). Когда вы поправляетесь, вы отправляете открытку. Когда вы загрызаете палец, вы отправляете открытку. Когда вы получаете A, вы отправляете открытку.

Любой, кому небезразлично, попадает в ваш список рассылки и может ответить, как ему угодно.

Эта зависимость отлично подходит для пользовательского интерфейса. Если у меня есть процесс, который является медленным (например), он может запустить даже тогда, когда достигнут прогресс. Элемент индикатора выполнения может наблюдать это и обновлять свое покрытие. Кнопка ОК может наблюдать это и стать активной на все 100%. Курсор может наблюдать это за анимацией, пока прогресс не станет 100%. Ни одному из этих наблюдателей не нужно знать друг о друге. Кроме того, ни один из этих элементов не должен строго знать, что их движет.

1 голос
/ 20 апреля 2009

Шаблон наблюдателя так же, как звучит -

Это средство для некоторых объектов наблюдать за объектом, наблюдая за его изменениями.

В C # это становится несколько простым, поскольку события в основном являются языковым средством реализации шаблона наблюдателя. Если вы когда-либо использовали события, вы использовали шаблон наблюдателя.

В других языках это не встроено, поэтому было много попыток формализовать подходы к решению этой проблемы.

0 голосов
/ 02 мая 2010

Те, кто предположил, что события в .NET действительно являются реализацией шаблона Observer, не тянут вашу цепочку; это так. Что касается того, как это на самом деле работает, как с точки зрения высокого уровня, так и с точки зрения более специфичных для реализации деталей, я буду использовать аналогию.

Подумайте о газетном издателе. В терминах ООП мы можем думать о газете как о наблюдаемой вещи. Но как это работает? Очевидно, что детали реализации самой газеты (то есть, по этой аналогии, журналисты, писатели, редакторы и т. Д., Все работающие в офисе, чтобы собрать газету) не публикуются. Люди ( наблюдатели ) не собираются вместе и наблюдают за тем, как сотрудники издательства делают свою работу. Они не могут просто сделать , потому что для этого нет протокола (или интерфейса ).

Вот как вы наблюдаете (то есть читаете) газету: вы подписываетесь на нее. Вы вносите свое имя в список подписчиков на эту газету, а затем издатель знает, что вы должны доставить его на ваш порог каждое утро. Если вы не хотите больше наблюдать (читать), отмените подписку; вы получили свое имя из этого списка.

Теперь это может показаться абстрактной аналогией; но на самом деле почти идеальная параллель тому, как работают события .NET .

Учитывая некоторый класс, предназначенный для наблюдения, его реализация в общем случае не должна быть известна общественности. Тем не менее, он будет представлять особый вид интерфейса для публики, и этот интерфейс является событием. Код, который хочет наблюдать это событие, по существу регистрируется как подписчик:

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

Когда тот же код решает, что больше не хочет получать уведомления об этом событии, он отписывается:

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

Теперь для быстрого обсуждения делегатов и того, как на самом деле работает этот код. Делегат, как вы можете знать, а может и не знать, по сути является переменной, в которой хранится адрес метода. Как правило, эта переменная имеет тип - как переменные value , объявленные как int, double, string и т. Д., Все имеют типы. В случае типов делегатов этот тип определяется методом signature ; то есть его параметры и его возвращаемое значение. Делегат определенного типа может указывать на любой метод, который выполняет любое действие, если этот метод имеет соответствующую сигнатуру.

Итак, вернемся к аналогии с газетой: чтобы успешно подписаться на газету, вы должны фактически следовать определенной схеме. В частности, вам нужно указать действительный адрес, по которому вы хотите доставить газету. Вы не можете просто сказать: «Да, отправь это Дэну». Вы не можете сказать: «У меня будет чизбургер с беконом». Вы должны предоставить издателю информацию, с которой он может эффективно работать. В мире событий .NET это означает необходимость предоставления обработчика событий правильной подписи.

В большинстве случаев эта сигнатура оказывается таким способом:

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

Выше приведен метод, который можно сохранить в переменной делегата типа EventHandler. Для более конкретных случаев есть общий тип делегата EventHandler<TEventArgs>, который описывает метод, аналогичный описанному выше, но с параметром e некоторого типа , полученным из EventArgs.

Учитывая, что делегаты - это действительно переменные, указывающие на методы, нетрудно установить окончательную связь между событиями .NET и подписками на газеты. Реализация событий осуществляется с помощью списка делегатов, к / из которых элементы могут добавляться и удаляться. Это действительно похоже на список подписчиков газетного издателя, каждый из которых получает копию, когда газеты распространяются каждое утро.

В любом случае, надеюсь, это помогло вам немного разобраться с паттерном Observer. Конечно, существует множество других реализаций этого шаблона, но события .NET - это парадигма, с которой знакомы большинство разработчиков .NET, поэтому я думаю, что это хорошая отправная точка для развития понимания.

...