Loading...
ArmenPotourlyan+deleted! avatar ArmenPotourlyan+deleted! 488 Точки

[Useful Info][C# OOP Advanced] The Observer Pattern vs C# Events

Здравейте, колеги,

 

Тези дни разглеждах какво представляват event-ите в C# и се сетих, че съм виждал нещо подобно в Observer Design Pattern-a. Реших да опитам да напиша един прост пример с Observer и после да го преработя да използва вградените възможности на C# - event-ите. Ето какво се получи:

 

Observer Pattern-ът свързва много обекти (напр. YouTube потребители) към един обект (напр. YouTube канал) така, че ако обектът промени състоянието си (напр. наличност на ново видео в YouTube канала), свързаните към него обекти да бъдат уведомени (YouTube абонатите получават нотификация).

http://www.dofactory.com/net/observer-design-pattern

http://www.dofactory.com/net/observer-design-pattern

Как се реализира това? Имаме интерфейс ISubject, който дефинира методи за добавяне Attach и премахване Detach на абонати и метод за уведомяване Notify. Също така имаме интерфейс за абонатите IObserver, който дефинира метод Update. Конкретният Subject клас държи колекция от абонати (напр. List<IObserver>) и в Notify() минава през всеки един от тях и извиква Update(). Конкретните Observer класове пък дефинират какво точно да се случва в Update метода.

public interface ISubject
{
    void Attach(IObserver observer);
    void Detach(IObserver observer);
    void Notify();
}

public interface IObserver
{
    void Update(ISubject sender);
}

 

Нека имаме конкретен клас Lecture, който имплементира ISubject, и конкретен клас Student, който импрементира IObserver. При всяка смяна на името на инструктора, всички студенти, които са закачени към лекцията, се уведомяват в метода Notify.

public class Lecture : ISubject
{
    private List<IObserver> observers;

    private string trainer;

    public Lecture(string topic, string trainer)
    {
        this.Topic = topic;
        this.Trainer = trainer;
        this.observers = new List<IObserver>();
    }

    public string Topic { get; }

    public string Trainer
    {
        get { return this.trainer; }

        set
        {
            this.trainer = value;
            this.Notify();
        }
    }
        
    public void Attach(IObserver observer)
    {
        this.observers.Add(observer);
    }

    public void Detach(IObserver observer)
    {
        this.observers.Remove(observer);
    }

    public void Notify()
    {
        foreach (var observer in this.observers)
        {
            observer.Update(this);
        }
    }
}
public class Student : IObserver
{
    public string Name { get; set; }

    public void Update(ISubject sender)
    {
        var trainer = (sender as Lecture).Trainer;
        Console.WriteLine($"{this.Name} does not like the new trainer {trainer}!"); 
    }
}

 

В Main метода:

var lecture = new Lecture("C# events", "Ivaylo Kenov");
var studentPesho = new Student() { Name = "Pesho" };
var studentGosho = new Student() { Name = "Gosho" };

lecture.Attach(studentPesho);
lecture.Attach(studentGosho);
lecture.Trainer = "Chuck Norris";
// Pesho does not like the new trainer Chuck Norris!
// Gosho does not like the new trainer Chuck Norris!
lecture.Detach(studentPesho);
lecture.Trainer = "Bill Gates";
// Gosho does not like the new trainer Bill Gates!

 

Как изглежда същото нещо със C# event-ите? Нямаме нужда от старите интерфейси, така че методите Attach, Detach, Notify в класа Lecture вече са излишни. Дефинираме си event TrainerNameChanged и в set-ъра на свойството Trainer го вдигаме(извикваме), т.е. всеки път, когато се смени името на лектора, ще бъде обработено въпросното събитие(стига да има прикачени студенти).

public class Lecture 
{
    public event EventHandler TrainerNameChanged;

    private string trainer;

    public Lecture(string topic, string trainer)
    {
        this.Topic = topic;
        this.Trainer = trainer;
    }

    public string Topic { get; }

    public string Trainer
    {
        get { return this.trainer; }

        set
        {
            this.trainer = value;

            TrainerNameChanged?.Invoke(this, EventArgs.Empty);
        }
    }
}

 

Как става закачането и откачането на студенти? Класът Student дефинира метод за обработка на събитието OnTrainerNameChanged и старите методи Attach и Detach са заменени от += и –= оператори:

public class Student
{
    public string Name { get; set; }

    public void OnTrainerNameChanged(object sender, EventArgs e)
    {
        var trainer = (sender as Lecture).Trainer;
        Console.WriteLine($"{this.Name} does not like the new trainer {trainer}!"); 
    }
}

 

В Main метода:

var lecture = new Lecture("C# events", "Ivaylo Kenov");
var studentPesho = new Student() { Name = "Pesho" };
var studentGosho = new Student() { Name = "Gosho" };

lecture.TrainerNameChanged += studentPesho.OnTrainerNameChanged;
lecture.TrainerNameChanged += studentGosho.OnTrainerNameChanged;
lecture.Trainer = "Chuck Norris";
// Pesho does not like the new trainer Chuck Norris!
// Gosho does not like the new trainer Chuck Norris!
lecture.TrainerNameChanged -= studentPesho.OnTrainerNameChanged;
lecture.Trainer = "Bill Gates";
// Gosho does not like the new trainer Bill Gates!

 

Имайте предвид, че примерът е доста прост и целта му е да демонстрира прехода от Observer Pattern към C# events, а не как трябва да се пише код, така че подхождайте критично към него :)))

4
C# OOP Advanced 23/07/2016 14:05:22
silvi81 avatar silvi81 77 Точки

Много добре си описал всичко. Тъкмо сутринта гледах едно видео за Design Patterns :

https://softuni.bg/trainings/resources/video/7654/video-behavioral-patterns-3-february-2016-atanas-rusenov-high-quality-code-december-2015

След 59-та минута обясняват за Observer Pattern, aко някой иска още инфо по темата. Всъщност,  това което разбрах от видеото е, че Event-ите  в С# имплементират този патерн и са синтактична захар на езика, за да не се налага сами да го пишем. Преглеждайки мненията в stackoverflow, повечето са за ползване на event-и.

2
23/07/2016 15:46:27
Можем ли да използваме бисквитки?
Ние използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Можете да се съгласите с всички или част от тях.
Назад
Функционални
Използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Използваме „сесийни“ бисквитки, за да Ви идентифицираме временно. Те се пазят само по време на активната употреба на услугите ни. След излизане от приложението, затваряне на браузъра или мобилното устройство, данните се трият. Използваме бисквитки, за да предоставим опцията „Запомни Ме“, която Ви позволява да използвате нашите услуги без да предоставяте потребителско име и парола. Допълнително е възможно да използваме бисквитки за да съхраняваме различни малки настройки, като избор на езика, позиции на менюта и персонализирано съдържание. Използваме бисквитки и за измерване на маркетинговите ни усилия.
Рекламни
Използваме бисквитки, за да измерваме маркетинг ефективността ни, броене на посещения, както и за проследяването дали дадено електронно писмо е било отворено.