Пишіть чистий код з Реактивними Розширеннями (Reactive Extensions)

Якщо у вас є якийсь процес, який може виконуватися довго і повертати декілька проміжних результатів з плином часу, то Реактивні Розширення (.NET Framework Reactive Extensions) дозволять вам спростити код і краще керувати ним.
чистий код з реактивними розширеннями

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

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

В документації Реактивні Розширення описуються як спосіб обробки потоку даних. При роботі з Розширеннями не важко собі уявити який-небудь процес, який перебирає дані в колекції, періодично шукає щось цікаве і відправляє в додаток те, що знайшов, — це змушує додаток реагувати (to react), звідси і назва «Реактивні» (reactive)). У нашому прикладі ми будемо вважати, що ми хочемо щось зробити поки наше додаток виконує ряд змін в замовленні на продаж. Для цього ми повинні написати метод StatusChanged і викликати його кожен раз, коли в додатку відбувається зміна замовлення. Або ми можемо додати подію StatusChanged у наш клас SalesOrder і викликати його при кожній зміні стану — ми можемо просто підключити наш код до цієї події.

Рішення цієї задачі за допомогою Реактивних Розширень, на мій погляд, не тільки простіше, ніж запропоновані вище, але і — спасибі за їх інтеграцію з LINQ — більш гнучке. Безумовно, коли справа доходить до підключення або відключення джерела інформації, рішення з використанням Реактивних Розширень більш просте, ніж аналогічний код з використанням подій.

Установка Реактивних Розширень
Для початку необхідно підключити Реактивні Розширення до проекту з допомогою NuGet. Є кілька пакетів Реактивних Розширень, включаючи реалізацію JavaScript, Android і обробку LINQ-запитів до веб-сервісів. Щоб знайти потрібний пакет, досить запустити пошук по фразі «Reactive Extensions» і додати головну бібліотеку (Main Library package) до проекту.

На другому кроці потрібно вирішити, які дані ми хочемо повертати при кожному зміні замовлення. Разом з поточним станом замовлення має сенс також повертати і ідентифікатор замовлення. Зробимо для цього клас з необхідними властивостями:
public class StatusChange
{
public int OrderId { get; set; }
public string OrderStatus { get; set; }
}

На наступному кроці визначимо Суб'єкт Реактивних Розширень (Reactive Extensions Subject), який буде працювати з цим типом, проинициализируем його при запуску додатку та підпишемося, з допомогою методу Subscribe, на виклики методів Суб'єкта, щоб бути в курсі змін, що відбулися:
ISubject<StatusChange> statChange = new Subject<StatusChange>();
statChange.Subscribe(sc => MessageBox.Show(sc.OrderStatus));

В метод Subscribe ми передаємо лямбда-вираз, що вказує, що ми хочемо зробити, коли відбулися якісь зміни в замовленні. У нашому прикладі ми просто показуємо значення властивості OrderStatus нашого класу.

Тепер скрізь у нашому додатку коли ми змінюємо статус замовлення ми будемо створювати екземпляр класу StatusChange, заповнювати його властивості і викликати метод OnNext створеного Суб'єкта. Типовий код повідомлення про первинному створенні замовлення може виглядати так:
statChange.OnNext(new StatusChange() { OrderId = 1, OrderStatus = "New" });

При кожному виклику методу OnNext буде показано повідомлення зі значенням властивості StatusChange.OrderStatus, що ми і визначили в лямбда-виразу.

Розширення рішення
Звичайно, в реальному проекті обробка зміни статусу може зажадати більше одного рядка коду, навіть більше, ніж ми захочемо вмістити в лямбда-виразу. Замість лямбда-виразу ми завжди можемо метод Subscribe передати вказівник на інший метод, наприклад так:
statChange.Subscribe(StatusChanged);

Метод може не приймати ніякі параметри, але якщо він приймає як параметр об'єкт того ж типу, з яким пов'язаний Суб'єкт, тоді йому буде переданий об'єкт, вказаний при виклику OnNext. Такий метод, наприклад, може просто виводити кожне нове стан в консоль:
public static void StatusChanged(StatusChange status) {
Console.WriteLine(status.OrderStatus);
}

Якщо нам необхідно виконати кілька різних методів при зміні статусу, то немає необхідності упаковувати їх в один метод. Замість цього ми можемо кілька разів викликати Subscribe нашого Суб'єкта, передавши необхідні методи по черзі.
statChange.Subscribe(StatusChanged);
statChange.Subscribe(StatusAudit);

З таким підходом ми маємо слабке зв'язування процесу, який вносить зміни в замовлення (наше додаток), з процесами, які реагують на ці зміни (методи StatusChanged, StatusAudit). Тільки одна річ пов'язує ці процеси — визначення класу StatusChange , який ми можемо безпечно розширити додатковими властивостями, не поламавши інші процеси. Досі, це майже не відрізнялося від використання подій, хіба що коду потрібно написати трохи менше.

Але застосування Реактивних Розширень не тільки спрощує наш код, воно дозволяє нам піднятися над подіями. Для початку припустимо, ми не хочемо обробляти кожну зміну статусу замовлення. Наприклад, ми хочемо відловити тільки ті замовлення, у яких стан змінено на «В процесі». Ми можемо використовувати LINQ для уточнення, які результати ми хочемо отримувати від Суб'єкта. Перед тим, як випробувати це, ми повинні додати namespace System.Reactive.LINQ в наш код.

Після підключення цього простору імен, ми побачимо, що можемо писати LINQ-вираження або використовувати LINQ-методи-розширення для вибору, які результати ми хочемо отримувати та обробляти. Всі три наведені нижче приклади показують, що наш метод буде викликаний тільки для змін зі статусом «Processing»:
statChange.Where(c => c.OrderStatus == "Processing").Subscribe(ReportStatusChange);

var scs = from sc in statChange
where sc.OrderStatus == "Processing"
select sc;
scs.Subscribe(ReportStatusChange);

var sub = (from sc in statChange
where sc.OrderStatus == "Processing"
select sc).Subscribe(ReportStatusChange);

Також ми можемо захотіти мати спеціальні обробники при зміні статусу на помилковий або при здійсненні замовлення. Ми цілком могли б визначити це за станом замовлення, але Реактивні Розширення надають рішення краще: методи Суб'єкта OnError OnCompleted. Коли ми викликаємо метод Суб'єкта Subscribe, ми можемо передати параметрами покажчики на методи (або лямбда-вирази), які повинні бути виконані при виклику методів Суб'єкта OnError OnCompleted. У прикладі нижче змінені імена методів, щоб зробити код більш наочним:
statChange.Subscribe(OnNext, OnError, OnCompleted);

Метод OnError повинен приймати у вигляді параметра виняток, а метод OnCompleted повинен бути без параметрів. Типовий приклад може бути таким:
public static void OnError(Exception ex) {
Console.Error.WriteLine(ex.Message);
}
public static void OnCompleted() {
Console.WriteLine("order processing completed");
}

Тепер, у випадку, якщо щось пішло не так, наше додаток має викликати метод Суб'єкта OnError. При виклику цього методу необхідно передати параметром виключення, яке містить інформацію про проблему (в реальному проекті буде щось краще, ніж приклад нижче):
statChange.OnError(new Exception("Something has gone horribly wrong!"));

Коли додаток закінчує роботу із замовленням, воно повинно викликати метод Суб'єкта OnCompleted. Додатково до викликом обробників зміни цього стану, цей метод також інструктує Суб'єкт, що він не повинен посилати більше ніякі повідомлення (це, до речі, ще одна річ, яку можна зробити з подіями — відключити передплатників на стороні події). Також Суб'єкт може звільнити себе від усіх слухачів викликом методу Dispose.

Інкапсуляція повідомлень
У нашому додатку залишилася одна проблема — в кожному місці, де ми змінюємо статус замовлення, ми повинні не забути викликати метод OnNext Суб'єкта. Добре було б це автоматизувати. В ідеалі ми можемо сховати цей виклик всередину сетера властивості Status класу замовлення. Це виключить та дублювання коду і можливість забути викликати метод OnNext.

У лістингу коду нижче клас SalesOrder містить властивість типу ISubject, яка ініціалізується в конструкторі примірником Суб'єкта. Тепер метод OnNext Суб'єкта буде викликаний скрізь, де відбувається зміна властивості Status (у цей же клас можна додати додатковий код, щоб підтримати також виклик методів Суб'єкта OnError OnCompleted)
public class SalesOrder
{
string _status;
public ISubject<StatusChange> StatChange { get; private set; }

public int Id { get; set; }

public string Status
{
get { return _status; }
set
{
_status = value;
var sc = new StatusChange() { OrderId = this.Id OrderStatus = this.Status };
StatChange.OnNext(sc);
}
}

public SalesOrder()
{
StatChange = new Subject<StatusChange>();
}
}


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.