Типобезопасный способ повторного использования метода защитного копирования с учетом неизменяемой иерархии классов

#java #oop #inheritance #immutability

Вопрос:

У меня есть базовый класс и производный класс, оба предназначены для неизменяемости (ctor и геттеры опущены).:

 public class PageQuery<T> {
    private final T queryFilter;
    private PageCond pageCond; // pagination parameter

    // E withPageCond(PageCond newPageCond) { 
    //    return a brand new copy of `this` with `pageCond` replaced with `newPageCond`
    //    that can be reused across class hierarchy
    // }
}

public class PageSortQuery<T> extends PageQuery<T>{
    private final SortCond sortCond; // sorting parameter
}
 

Как добавить метод, возвращающий защитную копию this в базовый класс, чтобы все классы в этой иерархии могли извлечь из этого выгоду? Есть ли способ сделать это без clone() этого ?

Комментарии:

1. Можете ли вы определить защищенные поля и получить к ним доступ из подклассов?

2. @Барракуда, может быть. Но моя цель состоит в том, чтобы все классы были дополнительно добавлены в эту иерархию, чтобы извлекать выгоду withPageCond прозрачно с минимальными усилиями. Добавление кода в каждый подкласс не является разумным вариантом для моей цели.

3. Шаблон дизайна Builder — хорошая потенциальная замена вашему дизайну. Здесь PageQuery<T> невозможно предвидеть, как он будет получен, поэтому он не может контролировать создание экземпляров объектов из несуществующих подклассов.

4. @CedricSun, в каком смысле вы хотите, чтобы другие подклассы извлекли из этого выгоду? Есть ли какие-либо проблемы с определением метода копирования в суперклассе, который возвращает копию неизменяемого экземпляра и переопределяет этот метод в подклассах, поскольку они имеют доступ к полю своего суперкласса через методы получения?

5. Подклассы @Barracuda даже не должны знать о наличии PageCond поля. Все , что они знают, — это унаследованный метод withPageCond , вызов которого вернет копию this (с согласованным типом-типом вызывающего подкласса). Не нужно ничего отменять.

Ответ №1:

Я не знаю, имеете ли вы в виду что-то подобное, в этом случае, если вы вызовете метод withFilter, происхождение не изменится.

 @AllArgsConstructor
public class Query <T> {
  @Getter
  private final String name;
  @Getter
  private final Predicate<T> filter;

  public Query<T> withFilter(Predicate<T> filter){
    return new DelegatingQuery<T>(this){
      @Override
      public Predicate<T> getFilter() {
        return Query.this.filter;
      }
    };
  }

  static class DelegatingQuery<T> extends Query<T>{
    @Delegate
    private final Query<T> query;

    public DelegatingQuery(Query<T> query) {
      super(query.name,query.filter);
      this.query = query;
    }
  }

}
 

Комментарии:

1. Мне нравится, как это осталось filter окончательным, но смысл в том, чтобы один или несколько подклассов добавляли бизнес-поля в базу Query и использовали один и тот же withXXX() метод (типобезопасным способом).

2. Вы хотите, чтобы метод withXXX() возвращал экземпляр подкласса?

3. ДА. Я думаю о CRTP , как о java.lang.Enum том, чтобы базовый класс мог ссылаться на тип своего подкласса. Но это привело бы к 3-уровневому наследованию, поскольку должен быть абстрактный базовый класс, а CRTP с многоуровневым (более 2) наследованием всегда является проблемой (возможно, но там много усилий)… не говоря уже о расходах на техническое обслуживание. Также я не могу избежать clone этого до сих пор.