Проблема архитектуры: использование внедрения зависимостей, приводящее к мусорному API - PullRequest
5 голосов
/ 11 марта 2011

Я пытаюсь создать класс, который выполняет все виды низкоуровневых действий, связанных с базой данных, но представляет действительно простой интерфейс для уровня пользовательского интерфейса.

Этот класс представляет собой набор данных, все в пределах определенного совокупного корня, извлекаемых одним идентификатором int.

Конструктор принимает четыре параметра:

public AssetRegister(int caseNumber, ILawbaseAssetRepository lawbaseAssetRepository, IAssetChecklistKctcPartRepository assetChecklistKctcPartRepository, User user)
{
  _caseNumber = caseNumber;
  _lawbaseAssetRepository = lawbaseAssetRepository;
  _assetChecklistKctcPartRepository = assetChecklistKctcPartRepository;
  _user = user;
  LoadChecklists();
}

Уровень пользовательского интерфейса обращается к этому классу через интерфейс IAssetRegister. Замок Виндзор может сам предоставлять параметры ILawbaseAssetRepository и IAssetChecklistKctcPartRepository, но код пользовательского интерфейса должен предоставить два других, используя анонимный тип, подобный этому:

int caseNumber = 1000;
User user = GetUserFromPage();
IAssetRegister assetRegister = Moose.Application.WindsorContainer.Resolve<IAssetRegister>(new { caseNumber, user});

С точки зрения дизайна API это мусор. Разработчик уровня пользовательского интерфейса не может знать, что IAssetRegister требуется целое число и пользователь. Им нужно знать о реализации класса, чтобы использовать его.

Я знаю, у меня должна быть какая-то проблема с дизайном. Кто-нибудь может дать мне несколько советов?

Ответы [ 2 ]

3 голосов
/ 11 марта 2011

Попробуйте отделить сообщение от поведения. Создайте класс, содержащий данные для операции, и создайте другой класс, содержащий бизнес-логику для этой операции. Например, создайте эту команду:

public class RegisterAssetCommand
{
    [Required]
    public int CaseNumber { get; set; }

    [Required]
    public User Operator { get; set; }
}

Теперь определите интерфейс для обработки бизнес-команд:

public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

Ваш код презентации теперь будет выглядеть так:

var command = new RegisterAssetCommand
{
    CaseNumber = 1000,
    Operator = GetUserFromPage(),
};

var commandHandler = WindsorContainer
    .Resolve<ICommandHandler<RegisterAssetCommand>);

commandHandler.Handle(command);

Примечание: Если возможно, переместите ответственность за получение commandHandler из класса представления и вставьте его в конструктор этого класса (повторное внедрение конструктора).

Нет, вы можете создать реализацию ICommandHandler<RegisterAssetCommand> следующим образом:

public class RegisterAssetCommandHandler
    : ICommandHandler<RegisterAssetCommand>
{
    private ILawbaseAssetRepository lawbaseAssetRepository;
    private IAssetChecklistKctcPartRepository assetRepository;

    public RegisterAssetCommandHandler(
        ILawbaseAssetRepository lawbaseAssetRepository,
        IAssetChecklistKctcPartRepository assetRepository)
    {
        this.lawbaseAssetRepository = lawbaseAssetRepository;
        this.assetRepository = assetRepository;
    }

    public void Handle(RegisterAssetCommand command)
    {
        // Optionally validate the command

        // Execute the command
    }
}

При желании вы могли бы даже оставить User из RegisterAssetCommand, введя IUserProvider в RegisterAssetCommandHandler. Интерфейс IUserProvider может иметь GetUserForCurrentContext, который может вызывать обработчик.

Надеюсь, это имеет смысл.

2 голосов
/ 11 марта 2011

Как указывает Мортен, переместите неинъекционные зависимости из вызова конструктора в методы, которые действительно должны его использовать,

Если у вас есть параметры конструктора, которые не могут (или трудны дляЕсли вы будете внедрены, вы не сможете автоматически внедрить IAssetRegister в любой класс, который в этом нуждается.

Конечно, вы всегда можете создать интерфейс IUserProvider с конкретной реализацией по следующим направлениям:

public class UserProvider : IUserProvider 
{
    // interface method
    public User GetUser() 
    {
        // you obviously don't want a page dependency here but ok...
        return GetUserFromPage();
    }
}

Таким образом, создается еще одна инъекционная зависимость, где ее не было.Теперь вы избавляетесь от необходимости передавать пользователя каждому методу, который может ему понадобиться.

...