Nasza droga do Reactive Extensions – cz. 1 – Wstęp

Artykuł w wersji anglojęzycznej jest dostępny na blogu firmy Grape Up

Jeden z produktów jakie ostatnio rozwijaliśmy w naszym zespole, jest aplikacją desktopową opartą o rozbudowaną bibliotekę, która dostarczała spójne API do komunikacji, opartej o różne protokoły związane z telekomunikacją, co sprawiało że generowała naprawdę dużo sygnałów.

Architektura biblioteki

Większość klas w tej bibliotece pasowała do takiego wzorca:

public class Class1
{
public void SetDescription(string description, CompletionHandler handler)
{
/* Some logic here */
}
public void SetSize(int size, CompletionHandler handler)
{
/* Some logic here */
}
public string Description { get; private set; }
public int Size { get; private set; }
public event EventHandler DescriptionChanged;
public event EventHandler SizeChanged;
}
  • Metody SetDescription i SetSize wysyłają wartość przekazaną jako pierwszy parametr i wywołują metodę przekazaną jako Handler gdy serwer potwierdzi albo odrzuci żądanie
  • Właściwości Description i Size zwracają ostatnią znaną wartość
  • Zdarzenia DescriptionChanged i SizeChanged są uruchamiane gdy serwer powiadomi aplikację o zmianie wartości – niezależnie czy zmiana była spowodowana przez wywołanie metody Set* czy przez inne interakcje z serwerem np. działanie drugiej aplikacji

Biblioteka przetwarza wszystkie żądania na swojej własnej wewnętrznej puli wątków co ma swoje zalety i wady. Podstawową zaletą był fakt iż biblioteka, sama z siebie, nie wpływała na działanie wątku głównego aplikacji. Wadą był fakt iż wszystkie callbacki i zdarzenia podnoszone przez bibliotekę były uruchamiane na innym wątku, co wymuszało opracowanie jakiegoś mechanizmu do komunikacji między wątkami.

Ręczne zarządzanie wątkami – Nie chcesz tego robić

Już od początku rozwoju aplikacji zorientowaliśmy się, że ręczna komunikacja między wątkami kończy się powstaniem dużej ilości długiego i powtarzalnego kodu, który wygląda mniej więcej tak:

public class SomeOtherClass : IDisposable
{
private readonly SomeClass _someObject;
private readonly DispatcherService _dispatcherService;
public SomeOtherClass(
SomeClass someObject,
DispatcherService dispatcherService)
{
_someObject = someObject;
_dispatcherService = dispatcherService;
_someObject.DescriptionChanged += SomeObjectOnDescriptionChanged;
_someObject.SizeChanged += SomeObjectOnSizeChanged;
}
private void SomeObjectOnDescriptionChanged(object sender, EventArgs e)
{
_dispatcherService.Invoke(LoadState);
}
private void SomeObjectOnSizeChanged(object sender, EventArgs e)
{
_dispatcherService.Invoke(LoadState);
}
private void LoadState()
{
PropertyVisibleOnUI = $"Description: {_someObject.Description} Size: {_someObject.Size}";
}
public string PropertyVisibleOnUI { get; set; }
public void Dispose()
{
_someObject.DescriptionChanged -= SomeObjectOnDescriptionChanged;
_someObject.SizeChanged -= SomeObjectOnSizeChanged;
}
}

Kod staje się jeszcze bardziej złożony jeżeli założymy, że niektóre właściwości klasy są złożonymi obiektami, które mają właściwości, zdarzenia, metody asynchroniczne. A takich warstw może być znacznie więcej.

API do obsługi wiadomości tekstowych napisane w tej konwencji mogło wyglądać tak:

public interface IConversationService
{
event EventHandler ConverstionStarted;
event EventHandler ConversationEnded; 
IEnumerable Conversations { get; }
}
public interface IConversation
{
event EventHandler MessageAdded;
void SendMessage(string body, CompletionHandler handler); 
IEnumerable Messages { get; }
}
public interface IMessage
{
event EventHandler IsReadChanged;
bool IsMine { get; }
string Body { get; }
bool IsRead { get; }
}

Koniecznie potrzebowaliśmy lepszego rozwiązania. W drugiej części artykułu opiszę jak wzorzec event-aggregator pomógł nam pokonać te problemy.

2 odpowiedzi do “Nasza droga do Reactive Extensions – cz. 1 – Wstęp”

  1. Pingback: dotnetomaniak.pl

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *