Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement interface with additional parameters/info per implementation

My MVC webapp allows users to add and delete images. The UI calls ImageService.SaveImage(...) in my business layer which internally uses a flag that tells the method to save to either Azure or the file system. I might eventually add S3 to I figure an interface here would work great.

This is what I would imaging the code would look like in my ImageService class. It doesn't care about how the file is saved or where.

// Service the UI uses
public static class ImageService
{
    public static void SaveImage(byte[] data, IImageProvider imageProvider)
    {
        string fileName = "some_generated_name.jpg"
        imageProvider.Save(fileName, data);
    }
}

So I created these implementations

public interface IImageProvider
{
    void Save(string filename, byte[] imageData);
    byte[] Get(string filename);
    void Delete(string filename);
}

// File system implementation
public class FSImageProvider : IImageProvider
{
    public void Delete(string filename)
    {
        File.Delete(filename);
    }

    public byte[] Get( filename)
    {
        return File.ReadAllBytes(filename);
    }

    public void Save(string filename, byte[] imageData)
    {
        File.WriteAllBytes(filename, imageData);
    }
}

// Azure implementation
public class AzureBlobImageProvider : IImageProvider
{
    private string _azureKey = "";
    private string _storageAccountName = "";

    public AzureBlobImageProvider(string azureKey, string storageAccount)
    {
        _azureKey = azureKey;
        _storageAccountName = storageAccount;
    }

    public void Delete(string filename)
    {
        throw new NotImplementedException();
    }

    public byte[] Get(string filename)
    {
        throw new NotImplementedException();
    }

    public void Save(string filename, byte[] imageData)
    {
        throw new NotImplementedException();
    }
}

Question 1) What's the best way to pass in additional info each provider may need? I.e. Azure needs to know container name, blob name(filename), and storageAccount name. S3 may need more as well. A good example is the files path. This could be different for each provider or not exist at all. Azure needs a container name, the file system needs a directory name. If they are different for each provider how would I add that to the interface?

Question 2) Should I use dependency injection to resolve the interface within the ImageService class in the business layer or should I resolve it in the UI and pass it into the class?

like image 618
user3953989 Avatar asked Mar 06 '18 15:03

user3953989


2 Answers

First, you have major architectural disadvantage of passing IImageProvider into SaveImage method. You need this kind of function signature in cases where lifetime of your IImageProvider need to be controllable across methods. In your case you just saving image and pretty much doesn't care about any lifetime of any of your classes, but still using this approach and it will eventually clutter your code, duh - you don't even care about this provider in particular (this is why you wrapping it into interface I presume)

Ask yourself:

"Is my IImageProvider actually used anywhere outside of ImageService? If not, why everyone (methods, classes) need to even know about it's existance?"

Second, instead of creating provider - make your ImageService simple class (remove static), define interface for it, and implement for Azure/FS/etc. For concrete implmentation use factory:

public interface IImageService
{
    void SaveImage(byte[] bytes);
}

public interface IImageServiceFactory
{
    IImageService Create(/*here goes enum, string, connections strings, etc*/);
}


internal sealed class AzureImageService : IImageService {/*implmentation*/}
internal sealed class FileSystemImageService : IImageService {/*implmentation*/}

Overall

Do not pass dependencies in methods. Your methods should look simple, without any clutter like ILogger, IImageProvider, etc that you think is good to pass inside. If you need them at some point in some implementation - just create class which get all dependencies it need through constructor (so static modifier is pretty much always banned and used only for extensions of language). It will be easier for you to manage your depenecies and refactor your code without any colliteral damage of constantly reusing same interface in places where it doesn't even needed.

like image 91
eocron Avatar answered Jan 03 '23 16:01

eocron


Usually this type of implementation specific data would be provided in the concrete class constructor, just like you did in your example.

I would not create the concrete instances in the UI. You can either use dependency injection or have a single set of factory classes that create the instances with proper configuration. The key point is you want to centralize the configuration of these services into a single location and not have implementation-specific code sprinkled throughout the application.

like image 33
Samuel Neff Avatar answered Jan 03 '23 16:01

Samuel Neff