Usar genéricos y / u objetos de Constructor para devolver superclase O subclase

Sep 18 2020

Resumen: ¿Es posible obtener constructores con diferentes argumentos para crear objetos de diferentes subclases en un método?


Tengo una clase (no abstracta) con una subclase que la extiende. La subclase tiene un campo adicional. Ambos son inmutables:

class Superclass{
    final Type type; // Type is an enum; It has a final double decayFactor for each type.
    final int amount;

    Superclass(Type type, int amount){//simple constructor initializing final fields
        ...
    }

    //decay method talked about below is implemented here
}

class Subclass extends Superclass{
    final boolean additionalProperty;

    Subclass(Type type, int amount, boolean additionalProperty){
        super(type, amount);
        this.additionalProperty = additionalProperty;
    }
}

Ahora me gustaría tener un método que devuelva un Objeto de la misma clase ( Subclasso Superclass) con una cantidad actualizada. Este método debería disminuir amounten un cierto porcentaje definido por la enumeración Type. Este método simple podría verse así (implementado en Superclass):

public Superclass decay(){
    return new Superclass(type, amount * type.getDecayFactor())
}

Ahora mi problema es que si tengo un Subclassy llamo a este método, devolvería un Superclassmétodo en uno. Perdería la información almacenada en additionalProperty. Entonces pensé en otras formas de resolver esto. La más sencilla sería la de sobrescribir decay()en Subclass. Sin embargo, descubrí que no puedo forzar a una subclase a anular este método. Entonces, si luego se agregan otras subclases, puede ocurrir la misma pérdida de información si no la implementan. Entonces intenté resolver esto usando genéricos para obtener el constructor de la clase:

public <T extends Superclass> Superclass decay() throws ...[Many exceptions]{
    int newAmount = type.getDecayFactor() * amount;
    Constructor<? extends Superclass> constructor = this.getClass().getConstructor(Type.class, int.class);
    return constructor.newInstance(type, amount);
}

Lo intenté, pero no pude usar el genérico en Tlugar de this.getClass().

Sin embargo, esto tampoco funciona. Todavía tendría que sobrescribirlo ya que Subclass (Type, int) no existe. Mi pensamiento es que si de alguna manera pudiera obtener todos los campos de la clase, podría llamar al (único) constructor de subclases O superclase y pasar todos los valores que necesita, solo modificando la cantidad. Intenté obtener un Constructor coincidente usando this.getClass().getFields(), pero dice: ... getConstructor () ... no es aplicable para los campos de argumento []. (¿Necesitaría el tipo de campos?)

Mi pregunta: ¿Es esto posible en absoluto? ¿Y es esta una implementación elegante o favorecida? ¿O debería indicar mejor que las subclases tienen que anular decay (), que en mi humilde opinión es más simple y legible? ¿Son necesarios los genéricos aquí? (Poco a poco empiezo a pensar que no ...)

Información adicional: primero tuve Superclase y Subclase como abstractos y para cada Typeenumeración una subclase propia extendiendo esos ( Superclasso Subclass). Pero luego tendría que escribir un código muy similar para cada una de esas clases. Así que puse la información contenida en una enumeración con campos finales (fácilmente podrían haber sido 100 clases que representan cosas similares)

El método decay () - está destinado a usarse así para "modificar" el inmutable Superclass/ Subclass:

Superclass someObject = newSuperclass/Subclass
...
someObject = someObject.decay();

Muchas gracias por sus respuestas.

Respuestas

1 AhmadShahwan Sep 18 2020 at 22:13

Creo que anular el método debería funcionar bien. Puede cambiar el tipo de devolución de decay()siempre que sea un subtipo de Superclass.

También puede aislar el cálculo de la lógica de caída en un método separado para evitar el código reductor.

class Superclass {
    final double rate;
    final int amount;

    Superclass(double rate, int amount) {
        this.rate = rate;
        this.amount = amount;
    }

    public Superclass decay() {
        return new Superclass(this.rate, this.calcuateDecay());
    }
    
    protected int calcuateDecay() {
        return (int) (this.amount * this.rate);
    }
}

class Subclass extends Superclass {
    final boolean additionalProperty;

    Subclass(double rate, int amount, boolean additionalProperty) {
        super(rate, amount);
        this.additionalProperty = additionalProperty;
    }
    
    @Override
    public Subclass decay() {
        return new Subclass(this.rate, this.calcuateDecay(), this.additionalProperty);
    }
}

Véalo funcionando aquí .

Anular aquí es opcional. Para reforzar aún más la seguridad del tipo, la interfaz genérica se puede definir de la siguiente manera

interface <T extends Superclass> Decayable<T> {
    T decay();
}

Superclassluego puede implementar Decayable<Superclass>y Subclasspuede implementar Decayable<Subclass>. Véalo en acción aquí .