У меня есть вариант использования, когда у меня есть интерфейс базы данных, предоставленный внешним поставщиком, скажем, он выглядит следующим образом:
interface Database{
public Value get(Key key);
public void put(Key key, Value value)
}
Поставщик предоставляет несколько реализаций этого интерфейса, например. Актуалдатабасеимпл, Мокдатабасеимпл. Мои потребители хотят использовать интерфейс базы данных, но перед вызовом некоторых API они хотят выполнить некоторую дополнительную работу, например, вызвать ограничитель скорости на стороне клиента перед выполнением вызова. Таким образом, вместо того, чтобы каждый потребитель выполнял дополнительную работу по проверке лимита rateLimiter, я подумал о создании декорированного класса, который будет абстрагировать часть ограничения скорости, и потребители смогут взаимодействовать с БД, не зная логики RateLimiter. например
class RateLimitedDatabase implements Database{
private Database db;
public RateLimitedDatabase(Database db) {this.db = db;}
public Value get(Key key) {
Ratelimiter.waitOrNoop();
return db.get(key);
}
public void put(Key key, Value value) {
Ratelimiter.waitOrNoop();
return db.put(key, value);
}
}
Это прекрасно работает до тех пор, пока интерфейс базы данных не вводит новые методы. Но как только они начинают добавлять API, которые мне на самом деле не нужны, например. Начинают возникать проблемы с удалением/получениемDBInfo/deleteDB и т. д.
Всякий раз, когда выпускается новая версия БД с более новыми методами, моя сборка для RateLimitedDatabase будет ломаться. Один из вариантов — реализовать новые методы в декорированном классе при расследовании основной причины сбоя сборки, но это просто дополнительная проблема для разработчиков. Есть ли другой способ справиться с такими случаями, поскольку это кажется распространенной проблемой при использовании шаблона Decorator с постоянно меняющимся/расширяемым интерфейсом?
ПРИМЕЧАНИЕ. Я также могу подумать о создании решения на основе отражения, но это кажется излишним/чрезмерным для этой конкретной проблемы.




Если это возможно (вам нужно изменить весь клиентский код), вы можете извлечь «зеркало» интерфейса vendor.Database и вызвать его, например. mirror.Database; и скопируйте из интерфейса vendor.Database в интерфейс mirror.Database только нужные вам методы (с теми же сигнатурами).
Отредактируйте код клиента, чтобы использовать интерфейс mirror.Database, и позвольте RateLimitedDatabase реализовать этот mirror.Database интерфейс. Поскольку все сигнатуры методов одинаковы, переключение клиентского кода на зеркальный интерфейс должно быть безболезненным. RateLimitedDatabase, конечно же, делегирует vendor.Database реализацию.
(Я думаю, что то, что я описал, более или менее является шаблоном моста (используя интерфейс для «защиты» от базовых изменений), https://en.wikipedia.org/wiki/Bridge_pattern)
В аспектно-ориентированном программировании есть решение этой проблемы. Большинство фреймворков генерируют динамический прокси для вашего интерфейса, чтобы он всегда был синхронизирован.