Entendo corretamente que o Princípio da Substituição de Liskov não pode ser observado em linguagens em que objetos podem se inspecionar, como é habitual em linguagens tipificadas por pato?
Por exemplo, em Ruby, se uma classe B
herda de uma classe A
, então para cada objeto x
de A
, x.class
retornará A
, mas se x
for um objeto de B
, x.class
não retornará A
.
Aqui está uma declaração do LSP:
Vamos q (x) ser um provable propriedade sobre objetos x do tipo T . Em seguida, q (Y) deve ser demonstrável para objectos y de tipo S , onde S é um subtipo de T .
Então, em Ruby, por exemplo,
class T; end
class S < T; end
viole o LSP neste formulário, como testemunhado pela propriedade q (x) =x.class.name == 'T'
Adição. Se a resposta for "sim" (LSP incompatível com a introspecção), minha outra pergunta seria: existe alguma forma "fraca" modificada de LSP que pode ser válida para uma linguagem dinâmica, possivelmente sob algumas condições adicionais e apenas com tipos especiais de propriedades .
Atualizar. Para referência, aqui está outra formulação de LSP que encontrei na web:
Funções que usam ponteiros ou referências a classes base devem poder usar objetos de classes derivadas sem conhecê-lo.
E outro:
Se S é um subtipo declarado de T, objetos do tipo S devem se comportar como objetos do tipo T devem se comportar, se forem tratados como objetos do tipo T.
O último é anotado com:
Observe que o LSP tem tudo a ver com o comportamento esperado dos objetos. Só se pode seguir o LSP se estiver claro sobre qual é o comportamento esperado dos objetos.
Isso parece ser mais fraco que o original e pode ser possível observar, mas eu gostaria de vê-lo formalizado, em particular explicado quem decide qual é o comportamento esperado.
O LSP não é uma propriedade de um par de classes em uma linguagem de programação, mas de um par de classes juntamente com um determinado conjunto de propriedades, satisfeito pela classe ancestral? Na prática, isso significaria que, para construir uma subclasse (classe descendente) respeitando o LSP, todos os usos possíveis da classe ancestral devem ser conhecidos? De acordo com o LSP, a classe ancestral deve ser substituída por qualquer classe descendente, certo?
Atualizar. Eu já aceitei a resposta, mas gostaria de adicionar mais um exemplo concreto de Ruby para ilustrar a pergunta. Em Ruby, cada classe é um módulo no sentido de que Class
classe é descendente de Module
classe. Contudo:
class C; end
C.is_a?(Module) # => true
C.class # => Class
Class.superclass # => Module
module M; end
M.class # => Module
o = Object.new
o.extend(M) # ok
o.extend(C) # => TypeError: wrong argument type Class (expected Module)
Respostas:
Aqui está o princípio real :
E o excelente resumo da Wikipedia :
E algumas citações relevantes do artigo:
Então, vamos à pergunta:
Não.
A.class
retorna uma classe.B.class
retorna uma classe.Como você pode fazer a mesma chamada no tipo mais específico e obter um resultado compatível, o LSP é retido. O problema é que, com linguagens dinâmicas, você ainda pode chamar as coisas no resultado, esperando que elas estejam lá.
Mas vamos considerar uma linguagem estaticamente estrutural (tipo pato). Nesse caso,
A.class
retornaria um tipo com uma restrição de que ele deve serA
ou um subtipo deA
. Isso fornece a garantia estática de que qualquer subtipo deA
deve fornecer um métodoT.class
cujo resultado seja um tipo que satisfaça essa restrição.Isso fornece uma afirmação mais forte que o LSP contém em idiomas que suportam digitação de patos e que qualquer violação do LSP em algo como Ruby ocorre mais devido ao mau uso dinâmico normal do que a uma incompatibilidade de design de idioma.
fonte
fail unless x.foo == 42
e um subtipo retorna 0, é a mesma coisa. Isso não é uma falha do LSP, é a operação normal do seu programa. Polimorfismo não é uma violação do LSP.No contexto do LSP, uma "propriedade" é algo que pode ser observado em um tipo (ou objeto). Em particular, fala sobre uma "propriedade comprovável".
Essa "propriedade" poderia existir um
foo()
método que não tem valor de retorno (e segue o contrato estabelecido em sua documentação).Certifique-se de não confundir esse termo com "propriedade", como em "
class
é uma propriedade de todos os objetos no Ruby". Essa "propriedade" pode ser uma "propriedade LSP", mas não é automaticamente a mesma!Agora, a resposta para suas perguntas depende muito de quão rigorosa você define "propriedade". Se você disser "a propriedade da classe
A
é que.class
retornará o tipo do objeto",B
na verdade ela possui essa propriedade.Se, no entanto, você define a "propriedade" para ser "
.class
retornosA
", então, obviamente,B
não não têm essa propriedade.No entanto, a segunda definição não é muito útil, pois você encontrou uma maneira geral de declarar uma constante.
fonte
.class
retornará o tipo do objeto". Se isso significax.class == x.class
, não é uma propriedade interessante.Pelo que entendi, não há nada na introspecção que seja incompatível com o LSP. Basicamente, desde que um objeto suporte os mesmos métodos que outro, os dois devem ser intercambiáveis. Ou seja, se o seu código espera um
Address
objeto, então não importa se é umCustomerAddress
ou umWarehouseAddress
, desde que ambos fornecem (por exemplo)getStreetAddress()
,getCityName()
,getRegion()
egetPostalCode()
. Você certamente poderia criar algum tipo de decorador que usa um tipo diferente de objeto e usa a introspecção para fornecer os métodos necessários (por exemplo, umaDestinationAddress
classe que pega umShipment
objeto e apresenta o endereço de entrega como umAddress
), mas não é obrigatório e certamente não impedir que o LSP seja aplicado.fonte
x.class.name
com'A'
, efetivamente tornando-sex.class.name
inútil .x.class.name == 'A'
é um anti-padrão na digitação de pato: afinal, a digitação de pato vem de "Se grasna e anda como um pato, é um pato". Portanto, se ele se comportaA
e respeita os contratos queA
A
Depois de examinar o artigo original de Barbara Liskov, descobri como concluir a definição da Wikipedia para que o LSP realmente pudesse ser satisfeito em quase qualquer idioma.
Primeiro de tudo, a palavra "comprovável" é importante na definição. Isso não é explicado no artigo da Wikipedia e "restrições" são mencionadas em outros lugares sem referência a ele.
Aqui está a primeira citação importante do artigo:
E aqui está o segundo, explicando o que é uma especificação de tipo :
Portanto, o LSP só faz sentido em relação a determinadas especificações de tipo e, para uma especificação de tipo apropriada (para a vazia, por exemplo), pode ser satisfeita provavelmente em qualquer idioma.
Considero a resposta de Telastyn a mais próxima do que eu estava procurando, porque as "restrições" foram mencionadas explicitamente.
fonte
x.class.name = 'A'
é comprovável para toda ax
classe,A
se você permitir muito conhecimento. A especificação do tipo não foi definida e sua relação exata com o LSP também não, embora informalmente tenham sido dadas algumas indicações. Já encontrei o que estava procurando no artigo de Liskov e respondi minha pergunta acima.x
desses tipos,x.woozle
produziráundefined
, nenhum tipo para o qualx.woozle
não produzaundefined
será um subtipo adequado. Se o supertipo não documentar nadax.woozle
, o fato de que o usox.woozle
do supertipo acabe resultandoundefined
não implicaria em nada o que poderia fazer no subtipo.Para citar o artigo da Wikipedia sobre LSP , "substituibilidade é um princípio na programação orientada a objetos". É um princípio e parte do design do seu programa. Se você escrever um código que dependa
x.class == A
disso, é o seu código que está violando o LSP. Observe que esse tipo de código quebrado também é possível em Java, sem necessidade de digitação de pato.Nada na digitação com patos quebra inerentemente o LSP. Somente se você abusar, como no seu exemplo.
Pensamento adicional: a verificação explícita da classe de um objeto não anula o objetivo da digitação do pato, afinal?
fonte
Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).
. A classe exata de um objeto NÃO é uma das "propriedades desejáveis do programa"; caso contrário, seria contrário não apenas à digitação com patos, mas também à subtipagem em geral, incluindo o sabor do Java.x.class == A
violação de LSP e de digitação de pato. Não faz sentido usar a digitação de pato se você for verificar os tipos reais.A
,B
) satisfaz o LSP ou não. Se o LSP depender do código usado em outro lugar, não será explicado qual código é permitido. Espero encontrar algo aqui: cse.ohio-state.edu/~neelam/courses/788/lwb.pdfLet 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
. É óbvio quex.class
não é uma das propriedades interessantes aqui; caso contrário, o polimorfismo de Java também não funcionaria. Não há nada inerente à digitação lenta no seu "problema x.class". Você concorda até agora?