Self-referential generic, часть 2



В прошлом посте мы выяснили, зачем self-referential generic нужен в Enum: для использования дочернего типа при реализации интерфейса родителя. Это довольно экзотичный кейс. Сегодня покажу более практичный пример, как дженерики облегчили работу с иерархией и неизменяемыми переменными.



Дано: класс Delivery с информацией о доставке. Метод cancelled делает заказ недействительным. У класса есть наследник FastDelivery, в котором дополнительно хранится ID курьера:



class Delivery {

final long id;

final boolean isActive;



public Delivery(long id, boolean isActive) {…}



public Delivery cancelled() {

return new Delivery(this.id, false);

}

}



class FastDelivery extends Delivery {

private final long courierId;



public FastDelivery(…) {…}



public long getCourierId() {

return courierId;

}

}



Проблема: метод cancelled возвращает объект типа Delivery, и мы теряем информацию о курьере:



FastDelivery fast = new FastDelivery(…);

Delivery cancelled = fast.cancelled();



long id = fast.getCourierId();



В такой ситуации помогут self-referential generic и небольшой обходной манёвр:



🔸 Добавляем параметр в родителя

public class DeliveryᐸT extends DeliveryᐸTᐳᐳ



🔸 Создаём метод create, который возвращает нужный экземпляр

protected T create(long id, boolean isActive) {

return (T) new Delivery(id, isActive);

}



🔸 Используем этот метод в cancelled

public T cancelled() {

return create(this.id, false);

}



🔸 Определяем параметр в наследнике

public class FastDelivery extends DeliveryᐸFastDeliveryᐳ



🔸 Переопределяем метод create в наследнике

protected FastDelivery create(long id, boolean isActive) {

return new FastDelivery(this.id, this.isActive, courierId);

}



Всё! Теперь информация не теряется:

FastDelivery fast = new FastDelivery(…);

FastDelivery cancelled = fast.cancelled();



long id = cancelled.getCourierId();



Здесь используется комбо двух приёмов:



🔹 Метод create и его переопределение позволяют использовать поля, доступные в наследнике и вернуть нужный объект

🔹 Self-referential generic помогает вернуть нужный тип в методе cancelled



Готовый код доступен здесь



Резюме



Рассмотрите использование self-referential generic, когда



▫️ У вас есть иерархия

▫️ Родительский тип упоминается в аргументах или возвращаемом значении



Дополнительная типизация снизит количество кода, вытащит ошибки на этап компиляции и для некоторых случаев окажется очень изящным решением