views:

52

answers:

2

I have a DownloadManager class that manages multiple DownloadItem objects. Each DownloadItem has events like ProgressChanged and DownloadCompleted. Usually you want to use the same event handler for all download items, so it's a bit annoying to have to set the event handlers over and over again for each DownloadItem.

Thus, I need to decide which pattern to use:

  1. Use one DownloadItem as a template and clone it as necessary

        var dm = DownloadManager();
        var di = DownloadItem();
        di.ProgressChanged += new DownloadProgressChangedEventHandler(di_ProgressChanged);
        di.DownloadCompleted += new DownloadProgressChangedEventHandler(di_DownloadCompleted);
        DownloadItem newDi;
        newDi = di.Clone();
        newDi.Uri = "http://google.com";
        dm.Enqueue(newDi);
        newDi = di.Clone();
        newDi.Uri = "http://yahoo.com";
        dm.Enqueue(newDi);
    
  2. Set the event handlers on the DownloadManager instead and have it copy the events over to each DownloadItem that is enqeued.

        var dm = DownloadManager();
        dm.ProgressChanged += new DownloadProgressChangedEventHandler(di_ProgressChanged);
        dm.DownloadCompleted += new DownloadProgressChangedEventHandler(di_DownloadCompleted);
        dm.Enqueue(new DownloadItem("http://google.com"));
        dm.Enqueue(new DownloadItem("http://yahoo.com"));
    
  3. Or use some kind of factory

        var dm = DownloadManager();
        var dif = DownloadItemFactory();
        dif.ProgressChanged += new DownloadProgressChangedEventHandler(di_ProgressChanged);
        dif.DownloadCompleted += new DownloadProgressChangedEventHandler(di_DownloadCompleted);
        dm.Enqueue(dif.Create("http://google.com"));
        dm.Enqueue(dif.Create("http://yahoo.com"));
    

What would you recommend?

+1  A: 

I would say Template method pattern with a factory would be the right approach for this.

Prashant
Can you elaborate on what you mean here, and how you would merge those two methods (1 and 3)?
Mark
+2  A: 

Why are the DownloadItems responsible for reporting progress (from an API design perspective)?

I'd say that the DownloadManager is responsible for downloading DownloadItems, and therefore also for reporting progress. (The internal implementation strategy may, of course, differ.)

I'd go with the second option:

var dm = DownloadManager
{
    "http://google.com",
    new DownloadItem("http://yahoo.com") { Retries = 5 }
};

dm.ProgressChanged += (sender, e) =>
    Console.WriteLine("Download {0}: {1:P}", e.Uri, (double)e.Progress / 100.0);

dm.DownloadCompleted += (sender, e) =>
    Console.WriteLine("Download {0}: completed!", e.Uri);

dm.DownloadAllCompleted += (sender, e) =>
    Console.WriteLine("All downloads completed!");

dm.Add("http://stackoverflow.com");
dm.DownloadAllAsync();

If you happen to have a copy of the Framework Design Guideline (2nd ed.) at hand, have a look at pages 305--312 (Event-based Async Pattern).

dtb
Why shouldn't the download items be responsible for reporting their own progress? I'm designing it such that you *can* use the `DownloadItems` independently of the `DownloadManager` if, for example, you only need to download one thing. The `DownloadManager` is used to queue up downloads and limit the number of parallel downloads. It also has a `ManualResetEvent` to wait for the downloads to complete. I didn't know about that `{ Retries = 5}` syntax, that's super cool! Thanks.
Mark
ImO the API would be much cleaner if a DownloadItem would just hold the information related to downloading the item, and the DownloadManager does the actual downloading. For a single download this adds only 1 or 2 additional lines of code, but has the benefit of a simple, consistent, usable interface. Why let the user learn multiple ways of doing things if there is one way that does them all? :-)
dtb
If you like the object initializer syntax, you might also like the collection initializer syntax. I've changed my answer accordingly.
dtb
Regardless of whether or not `DownloadItem` is usable on its own (low-priority feature), it still makes more sense for them to hold the events. What does "ProgressChanged" mean on the DM? The total progress of all the files? What if you want to know the progress of an individual file, not as a whole? (So that you can display progress bars for each or something). Also, the DIs *are* the ones firing the events, and there's no real way around that. I really do like the syntax you have suggested, but it means that I have the copy the event handlers off the DM and add them to each DI.
Mark
DownloadManager.ProgressChanged would mean that the progress of an individual download in progress changed. There could be an additional event that tracks the overall progress. Individual downloads can be identified by the EventArgs (e.g., by the URL). There should also be a way to associate an `object userState` with each download so the user can put some custom information in a DownloadItem (e.g., a ProgressBar object).
dtb
BTW this advice is based almost 100% on what the Framework Design Guidelines suggest; it's not just based on my random opinion. :-)
dtb
So... what then, I should give all the `DownloadItem` events `internal` accessibility, have the `DM` listen in on them, and then re-fire the event with the appropriate EventArgs? ... Or, the DIs actually do no work at all, and might as well be structs. I was thinking the way I have it saves me some headache having to pass around the DI everywhere ("this" always refers to the current DI rather than having to pass the current DI into each method), and also allows me to do stuff like `di.Start()` or `di.Pause()`.
Mark
This, or pass the DownloadManager instance to the DownloadItem when the download starts and let the DownloadItem call the `protected internal void OnProgressChanged(ProgressChangedEventArgs)` on DownloadManager directly. There are some bits on the Event-based Async Pattern on MSDN; have a look: http://msdn.microsoft.com/en-us/library/wewwczdw.aspx
dtb
Implementing the download method in the DownloadItem is a very good idea, IMO. I just wouldn't expose this in the public interface. So even though the DownloadItem actually does the work, it looks like the DownloadManager is responsible for doing it and the DownloadItem is just holding the necessary information. You could start/pause individual DownloadItems with `dm.Start(di)` and `dm.Pause(di)`.
dtb
That's the "standard" way of doing things eh? It all looks like a lot of bloat to me, but if it saves me headaches down the road and keeps things flexible, then why not... I don't think I like the idea of passing the DM to the DI. The DM already has references to all the DIs, I shouldn't need it both ways. Also, maybe, by keeping the DM out of the DI, we can swap out for a different DM... like ParallelDownloadManager, and SerialDownloadManager. But polymorphism would solve that issue anyway....blah. I think I'm going to listen for the event.
Mark
You're sure this is the best way to go? The `DownloadItem` has a dozen properties that would be nice to be able to access during different events. If I understand correctly, you're supposed to customize this `EventArgs` class and add the "relevant" properties, but that could be a lot of work copying over every property into numerous EventArg classes.
Mark
Oh, this is brutal! I *can't* pass a reference of `DownloadManager` to the `DownloadItem` because the `DownloadManager` signature actually looks like this `public class DownloadManager<TPriority>` (it's generic), thus I'd have to make DownloadItem generic as well so that I can store the proper DM... then if there's a mismatch in `TPriority` it'll explode.
Mark
I don't know if this is the definite "best" way to go: I've never implemented a DownloadManager so far and I know only a fraction of your requirements. But from what you've described, I think a lot of elements of the Event-Based Async Pattern are very applicable here. But, of course, it's up to you :-) My advice is: write code samples for real-world usage scenarios of your component. Compare different ways of doing things and pick the one that requires the least amount of learning, lines of code and surprise. Standard patterns help as users are already familiar with them (less learning), but
dtb
there are other patterns than the Event-based Async Pattern as well. ... If you want to try going down the rabbit hole outlined in my answer, I can help you(\*), although I believe this is a bit out of scope for StackOverflow. Why don't you put a reference to the DownloadItem directly into the EventArgs subclass instead of copying all properties? And if you have different DownloadManagers, have you thought about a non-generic abstract base class? (* In fact, if you manage to find a nice, clean API and also implement resumable/fault tolerant downloads, I'd be quite interested in the outcome :-)
dtb