Curiously Recurring Template Pattern
Nombre / clasificación
Curiously Recurring Template Pattern
También conocido como
Recursive Type Bound, Recursive Generic
Propósito
Permitir que los componentes derivados hereden ciertas funcionalidades de un componente base que sean compatibles con el tipo derivado.
Explicación
Un ejemplo real
Para una promoción de artes marciales mixtas que esté planificando un evento, es crucial asegurarse de que los combates se organicen entre atletas de la misma categoría de peso. Así se evitan los enfrentamientos entre luchadores de tallas muy diferentes, como un peso pesado contra un peso gallo.
En pocas palabras
Hacer que ciertos métodos dentro de un tipo acepten argumentos específicos de sus subtipos.
Wikipedia dice
El patrón de plantilla curiosamente recurrente (CRTP) es un modismo, originalmente en C++, en el que una clase X deriva de una instanciación de plantilla de clase usando la propia X como argumento de plantilla.
Ejemplo programático
Definamos la interfaz genérica Fighter
public interface Fighter<T> {
void fight(T t);
}
La clase MMAFighter
se utiliza para crear luchadores que se distinguen por su categoría de peso.
public class MmaFighter<T extends MmaFighter<T>> implements Fighter<T> {
private final String name;
private final String surname;
private final String nickName;
private final String speciality;
public MmaFighter(String name, String surname, String nickName, String speciality) {
this.name = name;
this.surname = surname;
this.nickName = nickName;
this.speciality = speciality;
}
@Override
public void fight(T opponent) {
LOGGER.info("{} is going to fight against {}", this, opponent);
}
@Override
public String toString() {
return name + " \"" + nickName + "\" " + surname;
}
Los siguientes son algunos subtipos de MmaFighter
class MmaBantamweightFighter extends MmaFighter<MmaBantamweightFighter> {
public MmaBantamweightFighter(String name, String surname, String nickName, String speciality) {
super(name, surname, nickName, speciality);
}
}
public class MmaHeavyweightFighter extends MmaFighter<MmaHeavyweightFighter> {
public MmaHeavyweightFighter(String name, String surname, String nickName, String speciality) {
super(name, surname, nickName, speciality);
}
}
Un luchador puede enfrentarse a un oponente de la misma categoría de peso, si el oponente es de una categoría de peso diferente
se produce un error.
MmaBantamweightFighter fighter1 = new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai");
MmaBantamweightFighter fighter2 = new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo");
fighter1.fight(fighter2); // This is fine
MmaHeavyweightFighter fighter3 = new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing");
MmaHeavyweightFighter fighter4 = new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu");
fighter3.fight(fighter4); // This is fine too
fighter1.fight(fighter3); // This will raise a compilation error
Diagrama de clases
Aplicabilidad
Utilice el Patrón de Plantilla Curiosamente Recurrente cuando
- Tienes conflictos de tipos al encadenar métodos en una jerarquía de objetos
- Desea utilizar un método de clase parametrizado que pueda aceptar subclases de la clase como argumentos, permitiendo que se aplique a objetos que heredan de la clase
- Desea que ciertos métodos funcionen sólo con instancias del mismo tipo, por ejemplo, para lograr la comparabilidad mutua.
Tutoriales
- El blog de NuaH
- Respuesta de Yogesh Umesh Vaity a ¿Qué significa "Recursive type bound" en Generics?
Usos conocidos
Créditos
- How do I decrypt "Enum<E extends Enum<E>>"?
- Chapter 5 Generics, Item 30 in Effective Java