Conform Microsoft, tiparul observatorului este un design comportamental care permite unui obiect să notifice alte obiecte despre modificări ale stării sale.
Multor programatori începători (și chiar și celor mai experimentați) le este greu să înțeleagă legătura dintre delegați și evenimente, iar fundația pe care este construită această legătură este reprezentată tocmai de tiparul observatorului. Pentru ca acest concept să fie ușor de înțeles, astfel încât să puteți învăța tranziția la evenimente mai ușor, să luăm un exemplu simplu. Să ne imaginăm că vrem să punem în aplicare un sistem care să alerteze mașinile atunci când vine un tren, astfel încât acestea să se oprească. Prima cărămidă a acestui sistem va fi reprezentat de o clasă numită SemnalTren:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
using System; namespace BunaLume { public class SemnalTren { public Action VinTrenuri; public void VineUnTren() { // pretinde ca exista multa logica aici, ca de exemplu // luminare intermitenta, coborare bariere, alarme, etc // invoca delegatul VinTrenuri(); } } public class Program { public static void Main() { Console.Read(); } } } |
Nu este nimic complicat în ceea ce privește clasa mea: am adăugat un Action, VinTrenuri, și amintiți-vă, un Action este doar un delegat care ia între 0 și 16 parametri și returnează void. Acesta va reprezenta semnalul meu de tren. În cele din urmă, am adăugat o metodă publică numită VineUnTren(), care va efectua toate acțiunile necesare la sosirea unui tren și de asemenea, va informa pe oricine este interesat în venirea trenului, invocând delegatul VinTrenuri.
Următorul pas este să declar obiectele care sunt interesate de venirea unui tren, așa că voi adăuga o clasă Masina:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
using System; namespace BunaLume { public class SemnalTren { public Action VinTrenuri; public void VineUnTren() { // pretinde ca exista multa logica aici, ca de exemplu // luminare intermitenta, coborare bariere, alarme, etc // invoca delegatul VinTrenuri(); } } public class Masina { public Masina(SemnalTren semnalTren) { semnalTren.VinTrenuri += OpresteMasina; } private void OpresteMasina() { Console.WriteLine("Opreste masina imediat, vine un tren!"); } } public class Program { public static void Main() { Console.Read(); } } } |
În clasa mea Masina, am un constructor supraîncărcat care acceptă o instanță SemnalTren, iar în interiorul acestui constructor adaug o metodă numită OpresteMasina() la delegatul înlănțuit VinTrenuri. Acest lucru poate fi înțeles ca „Abonez metoda OpresteMasina() la delegatul VinTrenuri, astfel încât atunci când este invocat acest delegat, metoda mea va fi apelată și ea”, sau pur și simplu „hei, adăugă-mă la lista abonaților acestui delegat”.
Și tocmai acesta este modul .NET de a implementa tiparul observatorului: prin abonarea metodelor noastre la lanțul de metode al unui delegat, ori de câte ori va fi invocat acel delegat, toate metodele care s-au abonat vor fi apelate, astfel încât acestea vor „observa” că delegatul a fost invocat. În exemplul meu, toate instanțele Masina care se vor abona la delegatul VinTrenuri vor observa când acest delegat va fi invocat (când semnalul de tren va fi declanșat și toate metodele abonate vor fi notificate).
Acum, tot ce trebuie făcut este să punem efectiv întregul sistem în funcțiune:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
using System; namespace BunaLume { public class SemnalTren { public Action VinTrenuri; public void VineUnTren() { // pretinde ca exista multa logica aici, ca de exemplu // luminare intermitenta, coborare bariere, alarme, etc // invoca delegatul VinTrenuri(); } } public class Masina { public Masina(SemnalTren semnalTren) { semnalTren.VinTrenuri += OpresteMasina; } private void OpresteMasina() { Console.WriteLine("Opreste masina imediat, vine un tren!"); } } public class Program { public static void Main() { // creaza un semnal de tren nou SemnalTren semnalTren = new SemnalTren(); // subscrie cateva obiecte Masina la acest semnal de tren new Masina(semnalTren); new Masina(semnalTren); new Masina(semnalTren); new Masina(semnalTren); new Masina(semnalTren); // declanseaza semnalul si informeaza toate masinile ca vine un tren semnalTren.VineUnTren(); Console.Read(); } } } |
Primul lucru de reținut este faptul că un delegat ține evidența nu doar a metodei pe care o va apela ulterior, ci și a obiectului de care acea metodă aparține. Am explicat în lecția despre delegați că aceștia au două proprietăți importante: Method și Target. Din acest motiv, nu trebuie să declar un nume pentru instanțele mele de tip Masina, le pot instanția anonim, folosind direct operatorul new. Întrucât toate se abonează la delegatul VinTrenuri în interiorul constructorului lor, delegatul își va aminti fiecare instanță care a subscris la el.
În continuare, observați că atunci când apelez metoda VineUnTren(), aceasta va invoca delegatul, ceea ce înseamnă că delegatul va apela orice metodă OpresteMasina() a oricărei instanțe Masina care s-a abonat la el, notificând efectiv fiecare abonat.
Aceasta este rezultatul care dovedește faptul că toate cele 5 instanțe Masina abonate s-au oprit atunci când a fost invocat delegatul semnal de tren:
Tags: abonat, Action, delegați, delegați înlănțuiți, observator, tiparul observatorului