2016-10-28 17 views
2

Я смиряюсь с проблемой, связанной с AutoFac и абстрактным шаблоном завода. Мой пример - это сервис для использования IRepositoryFactory для создания репозитория на основе JSON или InMemory, связанных с вводом пользователя.Использование Autofac для переключения бетонной реализации абстрактной фабрики

// Abstract Factory 
public interface IRepositoryFactory{ 
    IRepository Create(string databaseIdentifier); 
} 

// JSON 
public class JsonRepositoryFactory{ 
    public IRepository Create(string databaseIdentifier){ 
     return new JsonRepository(databaseIdentifier); 
    } 
} 

// InMemory 
public class MemoryRepository{ 
    public IRepository Create(string databaseIdentifier){ 
     return new MemoryRepository(databaseIdentifier); 
    } 
} 

Служба должна вытащить завод путем инжекции конструктора.

public interface IShopService{ 
    public string Name {get;} 
} 

public class BeerShop : IShopService { 
    public string Name {get; private set;} 
    private readonly IRepository _repository; 

    public BeerShop(IRepositoryFactory repositoryFactory){ 
     Name = "beershop"; 
     _repository = repositoryFactory.Create(Name); 
    } 
} 

До сих пор я доволен этим. Но инициализация не моя любимая.

var builder = new ContainerBuilder(); 
var userInput = ReadInput(); 

if(userInput = "json") 
    builder.RegisterType<IRepositoryFactory>().As<JsonRepositoryFactory>(); 
else 
    builder.RegisterType<IRepositoryFactory>().As<MemoryRepositoryFactory>(); 

builder.RegisterType<IShopService>.As<BeerShop>(); 

var container = builder.build(); 

[...]  

var service = container.Resolve<IShoptService>(); 
// and so on ... 

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

+0

Ваш 'BeerShop' делает слишком много: [инжекторы должны быть простыми] (http://blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple/). Если вы переместите вызов 'factory.Create' из конструктора в Корзину композиции, вы больше не требуете этой заводской абстракции. Также обратите внимание, что [заводские абстракции - запах кода] (https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=100). – Steven

+0

Я согласен с Стивеном. Ваш клиент (который является BeerShop), похоже, не будет использовать тот же код/​​логику для другой вещи, например: colashoop. Также не используйте завод, если у вас нет других шансов. Потому что фабрика приносит сложность. Похоже, вам нужно это разделение на более низком уровне. Репозиторий будет таким же, но он будет использовать json или memory как Db. Изменение db во время выполнения очень опасно. Вы напишете что-нибудь в json и прочитаете из памяти по пользовательскому входу при той же самой runtime ??? –

ответ

0

Если вы хотите изменить поведение фабрики во время выполнения, тогда вам нужно переместить логику выбора типа IRepository, создаваемого на заводе. Вы можете адаптировать шаблон и настроить его в соответствии с вашими требованиями, а не соответствовать конкретному способу его выполнения.

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

public interface IRepository 
    { 
     //repository contracts 
    } 

    public interface IRepositoryFactory 
    { 
     IRepository Create(string arguments); 
    } 

    public interface IRepositoryBuilder 
    { 
     RepositoryType Type { get; } 
     IRepository Create(string args); 
    } 

    public class ApplicationSettings 
    { 
     public RepositoryType RepositoryType { get; set; } 
    } 

    public enum RepositoryType { Json, Text } 

    // Default implementation of repository factory based on applicationsettings. 
    public class ConfigurableRepositoryBuilder:IRepositoryFactory 
    { 
     private readonly ApplicationSettings _settings; 
     private readonly IEnumerable<IRepositoryBuilder> _repositoryBuilders; 

     public ConfigurableRepositoryBuilder(ApplicationSettings settings, IEnumerable<IRepositoryBuilder> repositoryBuilders) 
     { 
      _settings = settings; 
      _repositoryBuilders = repositoryBuilders; 
     } 

     public IRepository Create(string arguments) 
     { 
      var builder = _repositoryBuilders.First(x => x.Type == _settings.RepositoryType); 
      //configure builder settings and then call create 
      return builder.Create(arguments); 
     } 
    } 

Теперь вы можете изменить глобальные настройки в любое время, и вы получите новый тип репозитория от следующего вызова вперед. Вместо глобального синглтона вы также можете прочитать файл app.config или другой файл настроек.

Теперь необходимо реализовать IRepositoryBuilder для каждого типа поддерживаемого IRepository типа

1

Поскольку тип хранилища известен в то время контейнер сконфигурирован, вы должны зарегистрировать конкретное хранилище непосредственно. Нет необходимости вводить завод, так как там hardly ever is a reason ввести заводскую абстракцию.

Пример:

var builder = new ContainerBuilder(); 
var userInput = ReadInput(); 

if(userInput = "json") 
    builder.Register<IRepository>(c => new JsonRepository("dbidentifier")); 
else 
    builder.Register<IRepository>(c => new MemoryRepository()); 

builder.RegisterType<BeerShop>.As<IShopService>(); 

var container = builder.build(); 

[...]  

var service = container.Resolve<IShopService>(); 

Этот код позволяет BeerShop быть упрощена, так как its constructor would be simple и теперь это зависит только от IRepository вместо IRepositoryFactory и IRepository. Это упрощает тестирование и упрощает рассуждение об этом классе. Кроме того, он удаляет ненужную абстракцию.