То же стирание для подкласса, который отображает родительский метод в другой класс

Я пытаюсь реорганизовать DAO, чтобы сделать его более удобным для использования в нашей кодовой базе. В настоящее время у нас есть параметризованный AbstractDao, который принимает три типа:

  1. Таблица базы данных
  2. База данных pojo
  3. Другое сопоставленное представление pojo 2)

Так что в итоге это выглядит так:

public class AbstractDao<T extends DatabaseTable, R extends DatabaseRecord, M> {
  public AbstractDao(Connection connection, Mapper<R,M> mapper) {
  //save connection and mapper to protected variables
}
public List<M> insert(List<M> records) {
 connection.insertBulk(
   StreamEx.of(records).map(mapper::map).toList()
 );
 }
}

Однако это не работает в классическом случае DAO, когда мы имеем дело только с pojo и столом.

Однако здесь есть общая функциональность, которую можно абстрагировать в более базовый AbstractDao, который полезен для разных проектов. Что-то вроде:

AbstractDao<T extends DatabaseTable, R extends Record>

который имеет подкласс

AbstractMappedDao<T extends DatabaseTable, R extends Record, M> extends AbstractDao<T, R>

Аннотация имеет метод как:

public List<R> insert(List<R> records) {
  connection.insertBulk(records);
}

и у Mapped должен быть такой метод:

public List<M> insert(List<M> records) {
  super.insert(StreamEx.of(records).map(mapper::map).toList());
}

Тем не менее, это создает проблему «того же стирания», потому что вставка принимает список универсальных элементов.

Я попытался абстрагировать его в интерфейс:

public interface Dao<T> {
  public List<T> insert(List<T> records);
}

И заставляя Abstract реализовывать Dao, а Mapped - Dao, но, опять же, та же проблема.

Итак, мой вопрос, как лучше всего подойти к этой проблеме? Это работает, как и ожидалось, если я изменю подпись карты на что-то вроде:

insertMapped(List<M> mapped);

Но я бы предпочел сохранить контракт таким же.

Спасибо за помощь. С нетерпением жду обсуждения!

Всего 1 ответ


Когда дело доходит до компоновки поведения, всегда лучше использовать композицию, а не наследование. И это на самом деле ваш случай. mapper не увеличивает поведение, уже существующее в вашем Dao так же, как создает поведение, добавляя в него дополнительный уровень косвенности, например аспект.

Поэтому я рекомендую создать один класс AbstractDao с возможностью составления mappers (вы можете иметь только один, как вам нужно; но с составлением легко разрешить одному объекту Dao поддерживать несколько сопоставителей):

        private Map<Class, Function> mappers;

        public <M> void registerMapper(Class<M> mappingClass, Function<M, R> mapper) {
            mappers.put(mappingClass, mapper);
        }

Затем создайте метод insert который позволяет выполнять предварительное преобразование записей, если они не расширяют Record используя mappers в нем mappers , например:

        public <M> List<M> insert(List<M> records) {
            if (records.isEmpty()) return records;
            M rec = records.get(0);

            List<? extends Record> actualRecords = (rec instanceof Record) ? 
                    (List<Record>)records : createMappedRecords(records, rec.getClass());

            connection.insertBulk(actualRecords);
            return records;
        }

Это чище, надежнее и расширяемо, так как ваша вставка может быть сделана для централизованного решения всевозможных проблем с составными задачами. Полный код компиляции будет выглядеть примерно так:

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public class ParentErasure {

    public abstract class AbstractDao<T extends DatabaseTable, R extends Record> {
        private Connection connection;
        private Map<Class, Function> mappers;

        public <M> void registerMapper(Class<M> mappingClass, Function<M, R> mapper) {
            mappers.put(mappingClass, mapper);
        }

        public <M> List<M> insert(List<M> records) {
            if (records.isEmpty()) return records;
            M rec = records.get(0);

            List<? extends Record> actualRecords = (rec instanceof Record) ? 
                    (List<Record>)records : createMappedRecords(records, rec.getClass());

            connection.insertBulk(actualRecords);
            return records;
        }

        private <M> List<R> createMappedRecords(List<M> records, Class<? extends Object> recordsClazz) {
            Function<M, R> mapper = mappers.get(recordsClazz);
            return records.stream()
                    .map(mapper::apply)
                    .collect(Collectors.toList());
        }
    }

    public interface Dao<T> {
        public List<T> insert(List<T> records);
    }
}

class Record {}
class DatabaseTable {}
class DatabaseRecord {}
class Connection {
    public void insertBulk(List<? extends Record> records) {}
}

Надеюсь это поможет.


Есть идеи?

10000