Уровень абстракции (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.