Nadeszła pora, by doprowadzić do współpracy klas odczytujących pliki i klas wyodrębniających z nich dane. Aby ta współpraca była jednak w ogóle możliwa – klasa FileOfValuesReader musi zostać zmodyfikowana, albowiem musi być możliwe „wstrzyknięcie” do niej obiektu implementującego interfejs IValuesExtractor. W rezultacie konstruktor zyska jeszcze jeden, dodatkowy parametr, poprzez który będzie można przekazać odpowiedni interpreter danych.

Ponieważ ów interpreter trafia od razu do klasy bazowej i to on dokonuje wyodrębnienia wartości, to nie ma sensu, aby metoda Extract() nadal była abstrakcyjna i implementowana w klasach potomnych. Obecnie może być metodą tej klasy i delegować swoje zadanie do metody Process() interfejsu IValuesExtractor. Przestanie też być potrzebna właściwość Values – albowiem jej zadanie przejmuje także ten interfejs. A w związku z tym zmieni się również wnętrze metody Transfer(). Niezmienione pozostaną jedynie metody: Open(), Read() i Close(), wykorzystywane w metodzie Process(), które nadal będą abstrakcyjne.

Zaraz, zaraz! Wykorzystywane? Czemu zatem nie ma w metodzie Process() metody Close()? W tym celu trzeba się cofnąć aż do części 10-tej, gdzie powstał plan działania metody Process(). Został on oparty na metodzie ImportCSV(), która jednak używała konstrukcji using, a ta z kolei nie wymaga zamykania pliku, gdyż robi się ono automatycznie – przy zwalnianiu zasobów. Dlatego Close() – choć jak widać pojawiło się jako metoda abstrakcyjna (zdawałem sobie sprawę, że bez using zamykanie będzie niezbędne), nie zostało w ogóle użyte. Trzeba to oczywiście naprawić, ale trzeba też zabezpieczyć się przed tego typu błędem na przyszłość. Dlatego przygotowując kolejne testy, dodatkowo utworzymy także taki, który będzie sprawdzał, czy wszystkie kluczowe metody klasy bazowej są używane.

Skoro już zabieramy się za metodę Process(), to może warto przyjrzeć się jej w kontekście przeprowadzanych przez nią działań i ogólnej koncepcji? Osobiście nie bardzo pasuje mi nazwa właściwości EndOf. Właściwie nie pasuje mi ta właściwość w ogóle, bo prawdę powiedziawszy dubluje w pewnym stopniu właściwość Readed. Tak naprawdę wystarczyłoby w sumie sprawdzać, czy po odczycie są jakieś dane (Readed > 0) i uzyskamy ten sam efekt, jaki daje nam właściwość EndOf. Co prawda podczas startu pętli ilość odczytanych danych jest zerowa, ale można przebudować tę pętlę na nieskończoną, zaś przed jej uruchomieniem sprawdzać dodatkowo czy rozmiar odczytanego pliku (co komunikuje nam właściwość Size) jest większy od zera.

Zauważmy jeszcze, że w obecnej postaci metody – we wnętrzu pętli, część rozkazów wykona się choć nie powinno. Jeśli bowiem w wyniku odczytu nie zostaną zwrócone żadne dane (Readed = 0), wówczas i tak nastąpi próba wyodrębnienia z nich wartości, a także przekazanie tychże wartości dalej i powiadomienie o postępie. To są kompletnie niepotrzebne działania. Zatem – metoda Process() do poprawki, zaś właściwość EndOf do usunięcia, co da klasę FileOfValuesReader w następującej formie:

abstract public class FileOfValuesReader
{
	#region konstruktory
	public FileOfValuesReader(string fileName, IValuesExtractor valuesExtractor, Transfer transfer, IProgressNotifier progressNotifier)
	{
		this.fileName = fileName;
		if (progressNotifier == null)
			throw new FileOfValuesReaderException(this.GetType() +
				": brak obiektu rejestrującego postęp przetwarzania.");
		else
			this.progressNotifier = progressNotifier;
		if (transfer == null)
			throw new FileOfValuesReaderException(this.GetType() +
				": brak metody przekazującej dane.");
		else
			this.transfer = transfer;
		if (valuesExtractor == null)
			throw new FileOfValuesReaderException(this.GetType() +
				": brak obiektu wyodrębniającego dane.");
		else
			this.valuesExtractor = valuesExtractor;
	}
	#endregion
	#region właściwości
	abstract protected int Size { get; }
	abstract protected int Readed { get; }
	#endregion
	#region metody
	abstract protected void Open();
	abstract protected void Read();
	abstract protected void Close();
	public void Process()
	{
		Open();
		if (Size > 0)
		{
			InitializeProgress();
			while (true)
			{
				Read();
				if (Readed == 0)
					break;
				Extract();
				Transfer();
				RefreshProgress();
			}
		}
		Close();
	}
	private void Transfer()
	{
		transfer(valuesExtractor.Values);
	}
	private void InitializeProgress()
	{
		completed = 0;
		progressNotifier.Expected = Size;
		progressNotifier.Completed = completed;
	}
	private void RefreshProgress()
	{
		completed += Readed;
		progressNotifier.Completed = completed;
	}
	private void Extract()
	{
		valuesExtractor.Process();
	}
	#endregion
	#region pola
	protected string fileName;
	private IValuesExtractor valuesExtractor;
	private IProgressNotifier progressNotifier;
	private Transfer transfer;
	private int completed;
	#endregion
}

Ponieważ konstruktor zyskał dodatkowy parametr, także on musi podlegać kontroli i jeśli będzie negatywna – zgłaszać odpowiedni wyjątek. Dodanie trzeciego wyjątku do konstruktora skłania do zmiany samej treści wyjątków na bardziej komunikatywne. Jak widać wyżej – zgodnie z wcześniejszymi spostrzeżeniami, metoda Process() zyskała nowy kształt, a sama klasa FileOfValuesReader zaczęła używać interfejsu IValuesExtractor.

W obecnej postaci klasa FileOfValuesReader współpracuje z klasami wyodrębniającymi dane. Konieczne jest jeszcze przygotowanie jej klas potomnych, które będą realizowały import konkretnych typów plików. Będą to jedynie dwie klasy: odczytująca dane tekstowe i odczytująca dane binarne. To one będą także odpowiedzialne za przekazywanie odczytanych danych do klas wyodrębniających wartości, aby powyższa klasa bazowa mogła już za pomocą interfejsu IValuesExtractor pozyskać owe wartości na swoje potrzeby.

Budową tych klas zajmiemy się w kolejnej części.

Zanim jednak zakończę ten wpis, kierowany przeczuciem i zauważonymi w tym wpisie niedociągnięciami w klasie FileOfValuesReader, przyjrzę się jeszcze klasom wyodrębniającym wartości, aby upewnić się, że i w nich nie ukrywa się jakieś niedociągnięcie. W rzeczy samej – w klasie String20AndIntValuesExtractor, metoda Process(), druga wartość niepotrzebnie konwertowana jest do tekstu. Miała być przekazywana w oryginalnej postaci. Należy to zatem poprawić:

public override void Process()
{
	List<AnyValue> items = new List<AnyValue>();
	items.Add(new AnyValue(encoding.GetString(Content, 0, 20).Trim()));
	int value = BitConverter.ToInt32(Content, 20);
	items.Add(new AnyValue(value));
	values = items.ToArray();
}

Teraz spokojnie można zakończyć ten wpis i przygotować mentalnie do zadań czekających w kolejnym ;).