Nadeszła pora na cykl publikacji: „Historia pewnej refaktoryzacji”. Część 18.
W dotychczasowym procesie refaktoryzacji udało nam się stworzyć elastyczny mechanizm importu – uzyskaliśmy tym samym podstawową funkcjonalność w zadowalającej postaci. Pora zająć się pozostałymi operacjami, które są niezbędne, aby uzyskać kompletną, pierwotną funkcjonalność refaktoryzowanego kodu. Na implementację oczekują przecież: zdefiniowany na początku interfejs IFileSelector i jego pochodna IFilteredFileSelector. Konieczne jest też zaimplementowanie metody Transfer() – do tej pory była ona implementowana jedynie na potrzeby automatycznych testów, jako sposób na potwierdzenie ich poprawności. Należy także pamiętać o – występującym w pierwotnym kodzie – warunku sprawdzającym czy import przebiegł poprawnie i w przypadku gdy nie miało to miejsca – poinformowaniu o tym fakcie użytkownika.
Na pierwszy ogień pójdą interfejsy IFileSelector i IFilteredFileSelector, przypomnijmy sobie ich definicję:
public interface IFileSelector { void Select(); bool Selected { get; } string FileName { get; } } public interface IFilteredFileSelector : IFileSelector { string Filter { get; set; } string FilterDescription { get; set; } }
Z punktu widzenia dotychczasowej funkcjonalności zaimplementować należy jedynie drugi z interfejsów, ponieważ użyty pierwotnie OpenFileDialog umożliwia filtrowanie plików i możliwość ta jest skrupulatnie wykorzystywana. Natomiast – jak już poprzednio wspominałem – pierwszy z interfejsów posłuży jedynie do celów korzystania z wyboru pliku. Oto zatem implementacja interfejsu IFilteredFileSelector:
public class WinFormsFilteredFileSelector : IFilteredFileSelector { #region konstruktory public WinFormsFilteredFileSelector(OpenFileDialog openFileDialog, string filter = "", string filterDescription = "") { this.openFileDialog = openFileDialog; Filter = filter; FilterDescription = filterDescription; } #endregion #region właściwości public bool Selected { get { return (FileName != ""); } } public string FileName { get; private set; } public string Filter { get; set; } public string FilterDescription { get; set; } #endregion #region metody public void Select() { if (string.IsNullOrEmpty(Filter) || string.IsNullOrEmpty(FilterDescription)) openFileDialog.Filter = ""; else openFileDialog.Filter = FilterDescription + " (" + Filter + ")|" + Filter; bool success = (openFileDialog.ShowDialog() == DialogResult.OK); if (success) FileName = openFileDialog.FileName; else FileName = ""; } #endregion #region pola private OpenFileDialog openFileDialog; #endregion }
Jak widać w konstruktorze podawany jest rzeczywisty komponent WinForms, z którego klasa implementująca będzie korzystać (tzw. delegacja zadań), dodatkowo umożliwiono początkowe ustalenie wartości i opisu filtra. Zbudowanie filtra wg konwencji oczekiwanej przez klasę OpenFileDialog następuje już w metodzie Select(). Stwierdzenie, że plik wybrano (właściwość Selected) następuje poprzez sprawdzenie czy nazwa pliku jest ustalona.
Pora teraz zająć się metodą Transfer(). Przyjąłem, że wydzielę ją do dedykowanej klasy o nazwie DataWarehouse. Klasa ta będzie też zawierać metodę Store(), za pomocą której w pierwotnym kodzie zapisywano zaimportowany i przetransformowany do XML-a plik. Przyjrzyjmy się jak będzie wyglądać tak klasa:
public class DataWarehouse { #region właściwości public bool Empty { get { return (data == ""); } } #endregion #region metody public void Transfer(params AnyValue[] items) { if (items.Length < 2) return; data += "<r s=\"" + items[0].ToString() + "\" q=\"" + items[1].ToString() + "\"/>" } public void Clear() { data = ""; } public void Store() { // ... sposób składowania danych nie jest istotny dla zagadnienia refaktoryzacji ... } #endregion #region pola private string data; #endregion }
Ponieważ w naszych rozważaniach o refaktoryzacji nie jest istotny sposób zachowywania zaimportowanych danych, zatem treść metody Store() nie została zaprezentowana. Należy przyjąć, że jakoś będzie ona owe dane zapisywać. Ale z pewnością musi wykonywać ten kod:
string xml = "<data>" + data + "</data>";
Jak widać dodatkowo zaimplementowałem metodę Clear(), która pozwoli wyczyścić dane uzyskane przez metodę Transfer(), tak aby ponowne użycie klasy – w przypadku gdy ta metoda nie zostanie w ogóle wywołana – nie powodowało zachowania danych z poprzedniego importu (zakładając, że klasa DataWarehouse zostanie utworzona raz, po czym będzie wielokrotnie wykorzystywana).
Oprócz dodatkowej metody, jest też właściwość, pozwalająca stwierdzić czy klasa zawiera jakiekolwiek dane. Będzie to przydatne do sprawdzenia czy w ogóle należy dane zachować (no bo skoro ich nie ma…).
Po zaimplementowaniu powyższych klas osiągnęliśmy praktycznie finisz refaktoryzacji. Pozostało jedynie powrócić do wzorca Obiektu Metody i dostosować jego treść do obecnie obowiązującego kodu. Tym zajmę się w następnej, przedostatniej części.