Self-referential generic, часть 2
В прошлом посте мы выяснили, зачем self-referential generic нужен в
Дано: класс Delivery с информацией о доставке. Метод cancelled делает заказ недействительным. У класса есть наследник FastDelivery, в котором дополнительно хранится ID курьера:
🔸 Добавляем параметр в родителя
🔹 Метод create и его переопределение позволяют использовать поля, доступные в наследнике и вернуть нужный объект
🔹 Self-referential generic помогает вернуть нужный тип в методе cancelled
Готовый код доступен здесь
Резюме
Рассмотрите использование self-referential generic, когда
▫️ У вас есть иерархия
▫️ Родительский тип упоминается в аргументах или возвращаемом значении
Дополнительная типизация снизит количество кода, вытащит ошибки на этап компиляции и для некоторых случаев окажется очень изящным решением✨
В прошлом посте мы выяснили, зачем self-referential generic нужен в
Enum
: для использования дочернего типа при реализации интерфейса родителя. Это довольно экзотичный кейс. Сегодня покажу более практичный пример, как дженерики облегчили работу с иерархией и неизменяемыми переменными.Дано: класс Delivery с информацией о доставке. Метод cancelled делает заказ недействительным. У класса есть наследник FastDelivery, в котором дополнительно хранится ID курьера:
class Delivery {Проблема: метод cancelled возвращает объект типа 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;
}
}
FastDelivery fast = new FastDelivery(…);В такой ситуации помогут self-referential generic и небольшой обходной манёвр:
Delivery cancelled = fast.cancelled();
❌ long id = fast.getCourierId();
🔸 Добавляем параметр в родителя
public class DeliveryᐸT extends DeliveryᐸTᐳᐳ🔸 Создаём метод create, который возвращает нужный экземпляр
protected T create(long id, boolean isActive) {🔸 Используем этот метод в cancelled
return (T) new Delivery(id, isActive);
}
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, когда
▫️ У вас есть иерархия
▫️ Родительский тип упоминается в аргументах или возвращаемом значении
Дополнительная типизация снизит количество кода, вытащит ошибки на этап компиляции и для некоторых случаев окажется очень изящным решением✨