I make extensive use of DI, but I wonder, where is the 'granurality' limit, when some set of functionality should be separated to a class - lets take an example:public class DownloadManager : IDownloadManager{    public Uri ResourceIdentifier { get; }    public DownloadManager(Uri uniqueResourceIdentifier)    {        if (!uniqueResourceIdentifier.IsWellFormedOriginalString()) throw new ArgumentException(nameof(uniqueResourceIdentifier));        ResourceIdentifier = uniqueResourceIdentifier;    }    public DownloadManager(string uniqueResourceIdentifier) : this(new Uri(uniqueResourceIdentifier)) { }    public async Task<byte[]> GetBytesAsync(CancellationToken token = new CancellationToken())    {        using (var client = new HttpClient())        using (var response = await client.GetAsync(ResourceIdentifier, token).ConfigureAwait(false))        {            var data = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);            return data;        }    }}It looks for me that this class brings almost no additional benefit, and even hinders some operations: what if we need a stream (ReadAsStreamAsync())?it's not a great ideato litter the code with async, as it spreads like GPL (also see Tasketiqutte).may lead (but in this case it rather promotes composition) to yo-yo problemand could be replaced with a static method like this (or even the extension to Uri class):public static async Task<byte[]> GetBytesAsync(/*this */Uri uniqueResourceIdentifier, CancellationToken token = new CancellationToken()){    if (!uniqueResourceIdentifier.IsWellFormedOriginalString()) throw new ArgumentException(nameof(uniqueResourceIdentifier));    using (var client = new HttpClient())    using (var response = await client.GetAsync(uniqueResourceIdentifier, token).ConfigureAwait(false))    {        var data = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);        return data;    }}But one cannot simply DI a static method, so this will be wrapped in thin class implementing an interface.Option 3: The third option would be, the code could be written just where it's needed (the API is great, almost no redundant configuration), risking, that if needed, the code will need to be refactored (or copied) in the future. Also, it could be tempting than to make a god-class like FileManager, which will download, unzip CRUD & all, but sacrificing freedom and 'S' from SOLID.Which one is preferred in this specific case (almost the same goesfor file unzipping, using ZipArchive and couple more of great MSAPIs)?Based on what, should I make a decision which one of thisthree approaches to take?
The granularity level to repeat code: should downloading, unzipping etc. be handled by separate classes?
c#;design patterns;.net
I'm not sure I follow all the code details, example and options you put in the text of your question. But I'll address the title of the question specifically, that is The granularity level to repeat code:There's not a fixed rule about granularity; design is not about fixed rules (otherwise we'd just write a computer program to do designs and a lot of developers would be... working for ber?). Design has heuristics, which are like rules of thumb that seem to have worked well in the past for certain types of problems.The General Responsibility Assignment Software Patterns (GRASP) are pretty useful heuristics for design (no so different from SOLID). In your case, it sounds a lot like the problem solved by the Pure Fabrication principle (emphasis mine):Who is responsible when you are desperate, and do not want to violate high cohesion and low coupling?Assign a highly cohesive set of responsibilities to an artificial or convenience behavior class that does not represent a problem domain conceptsomething made up, in order to support high cohesion, low coupling, and reuse.Your DownloadManager class is definitely reusable and it looks pretty cohesive. I'm not sure about the DI necessity, unless it's really something that varies in implementations (but that's another question).I highlighted the word desperate above, because the granularity notion you seek is there. As you assign responsibilities to classes in your design, at some point you realize that giving the downloading (or unzipping) responsibility to other classes violates the strong cohesion principle, and so you're not satisfied by that (Larman's choice of the word desperate is probably hyperbole, as a way to get it to stick in your brain). The solution is to essentially refactor that responsibility into a separate class that is very cohesive. Pure Fabrication is a lot like the Extract Class refactoring, except pure fabrications don't seem like problem-domain classes. The example for Extract Class has a new class called TelephoneNumber, which sounds like a problem-domain class. But your DownloadManager class sounds like a solution-domain class (especially the Manager part).The Visitor classes in GoF pattern by the same name are also great examples of Pure Fabrications. They are highly cohesive, but came about only after developers got frustrated with adding non-cohesive responsibilities to classes in a data structure. It helps to think of the design before they existed.