Как разрешить тип на основе значения конфигурации конечного пользователя?

У меня есть интерфейс (называйте его IAcmeService), который имеет несколько реализаций.

FileSystemAcmeService
DatabaseAcmeService
NetworkAcmeService

Конечный пользователь должен иметь возможность выбирать, какая реализация будет использоваться, а также сохранить этот выбор.

В настоящее время я настраиваю свой контейнер IOC (Unity), чтобы зарегистрировать всю известную реализацию с именем.

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

Чтобы пользователь мог сохранить свой выбор, у меня есть файл раздела конфигурации app.config, в котором хранится выбранное имя службы, которое будет использоваться.

Чтобы разрешить выбранную реализацию, я делаю ручную процедуру Resolve в методе Initialize этого класса, который использует эту службу.

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

Это не кажется правильным, потому что мой класс должен знать о контейнере. Но я не могу понять другого пути.

Существуют ли другие способы разрешить выбор конечного пользователя без знания класса о контейнере?

Ответ 1

Определение и реализация и Аннотация Factory является стандартным решением этой проблемы. Если вы помилуете мое использование С#, вы можете определить интерфейс IAcmeServiceFactory следующим образом:

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

Теперь вы можете написать конкретную реализацию, подобную этой:

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");
        }
    } 
}

Вы можете сделать его более универсальным, если хотите создать произвольное количество экземпляров IAcmeService, но я оставлю это как упражнение для читателя:)

Это потребует, чтобы вы зарегистрировали Factory с Unity. В любом месте, где вам нужна IAcmeService, основанная на имени, вы принимаете зависимость от IAcmeServiceFactory вместо самого IAcmeService.