статья
Жорж Парадокс

Event - события в объектно-ориентированном программировании

Привет всем!

В объектно-ориентированном программировании существует одна очень полезная "вещь", которая называется "событие". И так, что же это такое, "событие", и зачем оно нужно?

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

Самым простым способ реализовать обработку "события" является использование 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#, удобнее всего использовать специальный класс.

Ну, вот теперь, я надеюсь, стало более понятно что такое "событие", и как оно организовано в ООП.

к началу статьи
0 639 0
Мы используем cookie-файлы, чтобы получить статистику, которая помогает нам улучшить сервис для Вас с целью персонализации сервисов и предложений. Вы можете прочитать подробнее о cookie-файлах или изменить настройки браузера. Продолжая пользоваться сайтом без изменения настроек, вы даёте согласие на использование ваших cookie-файлов.