Il chiamare un metodo protetto virtuale da una classe base viola l'LSP?

5

Diciamo che abbiamo questa classe base:

class MyBase
{
    public string Name { get; set; }

    public string GetDescription()
    {
        var descriptionList = new List<string>() { this.Name };
        descriptionList.AddRange(this.ExtraDescriptions.ToList());
        return string.Join(", ", descriptionList();
    }

    protected virtual IEnumerable<string> ExtraDescriptions()
    {
        return new List<string>();
    }
}

Quindi aggiungi una classe derivata:

class MyDerived : MyBase
{
    protected override IEnumerable<string> ExtraDescriptions()
    {
        return new List<string>() { "a", "b" };
    }
}

Mi sembra che questo violi il LSP (perché stiamo cambiando il comportamento del metodo GetDescription ), eppure è anche una caratteristica fondamentale della programmazione orientata agli oggetti. La classe base ovviamente dichiara la sua intenzione di consentire l'override del metodo ExtraDescriptions , ma questo non è necessariamente visibile a un consumatore della classe.

Da quando ho saputo della LSP, questo problema mi ha davvero tormentato. Quindi:

  1. Questo viola l'LSP?
  2. Qual è l'alternativa corretta a questo?
posta Scott Whitlock 17.11.2011 - 13:44
fonte

7 risposte

8

LSP richiede solo che una proprietà di una super-classe sia vera per tutti i suoi derivati. Per "proprietà", in questo contesto, intendiamo che "fornirà un elenco di descrizioni". L'implementazione di ciò è irrilevante.

Per quanto posso vedere, non vi è alcuna violazione nel tuo caso.

Le proprietà in LSP sono molto più circa le ipotesi che possono essere fatte dal codice chiamante di quanto lo siano sull'implementazione di funzionalità, o persino sul comportamento.

L'esempio canonico è "un quadrato è un rettangolo specializzato". Ma i rettangoli hanno proprietà di altezza e larghezza, che il codice di chiamata può assumere sono indipendenti l'uno dall'altro. Mentre un quadrato ha solo una dimensione, quindi se dico rect.X = 3; rect.Y = 2; e l'oggetto è un rettangolo, ottengo un rettangolo 3x2; ma con lo stesso codice chiamante su un quadrato, ottengo un rettangolo 2x2.

    
risposta data 17.11.2011 - 14:18
fonte
5

È valido e chiamato modello metodo del modello. È utile ridurre l'accoppiamento sequenziale.

In alternativa, puoi utilizzare chiusure, metaprogrammazione o schemi come la politica.

vedi: link per il modello di metodo del modello e molti altri.

    
risposta data 17.11.2011 - 13:48
fonte
3

Penso che dipenda da quale è il comportamento / contratto previsto (pubblicato / documentato) della classe base / dell'interfaccia.

Se GetDescription è inteso per restituire un testo di descrizione generale, definito genericamente (elenco di parole separato da virgole), è perfettamente appropriato per le diverse classi derivate aggiungere elementi diversi al proprio output tramite l'override di ExtraDescriptions . Questo è lo schema Metodo modello in azione. [Aggiorna] In altre parole, LSP è non violato in questo caso. [/ Update]

Se OTOH viene dichiarato di restituire sempre il nome dell'oggetto e nient'altro, l'uso di un metodo Template per aprire la possibilità di rompere il contratto con l'interfaccia non avrebbe il minimo significato. [Aggiorna] In altre parole, LSP verrebbe violato in questo caso. [/ Update]

    
risposta data 17.11.2011 - 13:50
fonte
1

La classe base, non la classe derivata, è responsabile per LSP. Quindi il creatore della classe base dovrebbe rendere la sua classe aperta per la sostituzione. Ciò significa che se la classe base rende un metodo virtual, dovrebbe essere sovrascrivibile da qualsiasi funzione senza modificare le proprietà desiderabili della funzionalità della classe.

    
risposta data 17.11.2011 - 14:01
fonte
1

Ok, in primo luogo, quello che stai facendo non viola necessariamente LSP, se capisco correttamente il contesto. LSP legge:

Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

Ora traduciamo questo in classi:

Let q(x) be a property of all instances x of class T. Then q(y) should also be a property of all instances y of any class S that is a subclass of T.

Ora dato il contesto, quel q qui è fondamentalmente: una chiamata al GetDescription -method dell'oggetto restituirà una descrizione significativa di quell'oggetto .

In questo caso non lo stai violando. Al contrario, si potrebbe obiettare che lo si sta effettivamente facendo rispettare, perché se non si sostituiva il metodo di conseguenza, il risultato di GetDescription in effetti non sarebbe significativo e quindi LSP non avrebbe tenere.
Per contrastare questo, ecco una vera violazione:

class MyUndescribable : MyBase
{
    override public string GetDescription()
    {
        throw "can't touch this!";
    }
}

Direi che applicare l'LSP a membri non pubblici è un po 'inutile, perché qualsiasi ipotesi sui membri non pubblici di un'istanza significa violare l'incapsulamento. L'unica volta in cui questo è vale la pena di considerare è, quando un'istanza di A chiama un metodo protetto di un'altra istanza di A (che potrebbe effettivamente essere un'istanza di una classe B che ha sovrascritto questo metodo).

In secondo luogo, ciò che stai facendo sarebbe meglio raggiunto attraverso la composizione, seguendo il DIP. La logica da ExtraDescriptions deve essere passata come oggetto funzione o come servizio astratto. In questo modo potresti ancora ricavare MyDerived da MyBase (se lo desideri davvero) e passare la logica al super costruttore, ad esempio. Pertanto MyDerived sarebbe ulteriormente disaccoppiato da MyBase .

    
risposta data 17.11.2011 - 15:11
fonte
0

Sulla base delle risposte di @deadalnix e @ PeterTorok, ecco, penso, una migliore implementazione che non violi LSP:

abstract class AbstractBase
{
    public string Name { get; set; }

    public string GetDescription()
    {
        var descriptionList = new List<string>() { this.Name };
        descriptionList.AddRange(this.ExtraDescriptions().ToList());
        return string.Join(", ", descriptionList();
    }

    protected abstract IEnumerable<string> ExtraDescriptions();
}

class DefaultImplementation : AbstractBase
{
    protected override IEnumerable<string> ExtraDescriptions()
    {
        return new List<string>() { };
    }
}

class OtherImplementation : AbstractBase
{
    protected override IEnumerable<string> ExtraDescriptions()
    {
        return new List<string>() { "a", "b" };
    }
}

Penso anche che potresti fare ciò che @deadalnix ha detto in un commento, che è spostare l'implementazione predefinita nella classe astratta, in questo modo:

abstract class AbstractBase
{
    public string Name { get; set; }

    public string GetDescription()
    {
        var descriptionList = new List<string>() { this.Name };
        descriptionList.AddRange(this.ExtraDescriptions().ToList());
        return string.Join(", ", descriptionList();
    }

    protected virtual IEnumerable<string> ExtraDescriptions()
    {
        return new List<string>() { };
    }
}

class DefaultImplementation : AbstractBase
{
}

class OtherImplementation : AbstractBase
{
    protected override IEnumerable<string> ExtraDescriptions()
    {
        return new List<string>() { "a", "b" };
    }
}

Ciò consente a qualsiasi classe che sovrascrive di riutilizzare l'implementazione predefinita.

    
risposta data 17.11.2011 - 14:04
fonte
0

Il Principio di sostituzione di Liskov non richiede che il comportamento visibile non cambi - richiede che il comportamento rimanga come definito e documentato. Per un metodo di "descrizione", è probabile che il comportamento sia definito come "restituisce una stringa con una descrizione umana leggibile adatta dell'oggetto". È molto probabile che il comportamento esatto deve essere modificato per essere compatibile con la definizione e non essere in conflitto con LSP.

La classe base ha (si spera) requisiti definiti per la funzione virtuale protetta. Chiamarlo non viola LSP. Ovviamente una sottoclasse potrebbe fornire un'implementazione che viola LSP, ma questo non è l'errore delle classi di base.

    
risposta data 14.04.2016 - 10:53
fonte

Leggi altre domande sui tag