Nadeszła pora na cykl publikacji: „Historia pewnej refaktoryzacji”. Część 19.
Ostatnią z czynności refaktoryzacyjnych będzie przystosowanie dotychczasowej klasy MethodObject do używania powstałych w trakcie refaktoryzacji klas. Jak można sprawdzić w części drugiej, klasa ta posiadała cztery publiczne metody: ImportCSV(), ImportTabSeparated(), ImportFixed(), ImportBinary(). Metody te były następnie wykorzystywane przez interfejs użytkownika do wykonywania odpowiednich importów. Te metody muszą zostać zachowane, zmieni się natomiast ich implementacja. Zmianie ulegnie też sygnatura konstruktora oraz – co za tym idzie – jego implementacja. Koniec końców na bardziej komunikatywną zostanie też zmieniona nazwa klasy: z MethodObject na Imports. Oto jak to będzie wyglądało:
public class Imports // Method Object { #region konstruktory public Imports(IProgressNotifier progressNotifier, OpenFileDialog openFileDialog, DataWarehouse dataWarehouse) { if (openFileDialog == null) throw new Exception(this.GetType() + " - brak obiektu klasy: OpenFileDialog."); else filteredFileSelector = new WinFormsFilteredFileSelector(openFileDialog); if (progressNotifier == null) throw new Exception(this.GetType() + ": brak obiektu rejestrującego postęp przetwarzania."); else this.progressNotifier = progressNotifier; this.dataWarehouse = dataWarehouse; } #endregion #region metody public void ImportCSV() { IFileSelector selector = SelectFile("*.csv", "Wartości separowane przecinkiem"); if (selector.Selected) Import(new TextFileOfValuesReader(selector.FileName, new SeparatedTextValuesExtractor(','), dataWarehouse.Transfer, progressNotifier)); } public void ImportTabSeparated() { IFileSelector selector = SelectFile("*.txt", "Wartości separowane tabulacją"); if (selector.Selected) Import(new TextFileOfValuesReader(selector.FileName, new SeparatedTextValuesExtractor('\t'), dataWarehouse.Transfer, progressNotifier)); } public void ImportFixed() { IFileSelector selector = SelectFile("*.fix", "Wartości o stałej długości"); if (selector.Selected) Import(new TextFileOfValuesReader(selector.FileName, new FixedTextValuesExtractor(20, 6), dataWarehouse.Transfer, progressNotifier)); } public void ImportBinary() { IFileSelector selector = SelectFile("*.dmp", "Wartości z obrazu pamięci"); if (selector.Selected) Import(new BinaryFileOfValuesReader(selector.FileName, new String20AndIntValuesExtractor(), dataWarehouse.Transfer, progressNotifier)); } private void Import(FileOfValuesReader reader) { dataWarehouse.Clear(); reader.Process(); if (dataWarehouse.Empty) throw new Exception("Nie udało się przetworzyć pliku!"); dataWarehouse.Store(); } private IFileSelector SelectFile(string filter, string description) { filteredFileSelector.Filter = filter; filteredFileSelector.FilterDescription = description; filteredFileSelector.Select(); return filteredFileSelector; } #endregion #region pola private IFilteredFileSelector filteredFileSelector; DataWarehouse dataWarehouse; private IProgressNotifier progressNotifier; #endregion }
Jak widać treść metod publicznych jest do siebie bardzo podobna – różni się jedynie pewnymi parametrami. W każdym przypadku jest to kompozycja kilku obiektów, które zebrane w jeden mechanizm potrafią to, co pierwotnie robił wielokrotnie powielony kod. I – jakby na to nie patrzeć – jest to czytelne. Od razu widać co się dzieje. Co więcej – przez prostą analizę porównawczą poszczególnych metod można domyślić się, jak zrealizować kolejny, nieistniejący jeszcze import. Kod mówi sam za siebie :).
To co widać w metodach publicznych można było także dostrzec już w testach. Tam też kod testu wyjaśniał, jak używać danej klasy, wyjaśniał lepiej, niż najbardziej komunikatywna dokumentacja. Nie bardzo nawet ma sens opisywać, co kod powyższej klasy realizuje – bo wystarczy się z nim zapoznać, aby ten opis stał się zbyteczny.
Użycie powyższej klasy w klasie implementującej interfejs użytkownika, będzie się tylko nieznacznie różnić od tego z części drugiej:
public partial class UI : Form, IProgressNotifier { #region konstruktory public UI() { InitializeComponent(); imports = new Imports(this, openFileDialog, new DataWarehouse()); } #endregion #region właściwości #region IProgressNotifier public int Expected { get { return progressBar.Maximum; } set { progressBar.Maximum = value; } } public int Completed { set { progressBar.Value = value; } } #endregion #endregion #region zdarzenia private void CsvImport_Click(object sender, EventArgs e) { imports.ImportCSV(); } private void TabImport_Click(object sender, EventArgs e) { imports.ImportTabSeparated(); } private void FixImport_Click(object sender, EventArgs e) { imports.ImportFixed(); } private void BinImport_Click(object sender, EventArgs e) { imports.ImportBinary(); } #endregion #region pola Imports imports; #endregion }
Ponieważ klasa Imports korzysta z interfejsu dla powiadamiania o postępie, zatem nic nie stało na przeszkodzie, aby ów interfejs implementowała klasa UI – stąd znalazły się w niej właściwości (nomen omen) właściwe temu interfejsowi. Jej instancja jest zatem przekazywana jako pierwszy parametr konstruktora Imports. Drugi parametr to po prostu obiekt klasy OpenFileDialog (jedyny parametr, który pozostał taki sam od początku refaktoryzacji), ostatni to obiekt odpowiedzialny za zapisanie zaimportowanego pliku (należy go traktować stricte poglądowo, mniejsza o szczegóły jego implementacji).
Co teraz? Czy to już koniec? Jeszcze nie, warto odłożyć sobie kod na kilka dni, np. weekend i wrócić do niego dokonując samemu jego przeglądu i dając go do przeglądu komuś jeszcze. Zakładam, że przegląd kodu przez innych odbywa się na bieżąco ;), ja sam odkładam go na weekend, po którym do niego wrócę i jeśli nic w nim nie wzbudzi niepokoju (tak mojego jak i waszego ), przyjdzie pora na uznanie dotychczasowych prac refaktoryzacyjnych za zakończone. I przyjdzie czas na podsumowanie cyklu.