Observable
O situație des întâlnită în programare este atunci când dorim să urmărim progresul unei computații sau să emitem evenimente după finalizarea acesteia, care să declanșeze alte acțiuni.
Un exemplu poate fi atunci când dorim să preluăm în mod asincron date de pe un server și să le afișăm într-o interfață web. Mai întâi, inițializăm apelul asincron către server, pentru a nu bloca firul de execuție care randează pagina, iar atunci când datele sosesc, actualizăm interfața.
În această situație, dorim să avem una sau mai multe componente cu rol de observator (observer) și una cu rol de observabil (observable).
Exemplul de mai jos este unul simplu pentru un observabil. O clasă concretă poate să-l moștenească, iar alte componente pot să-și aboneze acțiunile la acesta.
// Clasa este abstracta si trebuie mostenita, de asemena putem pune si
// un la parametru de genericitate pentru a sti ce fel de date proceseaza observabilul
public abstract class Observable<TInput>
{
// Tinem o lista cu actiunile ce trebuie sa se execute la o computatie,
// tipul Action<TInput> este o functie care ia un parametru de tip TInput si nu returneaza nimic
private readonly List<Action<TInput>> _actions = [];
// Observatori pot sa-si inregistreze apeluri (callbacks) la metode proprii pentru a lua actiuni in cazul unei actualizari
public void Subscribe(Action<TInput> onAction)
{
_actions.Add(onAction);
}
// Aceasta e metoda ce trebuie apelata atunci cand actualizam date in clasa care mosteneste
// astfel incat sa fie apelate a
public void OnAction(TInput input)
{
foreach (var action in _actions)
{
action(input);
}
}
}
De asemenea, un observabil poate fi util deoarece putem adăuga computații care nu sunt suportate implicit, extinzându-i astfel funcționalitatea.
Exercițiu - Observable
Completați codul de mai jos astfel încât să utilizați obiectul observabil și să înregistrați observatorul la acesta pentru a capta datele introduse de la tastatură.
public class InputLogObservable : Observable<string>
{
public void Log(string value)
{
// Putem aici sa facem si alte computatii daca e nevoie
OnAction(value);
}
}
public class InputLogger
{
public string Log { get; private set; } = "";
public void AddLog(string log)
{
Log += $"{DateTime.UtcNow}: {log}\r\n";
}
}
static class Program
{
static async Task Main(string[] args)
{
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
// TODO: Creati obiectele pentru observabil si observator, apoi abonati observatorul
var read = Task.Run(() =>
{
while (!cancellationToken.IsCancellationRequested)
{
var line = Console.ReadLine();
if (line == "exit")
{
cancellationTokenSource.Cancel();
}
else
{
// TODO: Trimiteti linia citita catre obsevabil
}
}
}, CancellationToken.None);
await read;
// TODO: Printati continutul din observator
}
}