10
votes

I have an interface (call it IAcmeService) that has multiple implementations.

FileSystemAcmeService
DatabaseAcmeService
NetworkAcmeService

The end-user needs to be able to select which implementation will be used and also save that selection.

Currently I'm configuring my IOC container (Unity) to register all the known implemenatation with a name.

container.RegisterType(of IAcmeService, FileSystemAcmeService)("FileSystemAcmeService")
container.RegisterType(of IAcmeService, DatabaseAcmeService)("DatabaseAcmeService")
container.RegisterType(of IAcmeService, NetworkAcmeService)("NetworkAcmeService")

To allow the user to save their selection I have app.config configuration section file that stores the chosen service name to use.

To resolve the selected implementation I'm doing a manual Resolve in the Initialize method of the class the uses the service.

Private _service as IAcmeService
Public Sub Initialize()
    _service = container.Resolve(of IAcmeService)(_config.AcmeServiceName)
End Sub

This doesn't seem right because my class has to know about the container. But I can't figure out another way.

Are there other ways to allow end-user selection without the class knowing about the container?

1

1 Answers

10
votes

Defining and implementing and Abstract Factory is the standard solution to this type of problem. If you will pardon my use of C#, you can define an IAcmeServiceFactory interface like this:

public interface IAcmeServiceFactory
{
    IAcmeService Create(string serviceName);
}

You can now write a concrete implementation like this one:

public class AcmeServiceFactory : IAcmeServiceFactory
{
    private readonly IAcmeService fsService;
    private readonly IAcmeService dbService;
    private readonly IAcmeService nwService;

    public AcmeServiceFactory(IAcmeService fsService,
        IAcmeService dbService, IAcmeService nwService)
    {
        if (fsService == null)
        {
            throw new ArgumentNullException("fsService");
        }
        if (dbService == null)
        {
            throw new ArgumentNullException("dbService");
        }
        if (nwService == null)
        {
            throw new ArgumentNullException("nwService");
        }

        this.fsService = fsService;
        this.dbService = dbService;
        this.nwService = nwService;
    }

    public IAcmeService Create(string serviceName)
    {
        switch case serviceName
        {
            case "fs":
                return this.fsService;
            case "db":
                return this.dbService;
            case "nw":
                return this.nwService;
            case default:
                throw new ArgumentException("serviceName");
        }
    } 
}

You can make it more general-purpose if you want to be able to create an arbitrary number of IAcmeService instances, but I will leave that as an exercise to the reader :)

This will require you to register the Factory with Unity as well. In any place where you need an IAcmeService based on a name, you take a dependency on IAcmeServiceFactory instead of IAcmeService itself.