Функції зворотного виклику, анонімні функції і механізм замикань

Reg.ru: домени і хостинг

Найбільший реєстратор і хостинг-провайдер в Росії.

Більше 2 мільйонів доменних імен на обслуговуванні.

Просування, пошта для домену, рішення для бізнесу.

Понад 700 тис. Клієнтів по всьому світу вже зробили свій вибір.

Перейти на сайт->

Безкоштовний Курс "Практика HTML5 та CSS3"

Освойте безкоштовно покроковий відеокурс

з основ адаптивної верстки

на HTML5 та CSS3 з повного нуля.

Начать->

Начать->

Фреймворк Bootstrap: швидка адаптивна верстка

Покроковий відеокурс з основ адаптивної верстки в фреймворку Bootstrap.

Навчитеся верстати просто, швидко і якісно, ​​використовуючи потужний і практичний інструмент.

Верстайте на замовлення і отримуйте гроші.

Отримати в подарунок->

Отримати в подарунок->

Безкоштовний курс "Сайт на WordPress"

Хочете освоїти CMS WordPress?

Отримайте уроки по дизайну і верстці сайту на WordPress.

Навчіться працювати з темами і нарізати макет.

Безкоштовний відеокурс по малюванню дизайну сайту, його верстці та встановлення на CMS WordPress!

Отримати в подарунок->

* Наведіть курсор миші для припинення прокрутки.


Так, анонімні функції відносяться не тільки до області об'єктно-орієнтованого програмування, проте обійти їх стороною було б дивно - настільки вони корисні й зручні.

Анонімні функції дуже активно використовуються в ООП поряд з функціями зворотного виклику (так звані callback-функції). Ну що ж, давайте звернемося до прикладів.

Для початку визначимо два класи.

class Product {public $ name; public $ price; function __construct ($ name, $ price) {$ this-> name = $ name; $ This-> price = $ price; }} Class ProcessSale {private $ callbacks; function registerCallback ($ callback) {if (! is_callable ($ callback)) {throw new Exception ( "callback not callable"); } $ This-> callbacks [] = $ callback; } Function sale ($ product) {print "{$ product-> name}: processing <br>"; foreach ($ this-> callbacks as $ callback) {call_user_func ($ callback, $ product); }}}?>

Даний код призначений для запуску декількох функцій зворотного виклику. У класі Product просто зберігаються значення властивостей $ name і $ price. Для спрощення конструкції вони оголошені відкритими, хоча в бойовому проекті слід зробити їх закритими (private) або захищеними (protected) і створити для цих властивостей методи доступу.

У класі ProcessSale визначені два методу. Методу registerCallback () передається звичайна скалярна змінна без жодних уточнень. Після її перевірки, вона додається в масив функцій зворотного виклику $ callbacks. Процес тестування при цьому виконується за допомогою вбудованої функції is_callable ().

В результаті гарантується, що методу registerCallback () буде передано ім'я функції, яку можна викликати за допомогою таких функцій, як call_user_func () або array_walk ().

Методу sale () передається об'єкт типу Product. Метод виводить про це інформацію і потім в циклі виконує перебір елементів масиву $ callback.

Кожен елемент разом з об'єктом типу Product передається функції call_user_func (), яка, власне, і викликає код, написаний користувачем.

Чим же корисні функції зворотного виклику? Вони дозволяють в процесі роботи скрипта додавати компоненту новий функціонал, який безпосередньо не пов'язаний з його початкової функціональністю.

Передбачивши в об'єкті роботу з функціями зворотного виклику, ви тим самим дозволите іншим розробникам розширювати функціональність вашого коду, якщо в цьому буде необхідність. Причому обставини можуть бути досить цікаві.

Уявіть собі, що через якийсь час хтось, допрацьовувати ваш клас ProcessSale захоче створити журнал продажів. Якщо у людини є доступ до вихідного коду класу, він може просто дописати код для фіксації фактів угод безпосередньо в метод sale (). Але це не завжди буде вдалим рішенням.

Якщо цей користувач (що вносить зміни) не є власником пакета, в якому визначено клас ProcessSale, то все його виправлення будуть затерті, коли вийде його оновлена версія. Але навіть якщо з цим моментом все добре, все одно, додавати в метод sale () додаткові гілки коду, вирішальні якісь "випадкові" завдання, нерозумно.

Чому? Справа в тому, що в кінцевому підсумку такий підхід призведе до зміни зони відповідальності класу і може знизити можливість його повторного використання в інших проектах, а це порушення одного з головних принципів веб-програмування - принципу повторного використання коду.

Незважаючи на це, завдяки щасливому випадку, ми закладемо в клас ProcessSale можливість зворотного виклику.

Давайте подивимося на код функції зворотного виклику, яка фіксує всі операції в журналі продажів.

$ Logger = create_function ( '$ product', 'print "Записуємо ... ({$ product-> name}) <br>"; "); $ Processor = new ProcessSale (); $ Processor-> registerCallback ($ logger); $ Processor-> sale (new Product ( "Туфлі", 6)); print "<br>"; $ Processor-> sale (new Product ( "Кава", 6));

Тут для створення функції зворотного виклику ми скористалися функцією create_function (). Як бачите, їй передаються два параметри. Спочатку вказується список параметрів функції, а потім - тіло самої функції.

В результаті у нас виходить конструкція, яку зазвичай називають анонімної функцією, оскільки при створенні ми не присвоїли їй ім'я, як це зазвичай буває.

Замість цього посилання на новостворену функцію зберігається в змінної, яку потім можна передати в якості параметра інших функцій і методів. Якраз це ми і робимо в коді, наведеному вище: ми зберігаємо посилання на анонімну функцію в змінній $ logger, яку потім передаємо як параметр методу ProcessSale :: registerCallback ().

В кінці цього фрагмента коду ми створюємо два об'єкти, що описують товари, і передаємо їх по черзі методу sale (). Про те, що станеться далі, ви вже, напевно, здогадалися: кожна операція з продажу буде оброблена (в нашому випадку просто буде виведено повідомлення про товар), після чого відбудеться виклик всіх прописаних callback-функцій.

Туфлі: обробляється ... Записуємо ... (туфлі) Кава: обробляється Записуємо ... (Кава)

Давайте ще раз проаналізуємо приклад коду з функцією create_function (). Вона ж виглядає просто жахливо, помітили?))

При приміщенні виконуваного коду в рядок завжди виникає головний біль. Для початку нам потрібно виконати екранування всіх символів "$" і "?", Які зустрічаються в тілі скрипта. Більш того, у міру зростання тіла функції (а чомусь це відбувається завжди!), Її буде все важче проаналізувати і зрозуміти.

Було б здорово знайти якийсь більш елегантний спосіб для створення таких функцій. І він існує :) Ми можемо просто оголосити функцію як зазвичай, а потім привласнити посилання на неї потрібної нам змінної. І все це - в одному операторі! Нижче наведено попередній приклад, в якому використаний інший синтаксис.

$ Logger2 = function ($ product) {print "Записуємо ({$ product-> name}) <br>"; }; $ Processor = new ProcessSale (); $ Processor-> registerCallback ($ logger2); $ Processor-> sale (new Product ( "Туфлі", 6)); print "<br>"; $ Processor-> sale (new Product ( "Кава", 6));

Як бачите, відмінність тут полягає в способі створення змінної, що посилається на анонімну функцію. Очевидно, що цей варіант коду не тільки зрозуміліше, але і простіше. Ми просто вказуємо в операторі присвоєння ключове слово function і не задаємо ім'я функції.

При цьому зверніть увагу, що оскільки в операторі присвоєння використовується вбудована функція, в кінці блоку потрібно обов'язково помістити крапку з комою.

Само собою, що функції зворотного виклику зовсім не зобов'язані бути анонімними. В якості такої функції можна сміливо використовувати ім'я звичайної функції або навіть посилання на метод якогось об'єкта. наприклад:

class Mailer {function doMail ($ product) {print "Упаковуємо ({$ product-> name}) <br>"; }} $ Processor = new ProcessSale (); $ Processor-> registerCallback (array (new Mailer (), "doMail")); $ Processor-> sale (new Product ( "Туфлі", 6)); print "<br>"; $ Processor-> sale (new Product ( "Кава", 6));

У коді вище ми створюємо новий клас Mailer, що містить метод doMail (). Даному методи ми передаємо об'єкт типу Product, про який метод виводить повідомлення.

Далі, при виклику методу registerCallback () ми передаємо йому як параметр масив, ане посилання на функцію зворотного виклику, як ми це робили раніше. Першим елементом даного масиву є об'єкт типу Mailer, а другим - рядок, що містить ім'я методу, який ми хочемо викликати.

Пам'ятайте, що в методі registerCallback () за допомогою функції is_callable () виконується перевірка типу переданого аргументу? Відмінно. Що цікаво, ця функція досить інтелектуальна, щоб розпізнати масиви подібного виду. Тому при вказівці функції зворотного виклику у вигляді масиву, в першому елементі такого масиву повинен знаходитися об'єкт, що містить викликається метод, а ім'я цього методу поміщається у вигляді рядка в другій елемент масиву.

Таким чином, ми успішно проходимо перевірку типу аргументу, а наш код при відпрацюванні дасть наступний результат:

Туфлі: обробляється Упаковуємо (туфлі) Кава: обробляється Упаковуємо (Кава)

Зрозуміло, що анонімну функцію можна повернути з методу, як показано нижче.

class Totalizer {static function warnAmount () {return function ($ product) {if ($ product-> price> 5) {print "купується дорогий товар: {$ product-> price} <br>"; }}; }} $ Processor = new ProcessSale (); $ Processor-> registerCallback (Totalizer :: warnAmount ());

У цьому прикладі немає нічого цікавого, крім того, що метод warnAmount () використовується в якості фабрики анонімної функції. Проте, подібна структура дозволяє нам робити набагато більше, ніж просто генерувати анонімну функцію. Вона дозволяє скористатися перевагами механізму замикання.

У анонімної функції нового типу можуть використовуватися змінні, оголошені в інший анонімної функції, що знаходиться в батьківській області видимості. Дана концепція досить важка, щоб зрозуміти її відразу. Але якщо говорити самим простою мовою, то анонімна функція як би запам'ятовує контекст, в якому вона була створена.

Припустимо, ми хочемо, щоб метод warnAmount () виконував наступне:

1. Щоб йому можна було передавати порогове значення вартості товарів.
2. Щоб він підраховував вартість (тобто суму цін) проданого товару.
3. І, коли вартість товару перевищить певний поріг, програма повинна виконати якусь дію (для спрощення конструкції в нашому випадку це не більше, ніж висновок повідомлення)

Щоб анонімна функція могла скористатися змінними, визначеними в батьківській області видимості, використовується ключове слово use, як показано в прикладі нижче.

class Totalizer {static function warnAmount ($ amt) {$ count = 0; return function ($ product) use ($ amt, & $ count) {$ count + = $ product-> price; print "сума: $ count <br>"; if ($ count> $ amt) {print "Продано товарів на суму: {$ count} <br>"; }}; }} $ Processor = new ProcessSale (); $ Processor-> registerCallback (Totalizer :: warnAmount (8)); $ Processor-> sale (new Product ( "Туфлі", 6)); print "<br>"; $ Processor-> sale (new Product ( "Кава", 6));

У директиві use анонімної функції, яка повертається методом Totalizer :: warnAmount (), вказані дві змінні. Перша з них - $ amt, яка є аргументом, переданим методу warnAmount ().

Друга - замкнута змінна $ count. Вона оголошена в тілі методу warnAmount (), і початкове її значення дорівнює нулю. Зверніть увагу на те, що перед ім'ям зміною $ count в директиві use вказано символ амперсанда - "&".

Це означає, що дана змінна буде передаватися в анонімну функцію по посиланню, а не за значенням. Справа в тому, що в тілі анонімної функції ми додамо до неї ціну товар і потім порівняємо нову суму зі значенням змінної $ amt. Якщо буде досягнуто порогове значення, виводиться відповідне повідомлення, як показано нижче.

Туфлі: обробляється Упаковуємо (туфлі) Кава: обробляється Упаковуємо (Кава) Продано товарів на суму: 12

У цьому прикладі було показано, що значення змінної $ count зберігається між викликами функції зворотного виклику. Обидві змінні і $ count, і $ amt, залишаються пов'язаними з цією функцією, оскільки вони вказані в контексті її оголошення і перераховані в директиві use.

На цьому закруглюється, а в наступному матеріалі поговоримо про те, як отримувати інформацію про об'єкт або класі, методах, властивості і успадкування .

Сподобався матеріал і хочете віддячити?
Просто поділіться з друзями і колегами!


Дивіться також:

наверх

Чим же корисні функції зворотного виклику?
Чому?
Вона ж виглядає просто жахливо, помітили?
Для початку нам потрібно виконати екранування всіх символів "$" і "?
Сподобався матеріал і хочете віддячити?