ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 126
Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 90
Abonament PDF

Extensiile Reactive (Rx) - Introducere în programarea reactivă

Bogdan Dobrea
Senior Software Developer @ AROBS



PROGRAMARE

Cu toții am învățat în școală modul de programare imperativ, care are avantajul de a fi simplu de explicat și implementat. Aceasta paradigmă de programare ne permite să scriem un cod în care descriem, pas cu pas, ce anume vrem să facem, iar rezultatul va fi întocmai ce voiam să se întâmple.

Să încercăm să exemplificăm acest lucru folosind o situație reală: un magazin online, unde, prin intermediul browserului, orice utilizator poate intra pentru a se uita la produse și să-și comande unul dintre produsele agreate.

Ca administrator al acestui magazin online, am vrea să creăm un algoritm care să ne returneze un top al celor mai "înstăriți" clienți: spre exemplu, dorim să aflăm câți clienți au în coșul lor produse care însumează cel puțin 1000 Ron.

În cod, clientul nostru va fi asociat cu clasa Customer. Avem nevoie de doar două proprietăți, numele acestuia (Name) precum și totalul coșului de cumpărături, exprimat prin proprietatea OrderAmount. Pentru simplitate, vom folosi lei românești (RON), atunci când ne vom raporta la totalul coșului. Exemplele ce vor urma sunt scrise în C#, însă subiectul articolului nu este limitat doar la acest limbaj.

public class Customer
{
  public string Name { get; set; }
  public int 
    OrderAmount { get; set; }

  public Customer
    (string name, int orderAmount)
    {
      Name = name;
      OrderAmount = orderAmount;
    }
}

Pornim de la premisa că la un moment dat, în spațiul nostru virtual privat se află cinci clienți, fiecare având un nume precum și un coș de o anumită valoare:

public class Customer
{
  public string Name { get; set; }
  public int 
    OrderAmount { get; set; }

  public Customer
    (string name, int orderAmount)
    {
      Name = name;
      OrderAmount = orderAmount;
    }
}

Algoritmul nostru imperativ, prin care dorim să aflăm care din cei cinci clienți a cheltuit cel puțin 1000 Ron ar putea arăta în felul următor:

public class Customer
{
  public string Name { get; set; }
  public int 
    OrderAmount { get; set; }

  public Customer
    (string name, int orderAmount)
    {
      Name = name;
      OrderAmount = orderAmount;
    }
}

În ton cu definiția stilului de programare imperativă amintită la începutul articolului, am descris pe fiecare linie, pas cu pas, ceea ce am dorit să se întâmple. Mai întâi am creat o listă nouă topSpendersList, apoi am parcurs lista customers element cu element, interesându-ne doar numele persoanei al cărei coș de cumpărături depășește sau este egal cu 1000 Ron. La finalul execuției acestui algoritm, vom observa ca în lista topSpendersList avem trei membri ("Felicia", "Robert" și "Fred"), pentru că fiecare din cei trei a cheltuit cel puțin 1000 Ron.

Programarea declarativă

Totul a fost bine până acum, pentru că am aflat ceea ce ne interesa, dar să vedem cum am putea rescrie acest algoritm folosind modul de programare declarativ.

Programarea declarativă, în contrast cu cea imperativă, este un stil de programare în care se descrie logica unui calcul, însă fără să se prezinte modul de execuție. Cei familiarizați cu .Net (C#, VB) deja folosesc acest mod de programare, atunci când scriu cod folosind LINQ:

var topSpendersList = customers
   .Where(customer => customer.OrderAmount >= 1000)
   .Select(customer => customer.Name)
   .ToList();

În liniile de cod de mai sus este exprimat logica unui calcul (ia din lista customers doar clienții care au valoarea coșului de cumpărături cel puțin egală cu 1000 Ron, apoi folosind numele acestora creează o nouă listă), fără a fi necesar să scriem pas cu pas ce trebuie făcut. Codul sursă al funcțiilor Where(), Select() si ToList() se află în librăria .NET Framework.

Rezultatul execuției acestui algoritm va fi identic cu cel anterior, deci lista topSpendersList va conține aceiași trei membri ("Felicia", "Robert" și "Fred").

Programarea reactivă

Ce înseamnă a fi reactiv? Din DEX aflăm că a fi reactiv înseamnă a răspunde la un stimul.

Să presupunem că de fiecare dată când apare un client nou pe site, vrem ca lista topSpendersList să își facă update cu numele noului client, dacă respectă condiția impusă de noi.

Exemplele scrise anterior nu vor mai funcționa, pentru ca ele depind de o listă de clienți fixă, ori noi acum avem un flux de clienți.

Și aici intervin Extensiile Reactive, ce reprezintă o librărie gratuită cu ajutorul căreia putem rescrie algoritmul nostru, astfel încât să țină cont de faptul ca trebuie sa "reacționeze" de fiecare dată când avem un client nou.

ObservableCollection customersList = 
  new ObservableCollection();

var topSpendersList = new List();

Observable
 .FromEventPattern(customersList,"CollectionChanged")
 .SelectMany(handler =>(handler.EventArgs 
    as NotifyCollectionChangedEventArgs)
   .NewItems.Cast())
 .Where(customer => customer.OrderAmount >= 1000)
 .Subscribe(customer =>
 {
   topSpendersList.Add(customer.Name);
 });

Deși sunt mai multe linii de cod scrise decât în exemplele anterioare, vom afla că sunt ușor de înțeles. De aceea, acest pattern îl vom folosi des pe viitor.

Să le luăm pe rând:

Dacă executăm noul algoritm, fără a avea nici un element în customersList, vom vedea că nu se întâmplă nimic. Pe măsură ce adăugăm elemente noi, algoritmul va fi parcurs și în urma execuției lui, topSpendersList va fi populat.

customersList.Add(new Customer("John", 200));
customersList.Add(new Customer("Alice", 150));
customersList.Add(new Customer("Felicia", 1250));
customersList.Add(new Customer("Robert", 2100));
customersList.Add(new Customer("Fred", 1850));

După ce adăugăm cei cinci clienți inițiali, în topSpendersList vom regăsi numele ultimilor trei clienți.

Însă acum customersList nu mai este o listă fixă, deci după ce îi vom adăuga si pe următorii cinci, algoritmul nostru va "reacționa" la această acțiune și va mai adăuga încă trei persoane (Phil, Anastacia și Aurora) în topSpendersList:

customers.Add(new Customer("Phil", 1100));
customers.Add(new Customer("Beatrice", 660));
customers.Add(new Customer("Anastacia", 3550));
customers.Add(new Customer("Aurora", 20000));
customers.Add(new Customer("Mark", 165));

Putem spune deci că programarea reactivă este un model (paradigmă) de programare declarativă care se concentrează pe fluxurile de date și propagarea schimbării.

Sintaxa nouă, ce folosește cuvintele cheie Observable și Subscribe, va fi accesibilă, după ce adăugăm pachetul NuGet numit System.Reactive

IObservable si IObserver

Ca să înțelegem mai bine exemplul de cod reactiv, vom încerca o minină detaliere a celor două interfețe, a căror definiție se află in .NET Framework încă de la versiunea 4.0. Implementarea lor, însă, nu este în framework, ci o regăsim în librăria System.Reactive

public interface IObserver
{
  void OnNext(T value);
  void OnError(Exception error);
  void OnCompleted();
}

public interface IObservable
{
  
  IDisposable Subscribe(IObserver observer);
}

Cel mai ușor de explicat este atunci când facem o analogie cu o situație din viața reală, precum am făcut cu magazinul online, așa că vom face și aici la fel. Să privim IObservable ca o bandă rulantă, care primește produse într-un capăt și le transportă până în celălalt capăt. Produsele pot fi de diferite mărimi și culori și pot ajunge pe bandă la diferite intervale de timp între ele. Dacă banda rulantă merge și nu este nimeni la capătul celălalt, ele vor cădea și vor fi pierdute.

Ca să nu se întâmple acest lucru, avem nevoie de un operator uman care să preia acele pachete. Cu alte cuvinte, el se "abonează" (Subscribe) la fluxul de pachete de pe banda rulantă. Operatorul (sau observer-ul) implementează interfața IObserver, ce conține trei metode: OnNext, OnError și OnCompleted.

Observable-ul (banda rulantă), din momentul în care are un subscriber, va ști să apeleze aceste metode astfel:

Observăm că este o legătură strânsă între IObservable si IObserver. Mai mult de atât, atunci când apelăm Subscribe, IObservable ne va returna un IDisposable, lucru care este foarte util, deoarece atunci când nu mai avem nevoie de această abonare (subscription), pur și simplu îi dăm Dispose(). Librăria System.Reactive vine cu o altă clasă ajutătoare, numită CompositeDisposable, în care putem adăuga fiecare abonare în parte folosind metoda Add(), iar la final, apelând Clear() pe instanța CompositeDisposable, eliminăm tot ce ține de abonări, pentru un management al memoriei cât mai bun.

Dacă ne întoarcem acum la algoritmul cu programarea reactivă, ne vom da seama că are mai mult sens. Pentru simplitate, în corpul metodei Subscribe am scris doar o expresie lambda care gestionează doar cazurile în care avem clienți noi (OnNext), nu și cazul în care apare o eroare (OnError). De asemenea, nici nu este gestionat situația în care Observable-ul apelează OnCompleted atunci când nu mai sunt elemente noi.

Extensiile reactive sunt disponibile și pentru alte limbaje de programare, nu numai C#:

Site-ul oficial este http://reactivex.io.

Concluzii

În rândurile de mai sus, am realizat o scurtă introducere în programarea reactivă. Extensiile Reactive ne pot ajuta să scriem algoritmi ușor de înțeles, scalabili, oferindu-ne o mână de ajutor în ceea ce privește managementul memoriei (prin intermediul CompositeDisposable). Nu în ultimul rând ne pot fi de un real ajutor referitor la programarea asincronă. Exemplele prezentate reprezintă baza acestui mod de a gândi reactiv.

Extensiile reactive ne mai pun la îndemână o serie de operatori foarte utili, a căror definiție o voi da pe scurt:

Linkuri utile:

VIDEO: NUMĂRULUI 126

Sponsori

  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • Connatix
  • BoatyardX
  • AboutYou
  • Telenav
  • .msg systems
  • Colors in projects