Principio di sostituzione di Liskov quando si implementano due interfacce indipendenti

0

Supponiamo di avere due interfacce non correlate con lo stesso metodo:

interface Table {
    /**
     * @param width (0 < width <= 100)
     */
    void setWidth(int width);
}

interface Square {
    /**
     * @param width (50 <= width <= 10000)
     */
    void setWidth(int width);
}

Quindi creo una classe che implementa entrambe le interfacce:

class SquareTable implements Square, Table {
    void setWidth(int width) {
        precondition = ...
        if (!precondition)
            throw new PreconditionFailedException("Precondition failed: ...");
    }
}

In che modo le espressioni di precondizione dovrebbero essere combinate per soddisfare il principio di sostituzione di Liskov? Dovrebbero e 'o o o ' ed? Cioè cosa dovrebbe essere scritto al posto di ... :

width > 0 && width <= 10000 // using OR
width >= 50 && width <= 100 // using AND

E quale regola dovrebbe essere usata per le postcondizioni?

interface Table {
    /**
     * @return width (0 < width <= 100)
     */
    int getWidth();
}

interface Square {
    /**
     * @return width (50 <= width <= 10000)
     */
    int getWidth();
}

class SquareTable implements Square, Table {
    int getWidth() {
        postcondition = ...
        if (!postcondition)
            throw new PostconditionFailedException("Postcondition failed: ...");
    }
}

width > 0 && width <= 10000 // using OR
width >= 50 && width <= 100 // using AND
    
posta ZhekaKozlov 05.09.2013 - 22:50
fonte

3 risposte

1

Requisiti

Dato che l'implementatore deve essere utilizzabile come tabella o quadrato, la precondizione deve essere l'unione ( OR ) delle due precondizioni dell'interfaccia (cioè larghezza > 0 & & width < = 10000).

Postcondizioni

L'implementazione deve soddisfare entrambe le post-condizioni dell'interfaccia, quindi la sua post-condizione potrebbe essere l'intersezione ( AND ) delle due precondizioni dell'interfaccia (cioè width > = 50 & width; lt; = 100) .

Implementazione

Una possibile implementazione che non viola LSP è quella di utilizzare limiti min / max per mantenere width > = 50 & & width < = 100, ad es. setWidth (10000) esegue efficacemente setWidth (100).

In generale, questa non è una soluzione molto buona in quanto è opaca per il chiamante setWidth (che potrebbe presumere che getWidth () restituisca il valore passato a setWidth). È molto preferibile (come dice Euforico) a non utilizzare un'implementazione per metodi da interfacce diverse .

    
risposta data 06.09.2013 - 21:53
fonte
2

In questo contesto setWidth e getWidth sono metodi differenti per entrambe le interfacce, anche se hanno lo stesso nome. La ragione di questo è che le condizioni pre / post, combinate con LSP diventano parte della firma del metodo. Quindi se le condizioni pre / post sono diverse, lo sono anche tutti i metodi.

Ecco perché dovresti implementarli come due metodi diversi. In C #, puoi utilizzare implementazione dell'interfaccia esplicita , che ti consente di implementare diversi metodi per ogni interfaccia, anche se hanno lo stesso nome. Java non ha qualcosa del genere, quindi devi o emularlo in qualche modo o provare a vivere senza di esso.

E solo i miei 2 centesimi, non dovresti creare condizioni pre / post per le proprietà, dovrebbe esserci solo una condizione che si applica sempre.

E a pensarci, in questo caso, il vincolo dovrebbe essere ANDed. Considera questo pezzo di codice:

SquareTable st = new SquareTable();
Table t = st;
Square s = st;
t.setWidth(20); // OK, because invariant for Table is 0 < w
int w = s.getWidth(); // returns 20, which breaks the postcondition of Square

In questo caso, sarebbe molto meglio se già fallisse su t.setWidth (20), secondo la mentalità "fallisci presto". Ecco perché ci vuole più tempo per eseguire il debug da s.getWidth() rispetto a t.setWidth(x) .

    
risposta data 06.09.2013 - 06:48
fonte
0

Il caso più semplice è che la condizione preliminare debba essere OR (se si soddisfano le condizioni per entrambe le interfacce, si può chiamare il metodo) e la post-condizione dovrebbe essere AND (il ritorno deve soddisfare tutti i vincoli applicabili, indipendentemente dall'interfaccia avevi).

È possibile allentare leggermente la post-condizione a seconda delle condizioni preliminari soddisfatte. Devi solo soddisfare le post-condizioni per l'interfaccia per cui hai soddisfatto le condizioni preliminari. Perché se non rispetti la precondizione per un'interfaccia, non dovresti essere chiamato per quell'interfaccia.

    
risposta data 06.09.2013 - 00:00
fonte

Leggi altre domande sui tag