Привет всем!
В объектно-ориентированном программировании существует одна очень полезная "вещь", которая называется "событие". И так, что же это такое, "событие", и зачем оно нужно?
"Событием" является возможность установить в классе некоторые точки, которые будут срабатывать при определённых предусмотренных условиях, и на реакцию которых, в дальнейшем, можно добавить извне запуск каких-либо установленных функций. Почему бы обработку этих точек не оставить в самом классе? Да потому, что добавление событий, во первых, может помочь разделить логику, что в дальнейшем очень облегчит жизнь при обслуживании программного кода. А во вторых, используя экземпляры одного и того же класса несколько раз в различных точках программного кода, можно добавить им разную реакцию на его события.
Самым простым способ реализовать обработку "события" является использование callback-функций, так называемых функций обратного вызова. Такой способ обработки "события" вполне работает и вне классов, поэтому, наверное, вряд ли его можно отнести к концепции ООП. Но, тем не менее, это прекрасно работает, и это вполне можно использовать. Для этого надо всего лишь подготовленную callback-функцию передать в обычную, запускаемую функцию в качестве параметра, а за тем вызвать эту переданную callback-функцию внутри запускаемой функции в установленной точке программного кода. Вот примеры реализации callback-функции в нескольких различных языках:
- Код на javaScript
- Код на Python 3
- Код на C#
- Код на Java
/**
* Функция генерации случайного значения от -5 до 5 с событиями
*/
function randomInt(callbackPositive, callbackNegative, callbackZero) {
let rndInt = randomInteger(-5, 5);
if (rndInt < 0)
callbackNegative(rndInt)
else if (rndInt > 0)
callbackPositive(rndInt)
else
callbackZero();
}
/**
* Callback-функция на событие выпадания случайного положительного значения
*/
function funcCallbackPositive(rndInt) {
print("Получено положительное число " + rndInt + ".");
}
/**
* Callback-функция на событие выпадания отрицательного значения
*/
function funcCallbackNegative(rndInt) {
print("Получено отрицательное число " + rndInt + ".");
}
/**
* Callback-функция на событие выпадания нуля
*/
function funcCallbackZero() {
print("Получен ноль.");
}
function randomInteger(min, max) {
// случайное число от min до (max+1)
let rand = min + Math.random() * (max + 1 - min);
return Math.floor(rand);
}
function main() {
for (let i=0; i<20; i++) {
randomInt(funcCallbackPositive, funcCallbackNegative, funcCallbackZero);
}
}
main();
import random
def random_int(callback_positive, callback_negative, callback_zero):
""" Функция генерации случайного значения от -5 до 5 с событиями """
rnd_int = random.randint(-5, 5)
if rnd_int < 0:
callback_negative(rnd_int)
elif rnd_int > 0:
callback_positive(rnd_int)
else:
callback_zero()
def func_callback_positive(rnd_int):
""" Callback-функция на событие выпадания случайного положительного значения """
print("Получено положительное число {}.".format(rnd_int))
def func_callback_negative(rnd_int):
""" Callback-функция на событие выпадания случайного отрицательного значения """
print("Получено отрицательное число {}.".format(rnd_int))
def func_callback_zero():
""" Callback-функция на событие выпадания нуля """
print("Получен ноль.")
def main():
for i in range(0, 10):
random_int(func_callback_positive, func_callback_negative, func_callback_zero)
if __name__ == '__main__':
main()
using System;
class MainClass {
delegate void CallBackFunction(int i);
static Random rnd;
static void Main() {
rnd = new Random();
for (int i = 0; i < 10; i++)
RandomInt(FuncCallbackPositive, FuncCallbackNegative, FuncCallbackZero);
}
/**
* Функция генерации случайного значения от -5 до 5 с событиями
*/
static void RandomInt(CallBackFunction callbackPositive, CallBackFunction callbackNegative, CallBackFunction callbackZero) {
int rndInt = rnd.Next(-5, 6);
if (rndInt < 0)
callbackNegative(rndInt);
else if (rndInt > 0)
callbackPositive(rndInt);
else
callbackZero(rndInt);
}
/**
* Callback-функция на событие выпадания случайного положительного значения
*/
static void FuncCallbackPositive(int rndInt) {
Console.WriteLine("Получено положительное число {0}.", rndInt);
}
/**
* Callback-функция на событие выпадания случайного отрицательного значения
*/
static void FuncCallbackNegative(int rndInt) {
Console.WriteLine("Получено отрицательное число {0}.", rndInt);
}
/**
* Callback-функция на событие выпадания нуля
*/
static void FuncCallbackZero(int rndInt) {
Console.WriteLine("Получен ноль.");
}
}
import java.util.Random;
public class Main
{
public interface ICallBackFunctions {
public void CallbackPositive(int rndInt);
public void CallbackNegative(int rndInt);
public void CallbackZero();
}
public static void main(String[] args) {
Main main = new Main();
main.Start();
}
public void Start() {
for (int i=0; i<10; i++)
RandomInt(new CallbackFunctions());
}
/**
* Функция генерации случайного значения от -5 до 5 с событиями
*/
void RandomInt(ICallBackFunctions callbackFunctions) {
Random rand = new Random();
int rndInt = rand.nextInt(11) - 5;
if (rndInt < 0)
callbackFunctions.CallbackNegative(rndInt);
else if (rndInt > 0)
callbackFunctions.CallbackPositive(rndInt);
else
callbackFunctions.CallbackZero();
}
/**
* Класс реализующий интерфейс callback-функций
*/
public class CallbackFunctions implements ICallBackFunctions {
/**
* Callback-функция на событие выпадания случайного положительного значения
*/
@Override
public void CallbackPositive(int rndInt) {
System.out.printf("Получено положительное число %d.\n", rndInt);
}
/**
* Callback-функция на событие выпадания случайного отрицательного значения
*/
@Override
public void CallbackNegative(int rndInt) {
System.out.printf("Получено отрицательное число %d.\n", rndInt);
}
/**
* Callback-функция на событие выпадания нуля
*/
@Override
public void CallbackZero() {
System.out.println("Получен ноль.");
}
}
}
Сам принцип callback-функций основан на способности поведения функции аналогично переменной. В некоторых скриптовых языках, вроде javaScript или Python, функции вполне могут вести себя как обычные переменные, то есть их можно присвоить переменной, вернуть как результат из другой функции или передать в качестве параметра в какую-либо функцию, а потом запустить её просто добавив скобки. Но в других языках программирования, всё немного сложнее.
Например, в C# функцию нельзя просто так передать в качестве параметра, и это не потому, что это запрещено, а потому, что в строго типизированных языках программирования, которым в том числе является и C#, для передачи параметра требуется указать его тип. А какой тип указать для функции? И вот именно для этих целей в C# был добавлен делегат (delegate). Делегат как раз то и позволяет, что-то вроде, создать тип функции с определенной сигнатурой, то есть набором параметров и типом, который она должна вернуть. После этого можно создавать функции, так сказать, с определенным типом и потом уже использовать их чтобы, например, сохранить функцию как переменную, или просто указать тип функции для передачи её в качестве параметра в другую функцию.
А вот в Java ничего подобного делегатам нет и поэтому в ней возможность использовать функцию аналогично переменной не предусмотрено в принципе никаким образом, поэтому передать функцию в качестве параметра в Java нельзя. Но, тем не менее, callback-функции существуют и в Java, просто для их реализации используется небольшая хитрость с использованием интерфейсов. Это всё выглядит следующим образом. Создаётся интерфейс, в котором описывается одна или несколько функций, которые должны исполнять роль callback-функций и потом в качестве параметра в функцию передаётся целый класс, реализующий этот интерфейс, ведь он то, имеет свой тип данных. А потом в определённых точках из этого класса запускаются нужные функции, реализованные в соответствии с интерфейсом.
Вообще описанный выше метод реализации callback-функций в Java довольно универсален, и вполне может быть использован и в других языках программирования, в том числе и в C#. И для его реализации теоретически можно использовать не только интерфейс, но и абстрактный класс или даже просто обычный класс, или вообще обойтись способностью объектов сохранять функции в виде их свойства, как это, например, возможно в javaScript или Python, и передавать в качестве параметра этот объект.
Но почему же тогда в Java всё-таки используются именно интерфейс, а не, скажем, абстрактный класс? Да потому, что интерфейс, в данном случае, будет гораздо удобнее и его использование открывает более широкие возможности. Например, он может позволить, в том числе, создать callback-функцию и в том же самом классе, который и инициирует событие, для этого надо всего лишь отметить его интерфейсом, описывающем callback-функцию (или функции), реализовать его, и теперь можно легко передать в качестве параметра самого себя, иногда это бывает очень удобно и полезно.
Вот примеры callback-функций реализованных по принципу похожему на Java на нескольких других языках программирования:
- Код на javaScript
- Код на Python 3
- Код на C#
/**
* Функция генерации случайного значения от -5 до 5 с событиями
*/
function randomInt(callback) {
let rndInt = randomInteger(-5, 5);
if (rndInt < 0)
callback.callbackNegative(rndInt)
else if (rndInt > 0)
callback.callbackPositive(rndInt)
else
callback.callbackZero();
}
/**
* Объект содержащий callback-функции
*/
let objectCallback = {
/**
* Callback-функция на событие выпадания случайного положительного значения
*/
callbackPositive: function(rndInt) {
console.log("Получено положительное число " + rndInt + ".");
},
/**
* Callback-функция на событие выпадания отрицательного значения
*/
callbackNegative: function(rndInt) {
console.log("Получено отрицательное число " + rndInt + ".");
},
/**
* Callback-функция на событие выпадания нуля
*/
callbackZero: function() {
console.log("Получен ноль.");
}
};
function randomInteger(min, max) {
// случайное число от min до (max+1)
let rand = min + Math.random() * (max + 1 - min);
return Math.floor(rand);
}
function main() {
for (let i=0; i<20; i++) {
randomInt(objectCallback);
}
}
main();
import random
def random_int(callback_functions):
""" Функция генерации случайного значения от -5 до 5 с событиями """
rnd_int = random.randint(-5, 5)
if rnd_int < 0:
callback_functions.callback_negative(rnd_int)
elif rnd_int > 0:
callback_functions.callback_positive(rnd_int)
else:
callback_functions.callback_zero()
class callback_functions:
""" Класс содержащий callback-функции """
def callback_positive(rnd_int):
""" Callback-функция на событие выпадания случайного положительного значения """
print("Получено положительное число {}.".format(rnd_int))
def callback_negative(rnd_int):
""" Callback-функция на событие выпадания случайного отрицательного значения """
print("Получено отрицательное число {}.".format(rnd_int))
def callback_zero():
""" Callback-функция на событие выпадания нуля """
print("Получен ноль.")
def main():
for i in range(0, 10):
random_int(callback_functions)
if __name__ == '__main__':
main()
using System;
class MainClass {
public interface ICallBackFunctions {
void CallbackPositive(int rndInt);
void CallbackNegative(int rndInt);
void CallbackZero();
}
static Random rnd;
static void Main() {
rnd = new Random();
for (int i = 0; i < 10; i++)
RandomInt(new CallbackFunctions());
}
/**
* Функция генерации случайного значения от -5 до 5 с событиями
*/
static void RandomInt(ICallBackFunctions CallbackFunctions) {
int rndInt = rnd.Next(-5, 6);
if (rndInt < 0)
CallbackFunctions.CallbackNegative(rndInt);
else if (rndInt > 0)
CallbackFunctions.CallbackPositive(rndInt);
else
CallbackFunctions.CallbackZero();
}
/**
* Класс реализующий интерфейс callback-функций
*/
public class CallbackFunctions : ICallBackFunctions {
/**
* Callback-функция на событие выпадания случайного положительного значения
*/
public void CallbackPositive(int rndInt) {
Console.WriteLine("Получено положительное число {0}.", rndInt);
}
/**
* Callback-функция на событие выпадания случайного отрицательного значения
*/
public void CallbackNegative(int rndInt) {
Console.WriteLine("Получено отрицательное число {0}.", rndInt);
}
/**
* Callback-функция на событие выпадания нуля
*/
public void CallbackZero() {
Console.WriteLine("Получен ноль.");
}
}
}
Как можно увидеть, у этого метода есть даже некоторые преимущества, программный код стал несколько проще и короче, так что этот способ вполне пригоден для использования.
Итак, для реализации "событий" можно просто использовать callback-функции. Но, у них есть один существенный недостаток - для обработки одного события в этом случае можно назначить только одну функцию. В классическом же понимании "события" в ООП, на одно "событие" должны иметь возможность быть "зарегистрированы" несколько "подписчиков", и каждый из них должен иметь возможность добавить свою реакцию на это событие, то есть запустить какую-то свою функцию. Получается, что одно "событие" должно иметь возможность запускать несколько установленных функций.
Ну и ничего страшного, концепция "событий" от этого измениться не очень сильно. В основе этой концепции останутся всё те же callback-функции, но, для того чтобы на "событие" смогли зарегистрироваться несколько "подписчиков", нужно всего лишь создать управляемый массив этих callback-функций, и функцию каждого "подписчика" с помощью специального метода надо добавить в этот массив. А когда "событие" произойдёт, нужно просто пройтись по этому массиву и запустить каждую добавленную в него callback-функцию.
В общем виде шаблон реализации "событий" имеет примерно следующий вид:
класс Событие {
массивФункцийСобытия1
массивФункцийСобытия2
метод добавитьФункциюДляСобытия1(функцияОбратногоВызова) {
массивФункцийСобытия1.добавить(функцияОбратногоВызова);
}
метод удалитьФункциюИзСобытия1(функцияОбратногоВызова) {
массивФункцийСобытия1.удалить(функцияОбратногоВызова);
}
метод запуститьФункцииСобытия1() {
перебрать функцияОбратногоВызова из массивФункцийСобытия1 {
запустить функцияОбратногоВызова();
}
}
метод добавитьФункциюДляСобытия2(функцияОбратногоВызова) {
массивФункцийСобытия2.добавить(функцияОбратногоВызова);
}
метод удалитьФункциюИзСобытия2(функцияОбратногоВызова) {
массивФункцийСобытия2.удалить(функцияОбратногоВызова);
}
метод запуститьФункцииСобытия2() {
перебрать функцияОбратногоВызова из массивФункцийСобытия2 {
запустить функцияОбратногоВызова();
}
}
}
В зависимости от особенностей языка программирования этот шаблон может несколько и отличаться. Но основа, "событие", которое сохраняет в себе массив функций обратного вызова, будет, скорее всего, одинаковая. Вот примеры нескольких реализации "событий" на разных языках программирования:
- Код на javaScript
- Код на Python 3
- Код на C#
- Код на Java
class GameDice {
constructor() {
this.listeners = [];
}
/**
* Выбрасывает кости
*/
throw() {
// Выбрасываем две кости, и получаем два случайных значения в диапазоне
// от 1 до 6.
let diceVal1 = this.randomInteger(1, 6);
let diceVal2 = this.randomInteger(1, 6);
// Инициируем событие выбрасывание костей с параметром полученной суммы
// значения костей.
this.triggerEvent("throw", (diceVal1 + diceVal2))
// Если значение двух костей одинаковы,
if (diceVal1 == diceVal2)
// то дополнительно инициируем событие выпадания "дубля" с параметром
// значения дубля.
this.triggerEvent("double", diceVal1)
}
/**
* Добавляет обработчик события
*/
addEvent(evt, callback) {
// Если данне событие еще не было зарегистрировано,
if (!this.listeners.hasOwnProperty(evt)) {
// то зарегистрируем это событие.
this.listeners[evt] = [];
}
// Добавим для указанного события callback-функцию.
this.listeners[evt].push(callback);
return this;
}
/**
* Удаляет обработчик события
*/
removeEvent(evt, callback) {
// Если указанное собтыие было зарегистрировано,
if (this.listeners.hasOwnProperty(evt)) {
let i, length;
// то пройдёмся по всем callback-функциям данного события,
for (i = 0, length = this.listeners[evt].length; i < length; i += 1) {
// и если указнная для удаления функция есть, то удалим её.
if (this.listeners[evt][i] === callback) {
this.listeners[evt].splice(i, 1);
}
}
}
return this;
}
/**
* Запускает обработчики события
*/
triggerEvent(evt, args) {
// Если указанное событие зарегистрировано,
if (this.listeners.hasOwnProperty(evt)) {
let i, length;
// то пройдёмся по всем callback-функциям этого события,
for (i = 0, length = this.listeners[evt].length; i < length; i += 1) {
// и каждую запустим с указанными аргументами.
this.listeners[evt][i](args);
}
}
}
/**
* Генерирует случайное целое число в указанном диапазоне
*/
randomInteger(min, max) {
// случайное число от min до (max+1)
let rand = min + Math.random() * (max + 1 - min);
return Math.floor(rand);
}
}
// Создаем класс новой игры,
let gameDice = new GameDice();
// и добавляем обработчики события на результат выбрасывание костей,
gameDice.addEvent("throw",
(val) => { console.log("Выпало значение " + (val) + "."); }
)
// и на результат выпадания дубля.
gameDice.addEvent("double",
(val) => { console.log("Выпал дубль " + (val) + ":" + (val) + "."); }
)
// Выбрасываем кости десять раз.
for (let i = 0; i < 10; i++)
gameDice.throw();
import random
class Event:
""" Класс события """
def __init__(self):
self.__handlers = []
def __iadd__(self, handler):
""" Добавляет обработчик события """
self.__handlers.append(handler)
return self
def __isub__(self, handler):
""" Удаляет обработчик события """
self.__handlers.remove(handler)
return self
def __call__(self, *args, **keywargs):
""" Запускает обработчики события """
for handler in self.__handlers:
handler(*args, **keywargs)
class GameDice:
def __init__(self):
self.on_throw = Event()
self.on_double = Event()
def throw(self):
# Выбрасываем две кости, и получаем два случайных значения в диапазоне
# от 1 до 6.
dice_val1 = random.randint(1, 6)
dice_val2 = random.randint(1, 6)
# Инициируем событие выбрасывание костей с параметром полученной суммы
# значения костей.
self.on_throw(dice_val1 + dice_val2)
# Если значение двух костей одинаковы,
if dice_val1 == dice_val2:
# то дополнительно инициируем событие выпадания "дубля" с параметром
# значения дубля.
self.on_double(dice_val1)
def main():
# Создаем класс новой игры,
game_dice = GameDice()
# и добавляем обработчики события на результат выбрасывание костей,
game_dice.on_throw += lambda val: print("Выпало значение {0}.".format(val))
# и на результат выпадания дубля.
game_dice.on_double += lambda val: print("Выпал дубль {0}:{0}.".format(val))
# Выбрасываем кости десять раз.
for i in range(0, 10):
game_dice.throw()
if __name__ == '__main__':
main()
using System;
class MainClass {
static void Main() {
// Создаем класс новой игры,
GameDice gameDice = new GameDice();
// и добавляем обработчики события на результат выбрасывание костей,
gameDice.ThrowEvent += ThrowHandler;
// и на результат выпадания дубля.
gameDice.DoubleEvent += DoubleHandler;
// Выбрасываем кости десять раз.
for (int i = 0; i < 10; i++)
gameDice.Throw();
}
/**
* Функция обработки события выбрасования костей
*/
static void ThrowHandler(object sender, DiceArgs args) {
Console.WriteLine("Выпало значение {0}.", args.Val);
}
/**
* Функция обработки события выпадения дубля
*/
static void DoubleHandler(object sender, DiceArgs args) {
Console.WriteLine("Выпал дубль {0}:{0}.", args.Val);
}
class GameDice {
public delegate void DiceEventDelegate(object sender, DiceArgs args);
public event DiceEventDelegate ThrowEvent;
public event DiceEventDelegate DoubleEvent;
Random rnd;
public GameDice() {
rnd = new Random();
}
public void Throw() {
// Выбрасываем две кости, и получаем два случайных значения в диапазоне
// от 1 до 6.
int diceVal1 = rnd.Next(1, 7);
int deceVal2 = rnd.Next(1, 7);
// Инициируем событие выбрасывание костей с параметром полученной суммы
// значения костей.
OnThrow(diceVal1 + deceVal2);
// Если значение двух костей одинаковы,
if (diceVal1 == deceVal2)
// то дополнительно инициируем событие выпадания "дубля" с параметром
// значения дубля.
OnDouble(diceVal1);
}
/**
* Обработка события выбрасования костей
*/
protected virtual void OnThrow(int val) {
DiceArgs args = new DiceArgs(val);
ThrowEvent?.Invoke(this, args);
}
/**
* Обработка события выпадения дубля
*/
protected virtual void OnDouble(int val) {
DiceArgs args = new DiceArgs(val);
DoubleEvent?.Invoke(this, args);
}
}
/**
* Класс аргумента события игры в кости
*/
public class DiceArgs: EventArgs {
public int Val { get; }
public DiceArgs(int val) {
Val = val;
}
}
}
import java.util.Random;
import java.util.*;
public class Main
{
public static void main(String[] args) {
Main main = new Main();
main.Start();
}
public void Start() {
// Создаем класс новой игры,
GameDice gameDice = new GameDice();
// и добавляем класс слушателя (обработчика событий).
gameDice.addListener(gameDice);
// Выбрасываем кости десять раз.
for (int i = 0; i < 10; i++)
gameDice.Throw();
}
public class GameDice implements IGameDiceListener {
ArrayList<IGameDiceListener> listeners = new ArrayList<IGameDiceListener>();
Random rand;
public GameDice() {
rand = new Random();
}
public void Throw() {
// Выбрасываем две кости, и получаем два случайных значения в диапазоне
// от 1 до 6.
int diceVal1 = rand.nextInt(6) + 1;
int diceVal2 = rand.nextInt(6) + 1;
// Инициируем событие выбрасывание костей с параметром полученной суммы
// значения костей.
onThrow(diceVal1 + diceVal2);
// Если значение двух костей одинаковы,
if (diceVal1 == diceVal2)
// то дополнительно инициируем событие выпадания "дубля" с параметром
// значения дубля.
onDouble(diceVal1);
}
/**
* Добавляет слушателя (обработчик события)
*/
public void addListener(IGameDiceListener listener)
{
listeners.add(listener);
}
/**
* Удаляет слушателя (обработчик события)
*/
public void removeListeners(IGameDiceListener listener)
{
listeners.remove(listener);
}
/**
* Обработка события выбрасования костей
*/
public void onThrow(int val) {
for (IGameDiceListener listener : listeners)
listener.ThrowHandler(new GameDiceEvent(this, val));
}
/**
* Обработка события выпадения дубля
*/
public void onDouble(int val) {
for (IGameDiceListener listener : listeners)
listener.DoubleHandler(new GameDiceEvent(this, val));
}
/**
* Функция обработки события выбрасования костей
*/
public void ThrowHandler(GameDiceEvent event) {
System.out.printf("Выпало значение %d.\n", event.val);
}
/**
* Функция обработки события выпадения дубля
*/
public void DoubleHandler(GameDiceEvent event) {
System.out.printf("Выпал дубль %d:%d.\n", event.val, event.val);
}
}
/**
* Интерфейс слушателя (класса обработчика события) игра в кости.
*/
public interface IGameDiceListener {
void ThrowHandler(GameDiceEvent event);
void DoubleHandler(GameDiceEvent event);
}
/**
* Класс аргумента события игры в кости
*/
public class GameDiceEvent extends EventObject {
public int val;
public GameDiceEvent(Object arg0, int val) {
super(arg0);
this.val = val;
}
}
}
Итак, что здесь можно увидеть. В программном коде выше в очень простом виде реализована игра в кости - GameDice. При выбрасывании костей (вызов метода throw()) две переменные получают случайные значения равные от 1 до 6, имитируя выброс двух игральных кубиков. При каждом выбросе костей инициируется вызов "события" throw, в параметре которого передаётся результат выброса, то есть сумма выпавших костей. Если значения двух кубиков одинаковы, то так же дополнительно инициируется "событие" выпадение дубля, в параметре которого передаётся значение дубля. Далее, имея два этих "события", уже в главной рабочей функции им назначаются обработчики. В нашем случае обработчики так же очень простые, которые всего лишь оповещают о результате выброса кубика, и о выпавшем дубле, если он выпал. Но, конечно же, на эти "события" можно "повесить" любые обработчики.
Для примера я взял реализацию "событий" в языках javaScript, Python 3, C# и Java, и во всех этих языках эта реализация немного отличается.
Например, в javaScript все "события" заключены в одном массиве (listeners), что вполне укладывается в общую концепцию событий на этом языке. Если посмотреть синтаксис обработки браузерных событий, например "onclick", то он будет очень похож на то, как далее будет реализована обработка "событий" с учётом указанного выше шаблона для javaScript, только в браузере используется метод "on", а в предлагаемом выше шаблоне для добавления обработчика событий используется метод "addEvent", хотя, при желании, это метод можно переименовать как угодно.
В Python-е же предложен другой способ реализации "событий", посредством назначения переменной "события" небольшого специального класса Event, который сам способен обрабатывать, то есть добавлять, удалять и запускать, назначаемые ему callback-функции обработки события.
В C# шаблон обработки "события" уже зашит внутрь языка, предлагая для работы с ним всего лишь ключевое слово event. С помощью event и делегата создается специальная переменная для события, которая, по сути, уже является массивом, и в который можно добавлять или удалять из него, обработчики событий, опять же callback-функции. В качестве параметров обработчику событий обычно передаётся два параметра: первый sender, через который передаётся экземпляр самого класса, который инициировал событие, а в качестве второго параметра чаще всего формируют специальный класс, через который достаточно удобно и передаются все нужные данные, относящиеся к произошедшему "событию".
Наверное, в Java будет самый непривычный шаблон реализации "событий" из всех выше представленных, с использованием так называемых "слушателей". Хотя, если разобраться, то и у него в основе лежат всё те же callback-функции, только так, как они реализованы в Java. Чаще всего это выглядит таким образом. Создаётся массив "listeners", который представляет из себя обычный массив классов, реализующих нужный интерфейс "слушателя". В этом массиве будут сохраняться все добавляемые классы с определёнными зарегистрированными "слушателями" callback-функциями. Интерфейс обязывает реализовать функции, требуемые для обработки событий, которые используются в классе. Таким образом, через один назначаемый в качестве обработчика событий класс, "слушатель" может назначить обработку сразу нескольким "событиям". В Java поэтому даже часто предусмотрено, на случай, если нужно обработать только одно или несколько событий из множества возможных, использование адаптеров, которые и позволяют из всех событий назначить обработчик только одному или нескольким "событиям", не нарушая требования интерфейса. Для передачи параметров в функции-обработчики "событий", так же как и в C#, удобнее всего использовать специальный класс.
Ну, вот теперь, я надеюсь, стало более понятно что такое "событие", и как оно организовано в ООП.