Nadeszła pora na cykl publikacji: „Historia pewnej refaktoryzacji”. Część 13.
Przygotowane w poprzedniej części testy uwidoczniły pewne ułomności zaimplementowanych klas – chcąc przetestować funkcjonalność powiadamiania o postępie przetwarzania pliku, konieczne było wykonanie samego importu. To nasuwa wniosek, że powiadamianie o postępie zależy od samego procesu importu. Co więcej, w jednym z testów tej funkcjonalności nie udało się początkowo uzyskać pozytywnego wyniku. To nasuwa kolejny wniosek – powiadamianie o postępie zależy od konkretnego typu importu. To już bardzo daleko idąca zależność. Koniec końców fakt, że trzeba dokonać rzeczywistego importu, zamiast użyć jego atrapy, sygnalizuje, że wybrane obecnie rozwiązanie – wykorzystanie wzorca metody szablonowej – samo w sobie nie jest dobrym rozwiązaniem. Dlaczego? Powiedzmy, że przyszłoby nam zaimplementować kolejny import. Z czym to się wiąże? Trzeba by utworzyć kolejną klasę potomną, która realizowałaby ów import, ale od innych klas potomnych różniła się jedynie … samą interpretacją odczytanej porcji danych. To jednoznacznie wskazuje na krystalizowanie się kolejnej funkcjonalności – interpretacji danych, obecnie odpowiada za to dedykowana klasa potomna wzorca metody szablonowej. Odpowiada? Hm, czyli mamy jakąś konkretną odpowiedzialność – skoro tak – należy ja zamknąć w klasie.
Przed nami zatem dwa zadania: doprowadzenie do uniezależnienia powiadamiania o postępie od samego procesu importu oraz wbudowanie tego procesu w specjalizowaną klasę.
W tym momencie pozwolę sobie na wskazanie kolejnej zalety testów – problem z ich przygotowaniem wskazuje na niedoskonałość zaimplementowanego rozwiązania – ono powinno dać się dowolnie komponować, być elastyczne, a skoro utrudnia test – takie nie jest. To bardzo ważne, należy zauważyć, że jeszcze przed rozpoczęciem używania klasy w środowisku produkcyjnym zyskujemy informację, że sprawia ona problemy. Jest to informacja nieoceniona – gdyby zdarzyło się to już podczas implementowania funkcjonalności zależnych od klasy, konsekwencją byłoby wstrzymanie tych prac i konieczność poprawy klasy. Sprzężenie zwrotne, jakie dają testy jest więc nieocenione – efektem jego działania będzie dostarczenie rozwiązania, które będzie bliskie doskonałości.
Wracając do meritum. W jaki sposób uniezależnić powiadamianie o postępie od samego procesu importu? Całość tego procesu musi realizować metoda szablonowa, żadna z jego części nie może być implementowana w klasach potomnych. Czy jest to możliwe? Tak – zwiększanie ilości przetworzonych danych musi następować w klasie bazowej. To implikuje, że musi ona posiadać informację o rozmiarze odczytanej porcji danych. Co z kolei implikuje, że klasy potomne muszą raportować rozmiar owej porcji. Zatem znana nam już klasa abstrakcyjna implementująca wzorzec metody szablonowej przyjmie następującą postać:
abstract public class FileOfValuesReader
{
#region konstruktory
public FileOfValuesReader(string fileName, Transfer transfer, IProgressNotifier progressNotifier)
{
this.fileName = fileName;
if (progressNotifier == null)
throw new FileOfValuesReaderException("Nie sprecyzowano klasy powiadamiającej o postępie przetwarzania");
else
this.progressNotifier = progressNotifier;
if (transfer == null)
throw new FileOfValuesReaderException("Nie sprecyzowano metody przekazującej dane");
else
this.transfer = transfer;
}
#endregion
#region właściwości
abstract protected int Size { get; }
abstract protected int Readed { get; }
abstract protected bool EndOf { get; }
protected string[] Values { get; set; }
#endregion
#region metody
abstract protected void Open();
abstract protected void Read();
abstract protected void Extract();
abstract protected void Close();
public void Process()
{
Open();
InitializeProgress();
while (!EndOf)
{
Read();
Extract();
Transfer();
RefreshProgress();
}
}
private void Transfer()
{
transfer(Values);
}
private void InitializeProgress()
{
completed = 0;
progressNotifier.Expected = Size;
progressNotifier.Completed = completed;
}
private void RefreshProgress()
{
if (EndOf)
return;
completed += Readed;
progressNotifier.Completed = completed;
}
#endregion
#region pola
protected string fileName;
private IProgressNotifier progressNotifier;
private Transfer transfer;
private int completed;
#endregion
}
Tym samym zrealizowaliśmy to co było zamierzone – klasa abstrakcyjna realizuje powiadamianie o postępie przetwarzania, klasy potomne muszą jedynie zwracać w dedykowanej właściwości (Readed) rozmiar odczytanej porcji danych. Wyodrębnienie interpretacji danych do oddzielnej klasy zrealizujemy w kolejnej części cyklu.