Java, pattern per limitare l'utilizzo del campo nelle sottoclassi a dopo aver chiamato il metodo specifico

0

Voglio estendere la classe Base che è definita in API specifiche e estendere di nuovo l'estensione. Come puoi vedere qui sotto, il metodo draw della classe intermedia calcola la variabile top e ho bisogno di usarlo nella seconda sottoclasse:

abstract class Base{
    public abstract void draw();
}

class Sub1 extends Base{
    @Override
    public void draw() {
        //calculate top
        int top = 10;
        //use "top"
    }
}

class Sub11 extends Sub1{
    @Override
    public void draw() {
        super.draw();
        //how to use "top" of "super.draw"?
    }
}

Penso ci siano alcune opzioni:

  • ricalcolo top in draw() di Sub11 ma penso che violi DRY
  • estraendo top come campo:

class Sub1 extends Base{

    private int top;

    public int getTop() {
        return top;
    }

    @Override
    public void draw() {
        int top = 10;
        this.top = top;
    }
}

class Sub11 extends Sub1{
    @Override
    public void draw() {
        super.draw();
        //use "top" of "super.draw"
        int top = getTop();
    }

    private void method(){
        //use "super.top" without executing "super.draw"
        int top = getTop();
    }
}

Sembra che il secondo sia migliore, ma rende top accessibile in altri posti di Sub11 senza eseguire super.draw() e questo può causare bug. Anche commentare top con alcune cose come Do not use before calling "draw()" non può limitare l'utilizzo errato degli utenti prima di chiamare draw() . C'è un modo per rimuovere la capacità dell'utente di sbagliare erroneamente?

Modifica

  • top in Sub1 e Sub11 ha lo stesso motivo per essere calcolato
  • Posso creare un composite invece di estendere Sub1 in Sub11 , ma prima devo estendere Base in un'altra classe ( Sub2 ) e poi usare Sub1 e Sub2 in composito. È una buona pratica?
  • Non è possibile calcolare top su draw() , perché è necessario calcolare gli argomenti di input di draw() e draw() può chiamare più volte con argomenti diversi.
posta hasanghaforian 27.10.2018 - 20:58
fonte

3 risposte

2

Che ne dici di progettare un contratto più esplicito per le sottoclassi di Sub1 ?

abstract class Base {
    public abstract void draw();
}

class Sub1 extends Base {
    @Override
    public void draw() {
        //calculate top
        int top = 10;
        //use "top"

        drawWithTop(top);
    }

    protected void drawWithTop(int top) {}
}

class Sub11 extends Sub1 {
    @Override
    protected void drawWithTop(int top) {
        // you are in the "draw" context
        // use your top here
    }
}
    
risposta data 28.10.2018 - 05:00
fonte
2

I want to extend class Base which is defined in specific API and the extend the extended again.

Ugg, per favore non farlo. Le catene di ereditarietà lunghe causano problemi yo yo . Usa l'ereditarietà se devi entrare nell'API, ma una volta entrato non continuare a utilizzarlo più e più volte. Composizione dei preferiti .

recalculating top in draw() of Sub11 but I think it violates DRY

Sub11 ha una ragione diversa per calcolare top come fa? DRY dovrebbe essere mitigato tenendo conto del principio di singola responsabilità . La definizione moderna di cui è responsabile è una sola fonte di cambiamento. Un po 'più vecchio di saggezza in questo senso proviene da Matteo 6:24 "nessuno può servire due padroni". Non forzare top da calcolare in un posto se ci sono due diversi motivi per calcolare top certi modi che sembrano identici al momento. Non ripetere te stesso non riguarda ciò che scrivi con la tastiera. Riguarda cosa intendi.

extracting top as a field

.. è una pessima idea se significa che devi scrivere commenti come

Do not use before calling "draw()"

La soluzione semplice consiste nel fermare il caching del calcolo come effetto collaterale di draw()

class Sub1 extends Base{
    @Override
    public void draw() {
        //calculate top somewhere else
        int top = calculateTop();
        //use "top"
    }

    public int calculateTop() {
        return 10; //If the calculation is expensive and needed often cache it here
    }
}
    
risposta data 28.10.2018 - 03:15
fonte
0

Prima di tutto, sei sicuro che usare la doppia ereditarietà sia la strada giusta? In genere, è preferibile utilizzare invece la composizione:

abstract class Base {
    public abstract void draw();
}

class Sub1 extends Base {
    @Override
    public void draw() {
        // draw Sub1 stuff
    }
}

class Sub2 extends Base {

   private Base sub1 = new Sub1();

    @Override
    public void draw() {
        this.sub1.draw()
        // draw other sub2 stuff
    }
}

Per quanto riguarda ciò che fai con 'top', dipende da come viene usato esattamente e da cosa dipende. Se è qualcosa determinato dal codice che usa Sub1, come qualcosa che determina dove posizionarlo su una tela o finestra, allora può essere impostato usando un argomento costruttore:

abstract class Base {
    public abstract void draw();
}

class Sub1 extends Base {

    private int top;

    public Sub1(int top) {
       this.top = top;
    }
    @Override
    public void draw() {
        // draw Sub1 stuff using top
    }
}

class Sub2 extends Base {

    private int top;
    private Base sub1;

    public Sub2() {
        this.top = 10; // compute top
        this.sub1 = new Sub1(this.top);
    }

    @Override
    public void draw() {
        this.sub1.draw()
        // draw other sub2 stuff
    }
}

Se 'top' è qualcosa che è determinato da Sub1 stesso, come se fosse calcolato in base ad altre proprietà di Sub1, allora potresti usare un metodo per recuperarlo. Puoi aggiungere un metodo top a Base, aggiungerlo a Sub1 o creare una sottoclasse interface / abstract per le cose con un top. Uno di quelli probabilmente ha più senso dato la tua situazione.

A meno che non ci siano ovviamente altre classi con una proprietà 'top' simile che si intende utilizzare, vorrei semplicemente creare un metodo su Sub1 e non andare fuori bordo con la creazione di una gerarchia di tipi complicata. Sarebbe banale aggiungere una interfaccia HasTop in un secondo momento se si scopre che si finisce per aggiungerlo perché non infrange alcun codice già usando Sub1.

abstract class Base {
    public abstract void draw();
}

class Sub1 extends Base {

    private int top = 10;

    @Override
    public void draw() {
        // draw Sub1 stuff using top
    }

    public void top() {
        return this.top;
    }
}

class Sub2 extends Base {

    private Sub1 sub1 = new Sub1();

    @Override
    public void draw() {
        this.sub1.draw()
        int top = this.sub1.top();
        // draw other sub2 stuff
    }
}
    
risposta data 28.10.2018 - 06:17
fonte

Leggi altre domande sui tag