Уровень абстракции (Java)

В настоящее время я работаю над проектом, который включает создание слоя абстракции. Целью проекта является поддержка нескольких реализаций серверного программного обеспечения в случае, если мне может понадобиться переключиться на него. Список функций, которые нужно абстрагировать, довольно длинный, поэтому я хочу рассмотреть довольно безболезненный способ сделать это.

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

В этом и заключается проблема. У меня нет большого опыта в этой области, и я действительно не уверен, как сделать так, чтобы это не стало сандвичем смерти. Вот цепочка примерно того, на что это должно быть похоже (и что я пытаюсь достигнуть).

/*
Software that is dependent on mine
    |
Public API layer (called by other software)
    |
Abstraction between API and my own internal code (this is the issue)
    |
Internal code (this gets replaced per-implementation, as in, each implementation needs its own layer of this, so it's a different package of entirely different classes for each implementation)
    |
The software I'm actually using to write this (which is called by the internal code)
*/

Слой абстракции (очевидно, в середине) - это то, что я пытаюсь собрать вместе.

Теперь я застрял только в одном глупом аспекте. Как я могу сделать слой абстракции чем-то, что не является серией

public void someMethod() {
    if(Implementation.getCurrentImplementation() == Implementation.TYPE1) {
        // whatever we need to do for this specific implementation
    else {
        throw new NotImplementedException();
    }
}

(простите за псевдокод; также представьте себе ту же ситуацию, но для переключателя / регистра, поскольку это, вероятно, лучше, чем цепочка if для каждого метода) для каждого метода в каждом классе абстракции.

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

1 ответ

Решение

Почему бы не использовать инверсию управления?

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

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


Я больше не знаю, является ли инверсия управления IS инъекцией зависимостей или DI является формой Ioc, но... просто вы снимаете с себя ответственность за управление зависимостями из своего компонента.

Здесь вы будете иметь

  • Уровень API (интерфейс, который использует клиент)
  • реализации (бесконечные)
  • обертка (что делает IoC, принося импл)

Уровень API:

// my-api.jar
public interface MyAPI {
    String doSomething();
}

public interface MyAPIFactory {
    MyAPI getImplementationOfMyAPI();
}

реализации:

// red-my-api.jar
public class RedMyAPI implements MyAPI {
    public String doSomething() {
        return "red";
    }
}

// green-my-api.jar
public class GreenMyAPI implements MyAPI {
    public String doSomething() {
        return "green";
    }
}

// black-my-api.jar
public class BlackMyAPI implements MyAPI {
    public String doSomething() {
        return "black";
    }
}

Некоторые оболочки предоставляют способ настроить правильную реализацию. Здесь вы можете спрятать корпус переключателя на заводе или загрузить impl из конфига.

// wrapper-my-api.jar
public class NotFunnyMyAPIFactory implements MyAPIFactory {
    private Config config;

    public MyAPI getImplementationOfMyAPI() {
        if (config.implType == GREEN) {
            return new GreenMyAPI();
        } else if (config.implType == BLACK) {
            return new BlackMyAPI();                
        } else if (config.implType == RED) {
            return new RedMyAPI();                
        } else { 
           // throw...
        }
    }
}

public class ReflectionMyAPIFactory implements MyAPIFactory {
    private Properties prop;

    public MyAPI getImplementationOfMyAPI() {
        return (MyAPI) Class.forName(prop.get('myApi.implementation.className'))
    }
}

// other possible strategies

Фабрика позволяет использовать несколько стратегий для загрузки класса. В зависимости от решения, вам нужно только добавить новую зависимость и изменить конфигурацию (и перезагрузить приложение... или нет), чтобы изменить реализацию.

Возможно, вы захотите проверить выступления.

Если вы используете Spring, вы можете использовать только интерфейс в своем коде и внедрить правильную реализацию из класса конфигурации (Spring - это DI-контейнер). Но нет необходимости использовать Spring, вы можете сделать это непосредственно на главной точке входа (вы вводите с ближайшей точки входа).

  • My-api.jar не имеет зависимостей (или, возможно, некоторых по отношению к внутренним слоям).
  • Весь jar для реализаций зависит от my-api.jar и от вашего внутреннего кода.
  • Флакон обертки зависит от my-api.jar и некоторых имплантов.

Таким образом, клиент загружает банку, которую он хочет, использует фабрику, которую он хочет, или конфигурацию, которая внедряет impl, и использует ваш код. Это зависит также от того, как вы выставите свой API.

Другие вопросы по тегам