MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Aplicarea delegatelor

În tema anterioară, delegații au fost examinați în detaliu. Totuși, exemplele date poate că nu arată adevărata putere a delegatelor, deoarece metodele necesare în acest caz pot fi apelate direct fără niciun delegat.

Cu toate acestea, partea cea mai puternică a delegatelor constă în faptul că ele permit delegarea executării unui cod extern. Și în momentul scrierii programului, putem să nu știm ce cod va fi executat.

Pur și simplu apelăm delegatul. Iar ce metodă va fi executată efectiv la apelarea delegatului, va fi decis ulterior.

Să luăm un exemplu detaliat. Să presupunem că avem o clasă care descrie un cont bancar:

public class Account
{
   int sum; // Variabilă pentru stocarea sumei
   // prin constructor se stabilește suma inițială în cont
   public Account(int sum) => this.sum = sum;
   // adaugă fonduri în cont
   public void Add(int sum) => this.sum += sum;
   // retrage bani din cont
   public void Take(int sum)
   {
       // retragem bani dacă sunt suficienți în cont
       if (this.sum >= sum) this.sum -= sum;
   }
}

În variabila sum este stocată suma din cont. Cu ajutorul constructorului se stabilește suma inițială în cont. Metoda Add() servește pentru a adăuga bani în cont, iar metoda Take - pentru a retrage bani din cont.

Să presupunem că, în cazul retragerii banilor prin metoda Take, trebuie cumva să-l notificăm pe deținătorul contului și, poate, alte obiecte.

Dacă este vorba de un program de consolă, și clasa va fi utilizată în același proiect în care a fost creată, putem scrie pur și simplu:

public class Account
{
   int sum;
   public Account(int sum) => this.sum = sum;
   public void Add(int sum) => this.sum += sum;
   public void Take(int sum)
   {
       if (this.sum >= sum)
       {
           this.sum -= sum;
           Console.WriteLine($"S-au retras {sum} unități.");
       }
   }
}

Dar ce facem dacă clasa noastră este planificată să fie utilizată în alte proiecte, de exemplu, într-o aplicație grafică pe Windows Forms sau WPF, într-o aplicație mobilă, într-o aplicație web. Acolo linia de notificare:

Console.WriteLine($"S-au retras {sum} unități.");

Nu va avea mare sens.

Mai mult, clasa noastră Account va fi utilizată de alți dezvoltatori sub forma unei biblioteci de clase separate. Și acești dezvoltatori vor dori să notifice retragerea fondurilor într-un alt mod, despre care nici nu putem bănui în momentul scrierii clasei. Prin urmare, notificarea primitivă sub forma unei linii de cod:

Console.WriteLine($"S-au retras {sum} unități.");

Nu este cea mai bună soluție în acest caz. Iar delegații permit delegarea definirii acțiunii din clasă către un cod extern, care va folosi această clasă.

Să modificăm clasa, aplicând delegații:

// Declarăm delegatul
public delegate void AccountHandler(string message);
public class Account
{
   int sum;
   // Creăm o variabilă delegat
   AccountHandler? taken;
   public Account(int sum) => this.sum = sum;
   // Înregistrăm delegatul
   public void RegisterHandler(AccountHandler del)
   {
       taken = del;
   }
   public void Add(int sum) => this.sum += sum;
   public void Take(int sum)
   {
       if (this.sum >= sum)
       {
           this.sum -= sum;
           // apelăm delegatul, transmitându-i mesajul
           taken?.Invoke($"S-au retras {sum} unități.");
       }
       else
       {
           taken?.Invoke($"Fonduri insuficiente. Sold: {this.sum} unități.");
       }
   }
}

Pentru delegarea acțiunii aici este definit delegatul AccountHandler. Acest delegat corespunde oricăror metode care au tipul void și acceptă un parametru de tip string.

public delegate void AccountHandler(string message);

În clasa Account definim variabila taken, care reprezintă acest delegat:

AccountHandler? taken;

Acum trebuie să legăm această variabilă de o acțiune concretă, care va fi executată. Putem folosi diferite modalități pentru a transmite delegatul în clasă. În acest caz, se definește o metodă specială RegisterHandler, în care în variabila taken este transmisă acțiunea reală:

public void RegisterHandler(AccountHandler del)
{
   taken = del;
}

Astfel, delegatul este stabilit, și acum poate fi apelat. Apelul delegatului se realizează în metoda Take:

public void Take(int sum)
{
   if (this.sum >= sum)
   {
       this.sum -= sum;
       // apelăm delegatul, transmitându-i mesajul
       taken?.Invoke($"S-au retras {sum} unități.");
   }
   else
   {
       taken?.Invoke($"Fonduri insuficiente. Sold: {this.sum} unități.");
   }
}

Deoarece delegatul AccountHandler acceptă ca parametru un șir de caractere, la apelarea variabilei taken() putem transmite în acest apel un mesaj concret. În funcție de faptul dacă s-a realizat retragerea banilor sau nu, în apelul delegatului sunt transmise mesaje diferite.

Deci, în locul delegatului vor fi efectuate acțiunile care sunt transmise delegatului în metoda RegisterHandler. Și, repet, la apelarea delegatului nu știm ce acțiuni vor fi. Aici doar transmitem acțiunilor mesajul despre reușita sau nereușita retragerii.

Acum testăm clasa în programul principal:

// creăm un cont bancar
Account account = new Account(200);
// Adăugăm în delegat o referință la metoda PrintSimpleMessage
account.RegisterHandler(PrintSimpleMessage);
// Încercăm de două ori la rând să retragem bani
account.Take(100);
account.Take(150);

void PrintSimpleMessage(string message) => Console.WriteLine(message);

Aici, prin metoda RegisterHandler, variabilei taken din clasa Account i se transmite o referință la metoda PrintSimpleMessage. Această metodă corespunde delegatului AccountHandler. Astfel, acolo unde în clasa Account se apelează delegatul taken, în realitate va fi executată metoda PrintSimpleMessage.

Prin parametrul message, metoda PrintSimpleMessage va primi mesajul transmis din delegat și îl va afișa în consolă:

S-au retras 100 unități.
Fonduri insuficiente. Sold: 100 unități.

Astfel, am creat un mecanism de apel invers pentru clasa Account, care se activează în cazul retragerii banilor. Aici afișăm mesajul în consolă. Da, am putea pur și simplu să afișăm mesajul în consolă și fără delegați. Totuși, cu un delegat, pentru clasa Account nu contează cum se afișează acest mesaj. Clasa Account nici nu știe ce se va face efectiv ca urmare a retragerii banilor. Ea doar trimite o notificare despre acest lucru prin delegat.

În rezultat, dacă creăm o aplicație de consolă, putem afișa mesajul în consolă prin delegat. Dacă creăm o aplicație grafică Windows Forms sau WPF, putem afișa mesajul sub formă de fereastră grafică.

Sau putem nu doar să afișăm mesajul. Putem, de exemplu, să înregistrăm informația despre această acțiune într-un fișier sau să trimitem o notificare prin e-mail. În general, putem trata apelul delegatului prin orice metode. Iar modul de tratare nu va depinde de clasa Account.

Adăugarea și eliminarea metodelor în delegat

Deși în exemplul nostru delegatul accepta adresa unei singure metode, în realitate el poate indica simultan mai multe metode. În plus, dacă este necesar, putem elimina referințele la anumite metode, astfel încât acestea să nu fie apelate la apelul delegatului.

Așadar, modificăm în clasa Account metoda RegisterHandler și adăugăm o nouă metodă UnregisterHandler, care va elimina metodele din lista metodelor delegatului:

public delegate void AccountHandler(string message);
public class Account
{
   int sum;
   AccountHandler? taken;
   public Account(int sum) => this.sum = sum;
   // Înregistrăm delegatul
   public void RegisterHandler(AccountHandler del)


   {
       taken += del;
   }
   // Anularea înregistrării delegatului
   public void UnregisterHandler(AccountHandler del)
   {
       taken -= del; // eliminăm delegatul
   }
   public void Add(int sum) => this.sum += sum;
   public void Take(int sum)
   {
       if (this.sum >= sum)
       {
           this.sum -= sum;
           taken?.Invoke($"S-au retras {sum} unități.");
       }
       else
           taken?.Invoke($"Fonduri insuficiente. Sold: {this.sum} unități.");
   }
}

În prima metodă, delegații taken și del sunt combinați într-unul singur, care apoi este atribuit variabilei taken. În a doua metodă, din variabila taken este eliminat delegatul del.

Aplicăm noile metode:

Account account = new Account(200);
// Adăugăm în delegat referințe la metode
account.RegisterHandler(PrintSimpleMessage);
account.RegisterHandler(PrintColorMessage);
// Încercăm de două ori la rând să retragem bani
account.Take(100);
account.Take(150);

// Eliminăm delegatul
account.UnregisterHandler(PrintColorMessage);
// încercăm din nou să retragem bani
account.Take(50);

void PrintSimpleMessage(string message) => Console.WriteLine(message);
void PrintColorMessage(string message)
{
   // Setăm culoarea roșie a caracterelor
   Console.ForegroundColor = ConsoleColor.Red;
   Console.WriteLine(message);
   // Resetăm setările de culoare
   Console.ResetColor();
}

Pentru testare, am creat încă o metodă - PrintColorMessage, care afișează același mesaj doar că în culoarea roșie. Referința la această metodă este, de asemenea, transmisă în metoda RegisterHandler, și astfel va fi primită de variabila taken.

În linia account.UnregisterHandler(PrintColorMessage); această metodă este eliminată din lista apelurilor delegatului, deci această metodă nu va mai fi executată. Afișarea în consolă va avea următoarea formă:

S-au retras 100 unități.
S-au retras 100 unități.
Fonduri insuficiente. Sold: 100 unități.
Fonduri insuficiente. Sold: 100 unități.
S-au retras 50 unități.
← Lecția anterioară Lecția următoare →