Расширяя ответ от @kara, следующий код реализует независимые Stomach
и Brain
объекты и использует Being
для их соединения.
Что Being
знает о Stomach
:
Что Being
знает о Brain
- есть
OnRaiseIsHungryEvent
(т. Е. "Голодный" сигнал - кого волнует, откуда он) - имеет
IsHungryEvent
Имейте в виду, что в реальной реализации могут быть другие объекты, прослушивающие эти события.Например, может быть, у вас есть система эмоций, которая переключается на «голодный», и искусственный интеллект, который переключается в режим поиска пищи.Ни одна из систем не должна знать о другой, но обе могут реагировать на сигналы от Brain
.В этой тривиальной реализации Being
реагирует на сигнал Stomach
и уведомляет и реагирует на Brain
.
Важным выводом из этого является не конкретный метод вызова и реагирования на события.(в этом случае механизм .Net по умолчанию), но тот факт, что ни один объект не знает ничего о внутренностях другого (см. различные реализации HumanStomach
и ZombieStomach
), и вместо этого соединение подключено на более подходящем уровне(Being
в этом случае).Также обратите внимание на зависимость от интерфейсов, что позволяет нам делать такие вещи, как создание гибридных существ (т.е. соединение ZombieBrain
с HumanStomach
).
Код был написан / протестирован с .Net Core CLI в качестве консолиприложение, но оно должно быть совместимо с большинством любых версий .Net> 3.5.
using System;
using System.Linq;
using System.Threading;
namespace so_example
{
public class Program
{
static void Main(string[] args)
{
var person1 = new Being("Human 1", new HumanBrain(), new HumanStomach());
var zombie1 = new Being("Zombie 1", new ZombieBrain(), new ZombieStomach());
var hybrid1 = new Being("Hybrid 1", new ZombieBrain(), new HumanStomach());
var hybrid2 = new Being("Hybrid 2", new HumanBrain(), new ZombieStomach());
Console.WriteLine("Hit any key to exit");
Console.ReadKey();
}
}
public class HungryEventArgs : EventArgs
{
public string Message { get; set; }
}
public interface IStomach
{
event EventHandler<HungryEventArgs> NeedsFoodEvent;
}
public class Stomach : IStomach
{
public event EventHandler<HungryEventArgs> NeedsFoodEvent;
protected virtual void OnRaiseNeedsFoodEvent(HungryEventArgs e)
{
EventHandler<HungryEventArgs> handler = NeedsFoodEvent;
if (handler != null)
{
handler(this, e);
}
}
}
public class HumanStomach : Stomach
{
private Timer _hungerTimer;
public HumanStomach()
{
_hungerTimer = new Timer(o =>
{
// only trigger if breakfast, lunch or dinner (24h notation)
if (new [] { 8, 13, 19 }.Any(t => t == DateTime.Now.Hour))
{
OnRaiseNeedsFoodEvent(new HungryEventArgs { Message = "I'm empty!" });
}
else
{
Console.WriteLine("It's not mealtime");
}
}, null, 1000, 1000);
}
}
public class ZombieStomach : Stomach
{
private Timer _hungerTimer;
public ZombieStomach()
{
_hungerTimer = new Timer(o =>
{
OnRaiseNeedsFoodEvent(new HungryEventArgs { Message = "Need brains in stomach!" });
}, null, 1000, 1000);
}
}
public interface IBrain
{
event EventHandler<HungryEventArgs> IsHungryEvent;
void OnRaiseIsHungryEvent();
}
public class Brain : IBrain
{
public event EventHandler<HungryEventArgs> IsHungryEvent;
protected string _hungryMessage;
public void OnRaiseIsHungryEvent()
{
EventHandler<HungryEventArgs> handler = IsHungryEvent;
if (handler != null)
{
var e = new HungryEventArgs
{
Message = _hungryMessage
};
handler(this, e);
}
}
}
public class HumanBrain : Brain
{
public HumanBrain()
{
_hungryMessage = "Need food!";
}
}
public class ZombieBrain : Brain
{
public ZombieBrain()
{
_hungryMessage = "Braaaaaains!";
}
}
public class Being
{
protected readonly IBrain _brain;
protected readonly IStomach _stomach;
private readonly string _name;
public Being(string name, IBrain brain, IStomach stomach)
{
_stomach = stomach;
_brain = brain;
_name = name;
_stomach.NeedsFoodEvent += (s, e) =>
{
Console.WriteLine($"{_name}: {e.Message}");
_brain.OnRaiseIsHungryEvent();
};
_brain.IsHungryEvent += (s, e) =>
{
Console.WriteLine($"{_name}: {e.Message}");
};
}
}
}
Некоторые заметки
Чтобы обеспечить вывод, я подделал вещи в реализациях 2 IStomach
.HumanStomach
создает таймер обратного вызова в конструкторе, который срабатывает каждую 1 секунду и проверяет, является ли текущий час временем приема пищи.Если это так, то поднимается NeedsFoodEvent
.ZombieStomach
также использует обратный вызов каждую 1 секунду, но он просто запускает NeedsFoodEvent
каждый раз.В реальной реализации Unity вы, скорее всего, инициируете четность на основе какого-либо события из Unity - действия, предпринятого игроком, через некоторое заданное время и т. Д.