Не зная слишком много о специфике вашего кода, я предложу три общих подхода.
(Кроме того, я никогда не использовал Kotlin. Надеюсь, примеров Java достаточно, чтобы вы могли разобраться.)
Первый подход
Похоже, вам нужна какая-то нетривиальная логика, чтобы определить, какую реализацию Базы данных использовать.Это классический случай для ProviderBinding .Вместо привязки Database
к конкретной реализации, вы привязываете Database
к классу, который отвечает за предоставление экземпляров ( Provider ).Например, у вас может быть этот класс:
public class MyDatabaseProvider.class implements Provider<Database> {
@Inject
public MyDatabaseProvider.class(Provider<SQLiteDatabase> sqliteProvider, Provider<H2Database> h2Provider) {
this.sqliteProvider = sqliteProvider;
this.h2Provider = h2Provider;
}
public Database get() {
// Logic to determine database type goes here
if (isUsingSqlite) {
return sqliteProvider.get();
} else if (isUsingH2) {
return h2Provider.get();
} else {
throw new ProvisionException("Could not determine correct database implementation.");
}
}
}
(Примечание: этот пример кода каждый раз возвращает вам новый экземпляр. Это довольно просто сделать так, чтобы он также возвращал одиночный экземпляр.)
Тогда, чтобы использовать его, у вас есть два варианта.В вашем модуле вы бы привязали Database
не к конкретной реализации, а к DatabaseProvider
.Например:
protected void configure() {
bind(Database.class).toProvider(MyDatabaseProvider.class);
}
Преимущество этого подхода заключается в том, что вам не нужно знать правильную реализацию базы данных, пока Guice не попытается сконструировать объект, для которого требуется Database
в качестве одного из аргументов конструктора.
Второй подход
Можно создать класс DatabaseRoutingProxy
, который реализует Database
, а затем делегировать правильную реализацию базы данных.(Я использовал этот шаблон профессионально. Я не думаю, что есть «официальное» имя для этого шаблона проектирования, но вы можете найти обсуждение здесь .) Этот подход основан на отложенная загрузкас Provider
с использованием провайдеров, которые Guice автоматически создает (1) для каждого связанного типа.
public class DatabaseRoutingProxy implements Database {
private Provider<SqliteDatabse> sqliteDatabaseProvider;
private Provider<H2Database> h2DatabaseProvider;
@Inject
public DatabaseRoutingProxy(Provider<SqliteDatabse> sqliteDatabaseProvider, Provider<H2Database> h2DatabaseProvider) {
this.sqliteDatabaseProvider = sqliteDatabaseProvider;
this.h2DatabaseProvider = h2DatabaseProvider;
}
// Not an overriden method
private Database getDatabase() {
boolean isSqlite = // ... decision logic, or maintain a decision state somewhere
// If these providers don't return singletons, then you should probably write some code
// to call the provider once and save the result for future use.
if (isSqlite) {
return sqliteDatabaseProvider.get();
} else {
return h2DatabaseProvider.get();
}
}
@Override
public QueryResult queryDatabase(QueryInput queryInput) {
return getDatabase().queryDatabase(queryInput);
}
// Implement rest of methods here, delegating as above
}
А в вашем модуле Guice:
protected void configure() {
bind(Database.class).to(DatabaseRoutingProxy.class);
// Bind these just so that Guice knows about them. (This might not actually be necessary.)
bind(SqliteDatabase.class);
bind(H2Database.class);
}
Преимущество этого подхода состоит в том, что вам не нужно знать, какую реализацию базы данных использовать, пока вы на самом деле не сделаете вызов базы данных.
Оба эти подхода предполагают, что вы не можете создать экземплярэкземпляр H2Database или SqliteDatabase, если файл резервной базы данных фактически не существует.Если можно создать экземпляр объекта без резервного файла базы данных, тогда ваш код станет намного проще.(Просто используйте маршрутизатор / прокси / делегат / все, что принимает фактические Database
экземпляры в качестве аргументов конструктора.)
Третий подход
Этот подход полностью отличается от последнегодва.Мне кажется, что ваш код на самом деле имеет дело с двумя вопросами:
- Существует ли база данных на самом деле?(Если нет, то создайте его.)
- Какая база данных существует?(И получите правильный класс для взаимодействия с ним.)
Если вы можете решить вопрос 1, прежде чем даже создать инжектор хитрости, которому нужно знать ответ на вопрос 2, тогда вам не нужносделать что-нибудь сложное.Вы можете просто иметь модуль базы данных, подобный этому:
public class MyDatabaseModule extends AbstractModule {
public enum DatabaseType {
SQLITE,
H2
}
private DatabaseType databaseType;
public MyDatabaseModule(DatabaseType databaseType) {
this.databaseType = databaseType;
}
protected void configure() {
if (SQLITE.equals(databaseType)) {
bind(Database.class).to(SqliteDatabase.class);
} else if (H2.equals(databaseType)) {
bind(Database.class).to(H2Database.class);
}
}
}
Поскольку вы разделили вопросы 1 и 2, при создании инжектора, который будет использовать MyDatabaseModule
, вы можете передать соответствующее значениедля аргумента конструктора.
Примечания
- В документации Injector указано, что для каждой привязки будет
Provider<T>
T
.Я успешно создал привязки без создания соответствующего поставщика, поэтому Guice должен автоматически создать поставщика для настроенных привязок.(Правка: я нашел больше документации , в которой это сказано более четко.)