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.