Проблема архитектуры: использование внедрения зависимостей приводит к мусорному API

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

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

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

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

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

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

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

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


person David    schedule 11.03.2011    source источник
comment
Используйте внедрение конструктора только для инъекционных зависимостей. Переместите назначенные вручную значения в качестве параметров метода или предоставьте метод для их установки после построения.   -  person Morten Mertner    schedule 11.03.2011
comment
lol @ thephpdeveloper's edit!   -  person David    schedule 11.03.2011
comment
да, эта правка меня тоже рассмешила. не уверен, стоит ли так «связываться со стилистической индивидуальностью авторов» :)   -  person jim tollan    schedule 11.03.2011
comment
это не DI, который приводит к мусорному API. вы используете SL, и это обычный запах. Не делайте SL.   -  person Krzysztof Kozmic    schedule 12.03.2011
comment
Я никогда не имел в виду, что сам DI приводит к мусорному API, только мое (неправильное) его использование.   -  person David    schedule 13.03.2011
comment
Почему вы говорите, что это SL? Из-за использования в коде WindsorContainer.Resolve ‹T› ()? Как еще я бы это сделал?   -  person David    schedule 13.03.2011


Ответы (2)


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

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

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

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

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

person Sergi Papaseit    schedule 11.03.2011
comment
Эта идея UserProvider может решить для меня несколько проблем. Спасибо. - person David; 11.03.2011
comment
Хорошо, я исправил проблему. Я использую интерфейс типа IUserProvider, чтобы получить текущего пользователя (конечно, введенный), и предоставляю номер дела через вызов метода, а не конструктор. Теперь мой API не требует от разработчика никаких догадок. Спасибо вам всем! - person David; 11.03.2011
comment
О, и концепция IUserProvider также решила эту проблему для меня: stackoverflow.com/questions/5192529/ - person David; 11.03.2011
comment
Это больше, чем я мог надеяться;) - person Sergi Papaseit; 11.03.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, который может вызвать обработчик.

Я надеюсь это имеет смысл.

person Steven    schedule 11.03.2011
comment
Извините, я не понимаю этот код. Мне нужно больше кормления с ложечки. Как получить экземпляр IAssetRegister? - person David; 11.03.2011
comment
Ваш IAssetRegister ушел. Он разделен на два класса: A RegisterAssetCommand и ICommandHandler<RegisterAssetCommand>. Код, который вы поместили в реализацию IAssetRegister, следует поместить в метод Handle класса RegisterAssetCommandHandler. - person Steven; 11.03.2011
comment
О, я думаю, я понял. Вы думаете, что IAssetRegister просто выполняет какую-то команду. Фактически, это в основном как сущность - она ​​содержит состояние. Сказав это, я думаю, что проблема заключается в дизайне - мне нужен репозиторий, чтобы получить реестр активов. Затем я могу указать номер дела и пользователя методу Load репозитория. - person David; 11.03.2011
comment
Ах я вижу. Зависимость ваших сущностей от сервисов, как правило, плохая идея, но я думаю, вы уже это поняли, потому что именно об этом и идет ваш вопрос. - person Steven; 11.03.2011
comment
Стивен, я чувствую, что ты золотая жила, но не могу отказываться от твоего жаргона. Сущности зависят от сервисов ??? Нужно ли мне читать дизайн, ориентированный на предметную область? - person David; 11.03.2011
comment
(Большое спасибо за ваше время и усилия, которые привели меня сюда ...) - person David; 11.03.2011
comment
Кстати, ваш блог выглядит потрясающе. Я нанесу удар после крепкого кофе! :) - person David; 11.03.2011
comment
По умолчанию все, что вы вводите в другой класс, является службой, например ILawbaseAssetRepository. Я говорю здесь на жаргоне внедрения зависимостей. Когда вы пытаетесь внедрить репозиторий в сущность, он сообщает мне, что у вашей сущности несколько обязанностей. Попробуйте разделить обязанности. Пусть сущность будет сущностью, а логику (код) определите где-нибудь еще. Если вы хотите изменить состояние, используйте команды и обработчики команд. Если вы просто хотите сделать запрос, поместите его также в отдельный класс. - person Steven; 11.03.2011
comment
Спасибо, что нашли время уточнить. - person David; 11.03.2011