это хороший способ использования событий?

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

Это хороший способ использования события? Можно ли это улучшить?

class Program
{
    static void Main(string[] args)
    {
        Player player = new Player();
        player.OnLevelUp += Player_OnLevelUp;
        Console.WriteLine(player.Level);

        for (int i = 0; i < 10; i++)
        {
            player.SetXp(10);
        }

        Console.ReadLine();
    }

    private static void Player_OnLevelUp(object sender, EventArgs e)
    {
        var player = (Player)sender;
        player.Level += 1;

        Console.WriteLine("You leveled up!");
        Console.WriteLine(player.Level);
    }
}

public class Player
{
    public event EventHandler OnLevelUp;
    public string Name { get; set; }
    public int Level { get; set; }
    public int XP { get; set; }
    
    private int _nextLevelXp = 50;

    private void LevelUp()
    {
        OnLevelUp?.Invoke(this, EventArgs.Empty);
    }

    public void SetXp(int xp)
    {
        XP += xp;
        if (XP >= _nextLevelXp)
        {
            LevelUp();
            _nextLevelXp *= 2;
        }
    }
}

2 ответа
2

То, что вы сделали, концептуально неправильно

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

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

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

То, что вы сделали, перекладывает ответственность на player класс (фактическое повышение уровня — увеличение уровня) в другой класс (Program). В Program несет другую ответственность, чем поддержание внутреннего состояния игрока.

В player это класс, у которого есть собственное состояние и методы, которые его изменяют (весь SetXP а также LevelUp методы). В Program просто использует эти методы (а иногда и читает это состояние), чтобы player что-то делать и использует событие для управления своей собственной ответственностью, то есть писать о повышении уровня, когда это происходит.

Код просто нужно немного изменить и сделать что-то вроде:

    private void LevelUp()
    {
        Level++; //this leaves the responsibility of leveling up to the Player
        OnLevelUp?.Invoke(this, EventArgs.Empty);
    }

а затем оставьте OnLevelUp в виде:

    private static void Player_OnLevelUp(object sender, EventArgs e)
    {
        //this allows the Program to only be responsible for it's own state
        var player = (Player)sender;
        Console.WriteLine("You leveled up!");
        Console.WriteLine(player.Level);
    }

Чтобы быть достаточно хорошо сформированным.

Обращайтесь с LevelUp в LevelUp метод, а затем позвольте Program знаю через OnLevelUp событие и пусть Program выполнить свои обязанности, написав об этом в консоль.


Принципы SOLID

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

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

Одна из тех парадигм, которые люди, работающие с объектно-ориентированными языками, начнут (или должны) начать изучать, как только это станет разумным, — это принципы SOLID.

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

Один из ТВЕРДЫЙ принципы Принцип единоличной ответственности — класс / модуль / функция должны иметь одну ответственность.

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

По этому принципу — мы называем это СЕРП для краткости — класс Player несет ответственность за представление игрока и его состояние а также В Program отвечает за то, чтобы пользователь мог играть в игру (включая использование методов, предоставляемых player класс и писать о том, что происходит с Console окно).

То, что вы сделали, нарушило этот принцип, позволив Program возиться с player напрямую.

Практически это достигается Разделение проблем. Мы разделяем логику и состояние на Player класс, который за это отвечает, и логику игры другому классу, который отвечает за свои собственные вещи и использует разделенную логику через интерфейс (методы, свойства и поля), которые он предоставляет.

Здесь, в обучающей программе, это не такая уж большая проблема. Но как только вы начнете работать с большими объектами, которые имеют множество свойств и полей и должны поддерживать сложное состояние, это может стать проблематичным.

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

Представьте, что вы пишете класс, который работает как HTTP-клиент. Он сложен и имеет множество событий, свойств и полей. Но вы не тот, кто будет его использовать. В этой большой команде, в которой вы работаете, будет какой-то другой программист (или просто кто-то, кто использует вашу библиотеку), который будет использовать ее для чего-то — вероятно, отправит HTTP-запрос и получит ответ …

Действительно ли это хорошая идея — позволить ему волей-неволей изменить внутреннее состояние вашего клиента? Или сделать другого программиста ответственным за изменение значений, за которые отвечает ваш класс (собственный внутренний статус вашего клиента)?

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

Также — на событие может быть подписано несколько потребителей.

Если я начну строить на вашем примере, может быть объект, который рисует игрока на экране (Graphics), а когда OnLevelUp запускается, запускает анимацию повышения уровня.

Тогда могут быть объекты, представляющие Fight игрок находится в, которые подписаны на OnLevelUp мероприятие. Когда он повышается, гипотетический Fight класс, ответственный за продолжающийся бой, может проверить, не слишком ли слабы его противники, и может заставить их попытаться убежать от игрока …

В вашем случае это Program подписавшись на Playerс OnLevelUp событие и напишите об изменении Console окно. Это ответственность Program писать все на экране. Это ответственность player объект для поддержания своего состояния и представления игрока.

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


ПОЧЕМУ ТАК МНОГО КОДА

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

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

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


ПОТЕНЦИАЛЬНЫЕ ПРОБЛЕМЫ

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

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

Другая проблема в том, что делегат может быть очищен.

Некоторые части кода могут делать что-то вроде:

//note the usage of = instead of +=
player.OnLevelUp = Player_DifferentOnLevelUp;

Назначение Player_DifferentOnLevelUp удалит все ранее подписанные методы. Внезапно повышение уровня не приведет к повышению уровня игрока, потому что логика повышения уровня в оригинале не контролируется Player и b / Не вызывается, поскольку он находится в методе, который больше не подписан на это событие …

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

    События должны сообщать, когда что-то изменилось.

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

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

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

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

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

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

      — мишань

    • 1

      Хорошая точка зрения. События могут быть не только после. После — это то, к чему я больше привык. Я обновлю ответ

      — CharlesNRice

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *