Princípio de substituição de Liskov: Se o subtipo possui algum comportamento extra implementado, que não está presente no tipo, então esta violação do LSP?

8

Na minha busca por escrever um código melhor e mais limpo, estou aprendendo sobre os princípios do SOLID. Nisso, o LSP está se mostrando pouco difícil de entender adequadamente.

Minha dúvida é: se eu tiver alguns métodos extras no meu subtipo, S, que não existiam no tipo, T, isso sempre será uma violação do LSP? Se sim, então como faço para extendminhas aulas?

Por exemplo, digamos que temos um Birdtipo. E seus subtipos são Eaglee Humming Bird. Agora, ambos os subtipos têm algum comportamento comum como o Bird. Mas Eagletambém tem um bom comportamento predatório (que não está presente no Birdtipo geral ), que eu quero usar . Portanto, agora não poderei fazer isso:

Bird bird = new Eagle();

Então, Eagleesse comportamento extra está quebrando o LSP?

Se sim, isso significa que não posso estender minhas classes porque isso causaria violação do LSP?

class Eagle extends Bird {
   //we are extending Bird since Eagle has some extra behavior also
}

A extensão de classes deve ser permitida de acordo com o princípio Aberto / Fechado, certo?

Agradecemos antecipadamente por responder! Como você pode ver claramente, o LSP me confundiu como tudo.

Editar: Consulte esta resposta SO. Nisto novamente, quando Cartem comportamento adicional como ChangeGear, viola o LSP. Então, como estendemos uma classe, sem violar o LSP?

user270386
fonte
Eu passei por isso, mas não respondeu à minha pergunta. Eu li muitas respostas, na verdade, mas nenhuma ajuda até agora.
User270386
1
está lá na resposta principal, você já leu: toda vez que der uma classe de outra, pense na classe base e no que as pessoas podem assumir sobre ela ... Então pense "essas suposições permanecem válidas na minha subclasse?" Caso contrário, repensar seu design.
Gnat
@gnat, Sim, eu fiz, mas sou um pouco lenta :) E eu geralmente preciso de mais explicações do que outras podem exigir. Depois de ler a resposta completa de David Arno, sou capaz de me relacionar com essa linha agora.
User270386
1
@FrankHileman muitos de nós trabalhamos com compiladores e bases de código que são abaixo do ideal. Mesmo se não o fizermos, ainda é bom quando os humanos também entendem como respeitá-los.
Candied_orange

Respostas:

10

Minha dúvida é: e se eu tiver alguns métodos extras no meu subtipo, S, que não existiam no tipo, T, isso sempre será uma violação do LSP?

Resposta muito simples: não.

O ponto para o LSP é que ele Sdeve ser substituído T. Portanto, se Timplementa uma deletefunção, também Sdeve implementá-la e deve executar uma exclusão quando chamada. No entanto, Sé gratuito adicionar funcionalidades adicionais além do que é Tfornecido. Os consumidores de a T, ao receberem um S, não teriam conhecimento dessa funcionalidade extra, mas é permitido que os consumidores de Sutilizem diretamente.

Um exemplo altamente artificial de como o princípio pode ser violado pode ser:

class T
{
    bool delete(Item x)
    {
        if (item exists)
        {
            delete it
            return true;
        }
        return false;
    }
}

class S extends T
{
    bool delete(Item x)
    {
        if (item doesn't exist)
        {
            add it
            return false;
        }
        return true;
    }
}

Resposta um pouco mais complexa: não, desde que você não comece a afetar o estado ou outro comportamento esperado do tipo base.

Por exemplo, o seguinte seria uma violação:

class Point2D
{
    private readonly double _x;
    private readonly double _y;

    public virtual double X => _x;
    public virtual double Y => _y;

    public Point2D(double x, double y) => (_x, _y) = (x, y);
}

class MyPoint2D : Point2D
{
    private double _x;
    private double _y;

    public override double X => _x;
    public override double Y => _y;

    public MyPoint2D(double x, double y) : 
        base(x, y) => (_x, _y) = (x, y);

    public void Update(double x, double y) => (_x, _y) = (x, y);
}

O tipo Point2D,, é imutável; seu estado não pode ser alterado. Com MyPoint2D, eu deliberadamente contornei esse comportamento para torná-lo mutável. Isso quebra a restrição de histórico Point2De, portanto, é uma violação do LSP.

David Arno
fonte
Talvez uma boa adição a essa resposta seja um exemplo de comportamento que possa ser adicionado a uma deletefunção que seria uma violação do LSP?
Andy Hunt
1
@ user270386: Não. O LSP não é sobre a estrutura da subclasse, é sobre o comportamento do código da subclasse, comparado ao comportamento da classe base. Não é a funcionalidade extra por si só que viola a restrição do histórico; em vez disso, a restrição será violada se essa nova funcionalidade fizer algo inesperado (por exemplo, proibido) na classe base (e isso inclui coisas que podem ser expressas através da linguagem, bem como coisas que só podem ser expressas através da documentação).
Filip Milovanović
1
Pode-se notar que, se T é uma interface estrita, uma classe totalmente abstrata ou mesmo o padrão de objeto nulo, então S é praticamente a estrutura. T é código. Não é necessariamente uma especificação, um documento de requisitos ou um proprietário do produto. Só nos importamos com violações do LSP quando T está sendo usado para manifestar uma restrição que deve sempre ser mantida. Nem toda classe faz isso. Porém, se você disser ao S para excluir e, por design, o S não fará nada melhor que o restante do código seja bom. Não posso te dizer se isso é verdade. Só pode lhe dizer que é melhor verificar antes de fazer isso.
Candied_orange
1
(continuação) IMO, é o requisito de substituibilidade que impulsiona tudo isso. A substituibilidade realmente decorre da capacidade de tratar duas implementações (comportamentos) diferentes como equivalentes em um nível mais alto de abstração prescrito pelo contrato definido pelo supertipo. Estrutura é, nesse sentido, um meio para um fim.
Filip Milovanović
1
@DavidArno "o código do tipo base" - não é uma especificação, nem acessível necessariamente.
Frank Hileman
3

Claro que não. Se o objeto Eagle puder ser usado por qualquer código que espere um pássaro ou subclasse e se comporte como um pássaro deve se comportar, você estará bem.

Obviamente, o comportamento da Águia só pode ser usado por código que sabe que é um objeto desse tipo. Esperamos que algum código crie explicitamente um objeto Eagle e o use como objeto Eagle, além de poder usar qualquer código que espere objetos Bird.

gnasher729
fonte