C # Реализация события - PullRequest
0 голосов
/ 17 мая 2018

Я изучаю реализацию событий C # на классах.

У меня есть пример дела:

Существует классы "Спорт" и "Город", унаследованные от класса "Автомобиль". Класс Car имеет базовый вызов метода OnBuy, унаследованный классами Sport и City. Внутри метода OnBuy обработчик событий был назначен на Event Buy.

Существует также Сервис или Класс, называемый LicenseService, который генерирует номер лицензии при каждой покупке.

В этом случае я реализовал программирование на основе событий. Вот мой пример мерзавца:

https://github.com/adityosnrost/CSharpLearningEvent

Вопросы:

  1. Правильно ли использовать Event на C #?

  2. если это правильно. Могу ли я переопределить метод OnBuy для каждого ребенка? и что я могу сделать, если доступно переопределение?

  3. Что я могу сделать, чтобы сделать его лучше из этого образца?

Спасибо

class Program
{
    static void Main(string[] args)
    {
        Car car = new Car();
        Sport sport = new Sport();
        City city = new City();

        //car.Name();
        //sport.Name();
        //city.Name();

        //Console.ReadLine();

        LicenseService ls = new LicenseService();

        city.Buy += ls.GenerateLicense;

        city.OnBuy();

        Console.ReadLine();
    }
}

internal class Car
{
    internal virtual void Name()
    {
        Console.WriteLine("Car");
    }

    internal event EventHandler Buy;

    internal virtual void OnBuy()
    {
        EventHandler handler = Buy;
        if (null != handler)
        {
            handler(this, EventArgs.Empty);
        }
    }
}

internal class Sport: Car
{
    internal override void Name()
    {
        Console.WriteLine("Sport");
    }
}

internal class City: Car
{
    internal override void Name()
    {
        Console.WriteLine("City");
    }
}

internal class LicenseService
{
    internal void GenerateLicense(object sender, EventArgs args)
    {
        Random rnd = new Random();

        string carType = sender.GetType().ToString();

        string licenseNumber = "";

        for(int i = 0; i < 5; i++)
        {
            licenseNumber += rnd.Next(0, 9).ToString();
        }

        Console.WriteLine("{1} Car has been bought, this is the license number: {0}", licenseNumber, carType);
    } 
}

Ответы [ 6 ]

0 голосов
/ 17 мая 2018

Следующим будет мой предпочтительный дизайн:

class Program
{
    static void Main(string[] args)
    {
        Car car = new Sport();
        car.BuyEvent += License.GenerateLicense;        
        car.OnBuy();
        car = new City();
        car.BuyEvent += License.GenerateLicense;
        car.OnBuy();
    }
}

internal abstract class Car
{
    internal abstract void Name();

    protected abstract event EventHandler Buy;

    public abstract event EventHandler BuyEvent;

    public abstract void OnBuy();   
}

internal class Sport : Car
{
    internal override void Name()
    {
        Console.WriteLine("Sport Car");
    }

    protected override event EventHandler Buy;

    public override event EventHandler BuyEvent
    {
        add
        {
            lock (this)
            {
                Buy += value;
            }
        }

        remove
        {
            lock (this)
            {
                Buy -= value;
            }
        }
    }

    public override void OnBuy()
    {
        if (Buy != null)
        {
            Buy(this, EventArgs.Empty);
        }
    }

}

internal class City : Car
{
    internal override void Name()
    {
        Console.WriteLine("City Car");
    }

    protected override event EventHandler Buy;

    public override event EventHandler BuyEvent
    {
        add
        {
            lock (this)
            {
                Buy += value;
            }
        }

        remove
        {
            lock (this)
            {
                Buy -= value;
            }
        }
    }

    public override void OnBuy()
    {
        if (Buy != null)
        {
            Buy(this, EventArgs.Empty);
        }
    }
}

internal static class License
{

    public static void GenerateLicense(object sender, EventArgs args)
    {
        Random rnd = new Random();

        string carType = sender.GetType().Name;

        string licenseNumber = "";

        for (int i = 0; i < 5; i++)
        {
            licenseNumber += rnd.Next(0, 9).ToString();
        }

        Console.WriteLine($"{carType} Car has been bought, this is the license number: {licenseNumber}");
    }

}

Важные моменты:

  1. Сделайте базовый класс Car абстрактным, он должен быть толькопроизводный и используемый как City / Sport Car
  2. Добавьте event add / remove accessor в каждый дочерний класс для пользовательской обработки события.Сделайте поток доступа безопасным для общего клиентского доступа
  3. Сделайте License - GenerateLicense либо статическим, так как в классе нет состояний / данных, либо интегрируйте их тоже с реализацией City / Sport class, используя другой базовый абстрактный метод
  4. Абстрактный класс Car должен вводиться с Sport \ City объектами во время выполнения и использоваться для подписки на события через базовый класс Car object
0 голосов
/ 17 мая 2018

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

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

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

Class Shop{ 

    public Car BuyCar( carWithoutLicense ){
        //Purchase logic
        LicenseService.AssignLicense( carWithoutLicense ).
        return carWithoutLicense.
    }
}
//A person would call this method, no the car

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

class Car {
   Bool CheckEngingLightIsOn = false;
   public void Ride( ... ){
    if( enginge.faultDetected == true ){
       TurnOnCheckEngineAlert( );
    }
   }
   public void TurnOnCheckEngineAlert( ){
      CheckEngingLightIsOn = true;
      if( CheckEngineLightSwitch != null ){
         CheckEngineLightSwitch.Invoke( ... )
      }
   }
}

class Driver {
   public Driver( Car car ){
      this.car = car;
      if( driverState != Drunk ){
       car.CheckEngineLightSwitch = TakeAction;
      }
   }
   public Drive( ){
      car.Ride( );
   }
   public void TakeAction( Car car, EventArgs e ){
      //get out, open the hood, check the engine...
      if( car.CheckEngingLightIsOn == true ){ "Light turned on
        //Check Engine
      }else{
        //continue driving
      }
   }
}

Не углубляясь в абстракцию, обратите внимание на цепочку событий:

  • Driver ведет машину и не беспокоится одругие вещи (например, контрольная лампочка двигателя) до тех пор, пока они не произойдут.
  • Если Car обнаруживает неисправность, включается контрольная лампочка двигателя и на этом событии (подписчики) появляются обработчики событий, которые автомобиль вызываетсобытие.
  • Событие происходит, но водитель должен быть подписан на него, чтобы заметить изменение.
  • ТОЛЬКО если водитель подписан на это событие (в данном случае, если он не пьян), он будетпринять меры на основе этого события.

Этот пример принципиально отличается от вашего примера, поскольку:

  • Во время вождения автомобиля водителю не нужно платитьВнимание, постоянно проверяйте свет двигателя (хотя он может это проверить).
  • Это стандартный процесс проверки состояния двигателя автомобиля и представления его на лампочке двигателя.
  • Водитель и Автомобиль влияют на дальнейшие действия друг друга, и эта логика неэффективна, если выражается линейно.

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


Несколько общих советов о вашем прецеденте

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

Первое, на что вы должны обратить внимание, - это какая логика имеет отношение к каждому объекту.Представляет ли событие / метод в вашем объекте что-то, что ваш объект делает (то есть функциональность, которую объект выполняет сам) или что-то, что влияет на ваш объект , но сам объект ничего не делает впроцесс?Например: автомобиль «едет» самостоятельно (даже если водитель запускает этот процесс и все его параметры, такие как скорость или направление);присвоение лицензии автомобилю происходит полностью вне структуры автомобиля, и только для автомобиля выполняется изменение атрибута (лицензия).

Это различие важно, потому что только логика , выполняемая вашим объектом , имеет отношение к этому объекту, и, соответственно, любая логика, которая выполняется другим объектом и влияет только на ваш объект , является не имеет значения принадлежит где-то еще. Так что Buy определенно не принадлежит автомобилю, а Ride (процесс перемещения) принадлежит автомобилю.

Во-вторых, ваши имена будут иметь большое значение, чтобы помочь вам понять эту тему. Методы представляют действия и должны называться так (Shop.BuyCar, Car.Ride, Driver.Drive), события представляют триггеры реакции (Car.CheckEngineLightSwitch), а обработчики событий представляют реакции на действие (реакция по-прежнему является действием, поэтому никаких специальных имен не требуется, но вы можете указать имя, чтобы провести различие между действием и реакцией).

0 голосов
/ 17 мая 2018

1) Это правильный способ использовать Event на C #?

Не полностью. OnBuy должен быть защищенным виртуальным методом. Это также исключает вызов этого метода из метода Main ().

Чаще всего вызывается someCar.Buy (), а затем Buy (), вызывая OnBuy ().

2) если это правильно. Могу ли я переопределить метод OnBuy для каждого ребенка? и что я могу сделать, если доступно переопределение?

Да, вы можете переопределить его. Посмотрите на это как на более эффективный способ подписки на себя (который будет альтернативой).

Вы можете делать все, что угодно, когда куплен автомобиль определенного типа. Но всегда звоните base.OnBuy()

3) Что я могу сделать, чтобы сделать его лучше из этого образца?

CreateLicense не выглядит хорошим кандидатом на событие, это скорее бизнес-правило, вызываемое CarDealer.

Следуя современным правилам проектирования, автомобиль будет довольно пассивным объектом (модель анемичной области).

События обычно используются, чтобы сказать другим компонентам: «Я изменился, делай свое дело», а не выполнять важные действия над собой.

0 голосов
/ 17 мая 2018

Если бы я делал эту программу, я бы сделал следующие изменения:

  • Подпись события Buy

сначала у него было два параметра Object и EventArgs где вам нужно только Car в методе-обработчике (а также Random, обсуждаемый ниже почему).

  • Я бы передал LicenseService в конструктор Child и зарегистрировал (подписался) Event в конструкторетолько.это было бы более чистым способом.
  • Сделал бы строковый член CarName в родительском классе, чтобы каждый ребенок мог использовать его в любом месте.
  • Еще одна вещь, которую я еще не сделалв этом коде я бы никогда не назвал событие Buy, вместо этого я бы назвал его Bought.
  • (это относится только к этому сценарию). В вашем коде внутри GenerateLicense() вы создаете новоеобъект Random каждый раз.Таким образом, если ваши два вызова для этого метода в течение короткого времени, он будет генерировать одно и то же случайное число для обоих вызовов.Зачем?посмотрите этот Вопрос - или вы можете попробовать приведенный ниже пример кода самостоятельно.Так что я бы передал уже созданный объект Random в GenerateLicense().Так что Random будет общим для каждого вызова этого метода.

Пример кода для объяснения поведения случайного числа

        //as object of Random numbers are different,
        //they will generate same numbers
        Random r1 = new Random();
        for(int i = 0; i < 5; i++)
            Console.WriteLine(r1.Next(0, 9));
        Random r2 = new Random();
        for(int i = 0; i < 5; i++)
            Console.WriteLine(r2.Next(0, 9));

Обновление

  • Как предложил Мринал Камбой (в комментариях ниже), мы не должны подвергать Events воздействию внешнего кода.Добавление своего комментария также в этом ответе

Два пункта, EventHandler Buy, к которым нельзя напрямую получить доступ вне его, должны быть закрытыми, так как в противном случае любой может установить для него значение null, и вся подписка будетушел.Для него требуется средство доступа к событиям, чтобы к событию можно было обращаться с помощью операторов += и -=, и там вы сами делали его потокобезопасным для нескольких подписчиков, иначе будет состояние гонки, посмотрите простой пример

следующий код,

структура вашего класса:

internal delegate void EventHandler(Car car, Random rnd);
internal class Car
{
    internal string CarName;
    internal virtual void SetName()
    {
        this.CarName = "car";
    }

    //Edit : As Mrinal Kamboj suggested in comments below
    //here keeping event Buy as private will prevent it to be used from external code
    private event EventHandler Buy;
    //while having EventAccessros internal (or public) will expose the way to subscribe/unsubscribe it
    internal event EventHandler BuyAccessor
    {
        add 
        {
            lock (this)
            {
                Buy += value;
            }
        }
        remove
        {
            lock (this)
            {
                Buy -= value;
            }
        }
    }

    internal virtual void OnBuy(Random rnd)
    {
        if (Buy != null)
            Buy(this, rnd);
    }
}

internal class Sport: Car
{
    LicenseService m_ls;
    internal Sport(LicenseService ls)
    {
        this.m_ls = ls;
        this.BuyAccessor += ls.GenerateLicense;
        SetName();
    }

    internal override void SetName()
    {
        this.CarName = "Sport";
    }
}

internal class City: Car
{
    LicenseService m_ls;
    internal City(LicenseService ls)
    {
        this.m_ls = ls;
        this.BuyAccessor += ls.GenerateLicense;
        SetName();
    }
    internal override void SetName()
    {
        this.CarName = "City";
    }
}

LicenseService class

internal class LicenseService
{
    internal void GenerateLicense(Car sender, Random rnd)
    {
        string carName = sender.CarName;
        string licenseNumber = "";
        for(int i = 0; i < 5; i++)
        {
            licenseNumber += rnd.Next(0, 9).ToString();
        }
        Console.WriteLine("{1} Car has been bought, this is the license number: {0}", licenseNumber, carName);
    } 
}

и вызов потока

static void Main(string[] args)
{
    Random rnd = new Random();
    LicenseService ls = new LicenseService();
    Sport sport = new Sport(ls);
    City city = new City(ls);

    city.OnBuy(rnd);
    sport.OnBuy(rnd);

    Console.ReadLine();
}
0 голосов
/ 17 мая 2018

Я рекомендую вам добавить сервис лицензий в автомобиль и вызвать функцию генерации лицензии при вызове покупки,

using System;

namespace ConsoleApp1.Test
{

    class Program
    {
        static void Maintest(string[] args)
        {
            ILicenseService licenseService = new LicenseService();

            Sport sport = new Sport(licenseService);
            City city = new City(licenseService);

            //car.Name();
            //sport.Name();
            //city.Name();

            //Console.ReadLine();            

            city.OnBuy();

            Console.ReadLine();
        }
    }

    internal abstract class Car
    {
        protected readonly ILicenseService licenseService;

        public Car(ILicenseService _licenseService)
        {
            licenseService = _licenseService;
        }

        internal virtual void Name()
        {
            Console.WriteLine("Car");
        }

        internal event EventHandler Buy;

        internal virtual void OnBuy()
        {
            // TODO
        }
    }

    internal class Sport : Car
    {
        public Sport(ILicenseService _licenseService) : base(_licenseService)
        {

        }

        internal override void Name()
        {
            Console.WriteLine("Sport");
        }

        internal override void OnBuy()
        {
            licenseService.GenerateLicense(new object());
        }
    }

    internal class City : Car
    {
        public City(ILicenseService _licenseService) : base(_licenseService)
        {

        }

        internal override void Name()
        {
            Console.WriteLine("City");
        }

        internal override void OnBuy()
        {
            licenseService.GenerateLicense(new object());
        }
    }

    internal interface ILicenseService
    {
        void GenerateLicense(object param);
    }

    internal class LicenseService : ILicenseService
    {
        public void GenerateLicense(object param)
        {
            // do your stuff
        }
    }

}
0 голосов
/ 17 мая 2018

Нет.

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

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

В противном случае вы просто вызываете функцию и можете не беспокоиться о событии.

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

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