I'm currently refactoring an application which periodically downloads content from various sources (http, ftp, sql, etc). There is a schedule which controls the times during which the application can be active. For example, it can download content between 8AM and 16PM. The application has been written in an object oriented language (C#).
This is the general design I came up with in my first iteration:
The Scheduler class will be responsible for keeping to the general schedule. It starts the downloading at the start of the scheduled period, and stops it at the end. The Scheduler contains a number of ITask implementations, each of which have their own bit of downloading to do. I've created an abstract base class implementation which periodically calls the protected abstract method "StartDownload". Subclasses of Task will only implement this method, and won't have to worry about timing and scheduling.
So far, so good. But while TDD'ing the Task baseclass, I realised that it was actually difficult to mock the behaviour of StartDownload. When the Task's timer ticks, it should only call StartDownload if it has finished the previous download iteration. But since these are implementation details, it's hard to mock.
This made me wonder if the Task class isn't actually violating the Single Responsibility Principle. After all, it's taking care of the periodical invocation of StartDownload. And the StartDownload method is responsible for the actual downloading. So I came up with a more separated design:
Here the responsibility of the Task class is limited to just periodically calling the Client. And the Client's only responsibility is downloading content. Testing the Task class will now be easier, because I can just inject an IDownloadClient mock. The Task and Download have a 1 to 1 relationship. So each Task performs one download, so to speak. In practice, all Task instances will be added to a single Scheduler instance and started/stopped from there.
I do wonder if this is actually a clearer design though... The Task class now seems a bit odd, being just a single implementation without any subclasses. What do you guys think?