Скажем, у меня есть интерфейс Repository (объект для доступа к данным).Я создал простой для этого вопроса.
interface UserRepository {
void insert(final User user);
void update(final User user);
List<User> findAll();
}
Теперь, поскольку я управляю различными типами СУБД, этот репозиторий реализован следующим образом:
class MySqlUserRepository implements UserRepository { ... }
class OracleUserRepository implements UserRepository { ... }
class SqlServerUserRepository implements UserRepository { ... }
Поскольку большинствоОператоры SQL могут быть общими для этих реализаций. Прежде всего я добавлю базовый класс Abstract, который обеспечивает все основные реализации.
abstract class AbstractUserRepository implements UserRepository {
// Provide access to statements
private final Configuration configuration;
// Provides access to JDBC operations
private final NamedParameterJdbcTemplate jdbcTemplate;
AbstractUserRepository(
final Configuration configuration,
final NamedParameterJdbcTemplate jdbcTemplate) {
this.configuration = configuration;
this.jdbcTemplate = jdbcTemplate;
}
... Implementations
}
Итак, все реализации теперь extends AbstractUserRepository
.Однако я должен переопределить метод в OracleUserRepository
, чтобы использовать другой оператор ...
class OracleUserRepository extends AbstractUserRepository {
private final Configuration configuration;
private final NamedParameterJdbcTemplate jdbcTemplate;
OracleUserRepository(
final Configuration configuration,
final NamedParameterJdbcTemplate jdbcTemplate) {
super(configuration, jdbcTemplate);
this.configuration = configuration.subset("user.sql.oracle");
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void update(final User user) {
// Use Configuration and JdbcTemplate
}
...
}
... и пару методов в SqlServerUserRepository
, используя тот же подход, очевидно.
Теперь вы можете видеть, что мы уже дублируем свойства класса, и, возможно, методы в основном одинаковы:
AbstractUserRepository#update
:
void update(final User user) {
final var parameters =
new MapSqlParameterSource()
.addValue("field1", user.field1)
.addValue("field2", user.field2);
jdbcTemplate.update(configuration.getString("user.sql.update"), parameters);
}
OracleUserRepository#update
:
void update(final User user) {
final var parameters =
new MapSqlParameterSource()
.addValue("field1", user.field1)
.addValue("field2", user.field2);
// Note the subtle difference. We got a "subset" (user.sql.oracle) in the constructor
jdbcTemplate.update(configuration.getString("update"), parameters);
}
Мне это не кажется правильным, слишком много повторений.Вам тоже не кажется?
Другой подход - использование делегирования.Это означает наличие отдельного объекта, который не наследует интерфейс UserRepository
и который имеет методы, которые принимают также операторы SQL вместо только объекта.
class UserRepositoryDelegate {
private final NamedParameterJdbcTemplate jdbcTemplate,
UserRepositoryDelegate(final NamedParameterJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void update(
final String statement,
final User user) {
final var parameters =
new MapSqlParameterSource()
.addValue("field1", user.field1)
.addValue("field2", user.field2);
jdbcTemplate.update(statement, parameters);
}
...
}
Этот объект будет внедрен в другие репозитории:
class OracleUserRepository implements UserRepository {
private final Configuration configuration;
private final UserRepositoryDelegate delegate;
OracleUserRepository(
final Configuration configuration,
final UserRepositoryDelegate delegate) {
this.configuration = configuration.subset("user.sql.oracle");
this.delegate = delegate;
}
@Override
public void update(final User user) {
delegate.update(configuration.get("update"), user);
}
...
}
И AbstractUserRepository
может стать конкретным GenericUserRepository
(обратите внимание, я не получаю подмножество свойств для конкретной СУБД, а вместо этого использую общие):
class GenericUserRepository implements UserRepository {
private final Configuration configuration;
private final UserRepositoryDelegate delegate;
GenericUserRepository(
final Configuration configuration,
final UserRepositoryDelegate delegate) {
this.configuration = configuration;
this.delegate = delegate;
}
@Override
public void update(final User user) {
delegate.update(configuration.get("user.sql.update"), user);
}
...
}
GenericUserRepository
также может использоваться другими реализациями:
class MySqlUserRepository implements UserRepository {
private final GenericUserRepository genericUserRepository;
MySqlUserRepository(final GenericUserRepository genericUserRepository) {
this.genericUserRepository = genericUserRepository;
}
@Override
public void update(final User user) {
genericUserRepository.update(user);
}
...
}
Является ли подход с делегатом лучшей стратегией для этого конкретного случая?
Вы бы решили эту "проблему"?по-другому?
Редактировать: чтобы вы поняли больше, единственная и правильная реализация репозитория выбирается во время запуска и вводится в одном конкретном UserService
.