Как бы вы описали шаблон Observer на языке начинающих?

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

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

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

Ответы

Ответ 1

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

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

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

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

С уважением,
Франк

Ответ 2

Откажитесь от "Head First: Design Patterns" для некоторых действительно, smack-your-forehead легко следовать описаниям основных шаблонов.

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

Ответ 3

Одно слово: Twitter

Ответ 4

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

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

Ответ 5

Если я говорю "Событие", значит, он обманывает?

Ответ 6

Есть два объекта NOTIFIER и OBSERVER. NOTIFIER ничего не знает о OBSERVER, в то время как OBSERVER знает, что NOTIFER реализует событие.

OBSERVER использует событие для информирования других объектов о том, что что-то произошло. Проще говоря, это список методов. Поскольку OBSERVER хочет получить уведомление, если что-то случится, OBSERVER добавляет метод, который должен быть вызван, если что-то случится, в событие NOTIFER.

Итак, если это произойдет, NOTIFIER публикует это событие, NOTIFIER просто перебирает список методов и вызывает их. Когда вызывается метод, добавленный OBSERVER, OBSERVER знает, что все происходит и может делать то, что когда-либо требуется в этом случае.

Вот пример класса notifier с событием 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().

Ответ 7

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

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

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

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

Ответ 8

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

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

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

Ответ 9

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

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

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

Ответ 11

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

Сначала создайте приложение С# 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);
            }
        }

    }
}

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

Затем создайте форму с двумя кнопками, одна с надписью Form 2 и другая маркированная форма 3. Затем добавьте текстовое поле, а затем еще одну кнопку с надписью Add

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. После того, как я покажу форму, я вызываю обновление в списке Observer, чтобы новая форма была обновлена ​​с последними данными. Заметьте, я мог бы передать его переменной 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();
        }

    }
}

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

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

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

Ответ 12

В нем очень просты, существуют две компоненты: Observer и Observed.

Внешне, Observed требуется способ добавления (регистрации) и удаления Observer.
Внутри Observed требуется список зарегистрированных наблюдателей.

Наблюдателю нужен открытый метод, например Notify() или Notify (params).

Всякий раз, когда происходит какое-либо конкретное событие с наблюдаемым, он прокручивается по списку и вызывает Notify() для каждого зарегистрированного наблюдателя.

В простейшем случае это простое уведомление, в котором говорится, что "Hey, Observer, My Data Changed, приходят и обновляют себя" В более сложных версиях параметры могут быть пропущены, позволяя наблюдателю узнать, что изменилось.

В Model-View-Controller Observed обычно является объектом сущности - что-то, что содержит данные. Контроллер - Наблюдатель. Он следит за изменениями в Модели и сообщает View обновить себя, если он заинтересован в изменении.

Java-прослушиватели событий являются реалистичной реализацией этого шаблона.

Ответ 13

Представьте, что у вас есть объект, чье поведение (или состояние) вы хотите наблюдать. Например, когда поле А достигает значения 10, вы хотите получить информацию об этом событии, не связавшись с деталями реализации этого сложного объекта, который вы хотите наблюдать. Вы определяете интерфейс, называете его Observable и позволяете своей целевой реализации реализовать этот интерфейс, он должен иметь как минимум два метода регистрации и отмены регистрации Observer, который, в свою очередь, является объектом, который будет вызван Observer, когда поле A достигнет 10. Your Observer просто вызывает Observable для регистрации (и отмените регистрацию, когда закончите). Наблюдаемые обычно поддерживают список наблюдателей и уведомляют их сразу или, как вам заблагорассудится. Он также может выполняться синхронно или асинхронно, это зависит от вас. Это очень упрощенное объяснение без написания кода. Как только вы это поймете, реализации могут отличаться деталями, чтобы соответствовать вашим конкретным потребностям.

Ответ 14

Наблюдатель (публикация/подписка)

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

Ответ 15

В одном предложении:

Объект (Субъект) позволяет другим объектам (Наблюдателям) регистрироваться для уведомлений.

Практический пример:

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

Вы можете создать класс PluginSubject и поместить на него метод NotifyOrderCreated. Когда новый порядок создается на экране вашего заказа, он вызывает PluginSubject.NotifyOrderCreated.

Когда это произойдет, PluginSubject получает список PluginObservers и вызывает PluginObserver.Notify для каждого из них, передавая сообщение, описывающее событие.

Это позволяет использовать некоторые действительно опрятные функции.

Способ больше, чем вы хотите знать:

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

Затем вы можете разрешить пользователям подписывать свои собственные сборки (вам нужно где-то сохранить список имен сборки, а затем пройти), и bam, у вас есть расширяемость!

Ответ 16

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

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

Таким образом, ваш издатель может выглядеть примерно так:

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

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

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

Издатель действительно выполняет большую часть работы. Все, что требуется Observer, - это добавить в список и реализовать вызванный метод. Например:

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

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

Это довольно баребон идея о том, как это работает. Итак, почему это ценно? Выглядит неплохо, а? Одна из причин этого заключается в том, что я не использую интерфейс, который позволил бы мне создать множество классов с функциональностью Observer, а Publisher никогда не будет знать ничего о них больше, кроме того, что они могут получить вызов Alert(). Также обратите внимание, что издатель будет пытаться вызвать Alert на всех Наблюдателях, которые он имеет, даже если он не имеет.

Теперь, в мире С#, язык имеет встроенную версию этого шаблона через объекты Event. События очень мощные и используют делегаты, что является способом передачи метода в качестве параметра в другом вызове метода. Они допускают серьезную развязку, но я бы сохранил это для нового вопроса.

Ответ 17

Очень мало примеров в реальном времени:

  • Газета/Журнал/Список рассылки Подписка или любая подписка в целом
  • Пометка коллеги в MS Office Коммуникатор
  • Twitter

Ответ 18

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

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

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

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

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

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

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

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

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

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

В большинстве случаев эта подпись заканчивается следующим способом:

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

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

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

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