Нужен совет по моему дизайну API - есть ли у одного преимущества перед другим?

1

Я занимаюсь реорганизацией довольно крупного проекта. Один из моих основных этапов - скрыть каждую зависимость от стороннего API за моим собственным API. Основная цель - разрешить окурки и издевки в модульном тестировании, вторичные: улучшить читаемость бизнес-логики, избавившись от стороннего API-интерфейса "voodoo". Я уверен, что у вас у всех есть или есть эти части кода в ваших проектах, где вы идете с конкретными вещами вашего домена, чтобы просто внезапно оказаться в середине жонглирования сторонним API-интерфейсом только для того, чтобы отказаться от проблем с конкретным доменом несколько строк позже, Я хочу избавиться от этих частей, чтобы бизнес-логика была понятнее читать, понимать и поддерживать или расширять.

В основном первый шаг уже позади меня. Я избавился от каждой зависимости от третьей стороны и спрятал ее за моим API. Прямо сейчас это просто один большой интерфейс, грубо заменяющий вызовы третьей стороне. Если был третий участник "3rdPartyAPIitem.Load()", теперь он "myAPI.ItemLoad(myItem)", если был сторонний "3rdPartyAPI.DeleteItems(listOfItems, modeOfDeletion)", теперь он "myAPI.DeleteItems" (listOfMyItems, myModeOfDeletion )". Я создал интерфейс, чтобы скрыть сторонние "элементы". Я думаю, вы получите картину.

У меня уже есть свои операции API, разделенные на более мелкие и более ориентированные интерфейсы, от которых наследуется основной интерфейс. Поэтому для загрузки элементов у меня есть интерфейс ILoader, для сохранения у меня есть интерфейс ISaver и т.д., И мой основной интерфейс API наследует от них и, конечно же, реализацию за ним. В коде бизнес-логики используются только основные интерфейсы API и элементов, а также несколько моих пользовательских перечислений, заменяющих те, которые поддерживаются сторонним API.

Теперь я задаюсь вопросом, должен ли я переконструировать свой API, чтобы обеспечить более гранулярность в использовании. Я имею в виду, например: когда бизнес-логике нужно сохранять элементы прямо сейчас, он использует мой основной интерфейс API, хотя операция сохранения является частью интерфейса ISaver. Я мог бы, конечно, отправить свой экземпляр API там, где ISaver и VS/Resharper даже помогут найти эти места, но в одном методе я сохраняю в другом, который я удаляю, а в другом - еще что-то еще. Так что "проще" просто сохранить ссылку IMyAPI и назвать то, что ей нужно. По крайней мере, сейчас легче.

Хорошо, достаточно об истории и посмотрим код, о котором я хочу спросить.

public interface ILoader
{
    bool Load();
}

public interface ISaver
{
    bool Save();
}

public interface IService : ILoader, ISaver
{

}

public class ServiceA : IService
{
    public bool Load()
    {
        return false;
    }

    public bool Save()
    {
        return false;
    }
}

public class ServiceB : IService
{
    private readonly ILoader _loader;
    private readonly ISaver _saver;

    public ServiceB(ILoader loader, ISaver saver)
    {
        _loader = loader;
        _saver = saver;
    }

    public bool Load()
    {
        return _loader.Load();
    }

    public bool Save()
    {
        return _saver.Save();
    }
}

public interface IServiceC
{
    ILoader Loader { get; }
    ISaver Saver { get; }
}

public class ServiceC : IServiceC
{
    public ILoader Loader { get; private set; }
    public ISaver Saver { get; private set; }

    public ServiceC(ILoader loader, ISaver saver)
    {
        Loader = loader;
        Saver = saver;
    }
}

ServiceA - это то, что у меня есть. Один монолитный API, который, на мой взгляд, ленивый и уродливый "дизайн", и я хотел бы перейти в одну из двух других реализаций.

ServiceB предлагает инъекцию зависимостей, так что для модульных тестов могут быть введены заглушки/макеты, и мне даже нужно будет переключиться на другой сторонний API. Я просто "должен" реализовать ILoader и ISaver. С другой стороны, хотя зависимости видны, их внутреннее использование неясно. Я не уверен, что это проблема или не быть честным, возможно, нет.

ServiceC в основном такой же, как ServiceB, но он делает более понятным, что весь API разделен на функциональные части. Клиентский код (моя бизнес-логика) должен быть изменен, чтобы соответствовать этому, но это не проблема прямо сейчас, я все равно использую этот код. Но я могу получить или потерять что-либо, используя эту конструкцию над ServiceB?

О, и мой API - это не просто несколько простых методов. Мы говорим о нескольких десятках методов (которые, как минимум, я смог достичь во время рефакторинга), которые скрывают еще больше сторонних методов. Поэтому, хотя мои примеры - тривиальный реальный материал, нет, и именно поэтому я был бы признателен за некоторые намеки, советы или критические замечания по этому вопросу.

Теги:
design
refactoring

2 ответа

1

Я склоняюсь к ServiceB.

  1. Таким образом, вы можете пройти вокруг вашего ServiceB реализацию либо как ILoad или ISave. Клиентский код не должен знать, что он реализует оба.

  2. Вы можете легко заглушить/издеваться/использовать разные реализации.

  3. Вы можете легко адаптировать завернутые реализации или выполнить действия до или после вызова делегатов.

  4. Ваш код будет более чистым

Сравните ServiceB с ServiceC

serviceB.load(...)

против

serviceC.Loader.load(...)
  • 0
    Имея несколько дней, чтобы обдумать это и увидеть, как мой код выходит из рефакторинга в более согласованное состояние, я согласен, что ServiceB выглядит намного удобнее и понятнее, чем ServiceC. спасибо за ваш вклад.
1

Я лично пошлю за ServiceB. Вы говорите: "С другой стороны, хотя зависимости видны, их внутреннее использование непонятно. Я не уверен, что это проблема или не быть честным, возможно, нет". Я чувствую, что это на самом деле преимущество.

Допустим, что при загрузке вы должны что-то сделать, в случае C, если вам нужно было изменить способ использования объекта ILoader, вам нужно позаботиться о том, чтобы все реализации ILoader были скорректированы. В B это можно было бы накапливать в вызывающий код.

public bool Load()
{
    //something new can go here easy
    return _loader.Load();
}

Однако можно утверждать, что C легче понять и поддерживать... так что это может быть вопросом предпочтения?

  • 0
    Спасибо за Ваш ответ. Я как бы чувствую, что в итоге речь идет о предпочтениях и конкретной ситуации. Я вижу теоретические плюсы и минусы обеих конструкций, но не могу решить, какие из них мне лучше подойдут в этом проекте. Я задал этот вопрос в надежде, что кто-нибудь укажет на плюса или минуса, которых я не вижу. Ты знаешь свежую пару глаз и все такое :)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню