Implementando uma interface quando você não precisa de uma das propriedades


31

Bem direto. Estou implementando uma interface, mas há uma propriedade desnecessária para essa classe e, de fato, não deve ser usada. Minha idéia inicial era fazer algo como:

int IFoo.Bar
{
    get { raise new NotImplementedException(); }
}

Suponho que não há nada errado com isso, por si só, mas não parece "certo". Alguém já se deparou com uma situação semelhante antes? Se sim, como você o abordou?


11
Lembro-me vagamente de que havia alguma classe semi-comumente usada em C # que implementa uma interface, mas afirma explicitamente na documentação que um determinado método não foi implementado. Vou tentar ver se consigo encontrá-lo.
Mago Xy

Eu definitivamente ficaria interessado em ver isso, se você puder encontrá-lo.
Chris Pratt

23
Posso apontar vários casos disso na biblioteca do .NET - E TODOS SÃO RECONHECIDOS COMO MAUS ERROS TERRÍVEIS . Esta é uma violação icônico e comum da substituição de Liskov - razões não para violar LSP pode ser encontrado em minha resposta aqui
Jimmy Hoffa

3
Você precisa implementar essa interface específica ou pode introduzir uma superinterface e usá-la?
Christopher Schultz

6
"uma propriedade desnecessária para esta classe" - Se uma parte de uma interface é necessária, depende dos clientes da interface, não dos implementadores. Se uma classe não puder implementar razoavelmente um membro de uma interface, a classe não será a mais adequada para a interface. Isso pode significar que a interface foi mal projetada - provavelmente tentando fazer demais -, mas isso não ajuda a classe.
Sebastian Redl

Respostas:


51

Este é um exemplo clássico de como as pessoas decidem violar o Princípio da Subtituição de Liskov. Eu o desencorajo fortemente, mas encorajaria possivelmente uma solução diferente:

  1. Talvez a turma que você está escrevendo não forneça a funcionalidade prescrita pela interface se ela não tiver uso de todos os membros da interface.
  2. Como alternativa, essa interface pode estar fazendo várias coisas e pode ser separada de acordo com o Princípio de Segregação da Interface.

Se o primeiro for o seu caso, apenas não implemente a interface nessa classe. Pense nisso como uma tomada elétrica onde o furo do aterramento é desnecessário, para que ele não se encaixe no solo. Você não conecta nada com o chão e não é grande coisa! Mas assim que você usa algo que precisa de terreno - você pode ter uma falha espetacular. Melhor não perfurar um buraco falso-terra. Então se sua classe não realmente fazer o que a interface tem a intenção, não implementar a interface.


Aqui estão alguns trechos rápidos da wikipedia:

O Princípio de Substituição de Liskov pode ser simplesmente formulado como: "Não fortaleça as pré-condições e não enfraqueça as pós-condições".

Mais formalmente, o princípio de substituição de Liskov (LSP) é uma definição específica de uma relação de subtipo, chamada subtipo comportamental (forte), que foi inicialmente introduzida por Barbara Liskov em um discurso de conferência de 1987 intitulado Abstração e hierarquia de dados. É uma relação semântica e não meramente sintática, pois pretende garantir a interoperabilidade semântica dos tipos em uma hierarquia , [...]

Para interoperabilidade semântica e substituibilidade entre diferentes implementações dos mesmos contratos - é necessário que todos eles se comprometam com os mesmos comportamentos.


O Princípio de Segregação de Interface fala da idéia de que as interfaces devem ser separadas em conjuntos coesos, de modo que você não exija uma interface que faça muitas coisas díspares quando desejar apenas um recurso. Pense novamente na interface de uma tomada elétrica, ela também pode ter um termostato, mas isso dificultaria a instalação de uma tomada elétrica e o uso mais difícil para fins de não aquecimento. Como uma tomada elétrica com termostato, interfaces grandes são difíceis de implementar e difíceis de usar.

O princípio de segregação de interface (ISP) afirma que nenhum cliente deve ser forçado a depender dos métodos que não usa. [1] O ISP divide interfaces muito grandes em menores e mais específicas, para que os clientes precisem apenas conhecer os métodos que lhes interessam.


Absolutamente. Provavelmente é por isso que não me pareceu certo, em primeiro lugar. Às vezes, você só precisa de um lembrete gentil de que está fazendo algo estúpido. Obrigado.
Chris Pratt

@ChrisPratt, é um erro muito comum de cometer, é por isso que os formalismos podem ser valiosos - classificar os cheiros de códigos ajuda a identificá-los mais rapidamente e a recuperar as soluções usadas anteriormente.
Jimmy Hoffa

4

Parece bom para mim, se esta é a sua situação.

No entanto, parece-me que sua interface (ou seu uso) será interrompida se uma classe derivada não implementar realmente tudo isso. Considere dividir essa interface.

Isenção de responsabilidade: isso requer herança múltipla para funcionar corretamente, e não tenho idéia se o C # suporta isso.


6
O C # não suporta herança múltipla de classes, mas suporta interfaces.
mgw854

2
Eu acho que você está certo. Afinal, a interface é um contrato, e mesmo que eu saiba que essa classe em particular não será usada de maneira a quebrar qualquer coisa que utilize a interface se essa propriedade estiver desabilitada, isso não é óbvio.
Chris Pratt

@ChrisPratt: Sim.
Lightness Races com Monica

4

Eu me deparei com esta situação. De fato, como apontado em outro lugar, o BCL tem tais instâncias ... tentarei fornecer melhores exemplos e fornecer algumas justificativas:

Quando você já possui uma interface que mantém por motivos de compatibilidade e ...

  • A interface contém membros obsoletos ou descartados. Por exemplo BlockingCollection<T>.ICollection.SyncRoot(entre outros), embora ICollection.SyncRootnão seja obsoleto, ele será lançado NotSupportedException.

  • A interface contém membros documentados para serem opcionais e que a implementação pode gerar a exceção. Por exemplo, no MSDN sobre IEnumerator.Resetisso, diz:

O método Reset é fornecido para interoperabilidade COM. Não precisa necessariamente ser implementado; em vez disso, o implementador pode simplesmente lançar uma NotSupportedException.

  • Por um erro no design da interface, deveria ter sido mais de uma interface em primeiro lugar. É um padrão comum na BCL implementar versões somente leitura de contêineres NotSupportedException. Eu já fiz isso, é o que se espera agora ... faço ICollection<T>.IsReadOnlyretornotrue para que você possa dizer a eles. O design correto seria ter uma versão legível da interface e, em seguida, a interface completa herda disso.

  • Não há interface melhor para usar. Por exemplo, eu tenho uma classe que permite acessar itens por índice, verificar se ele contém um item e em qual índice, tem algum tamanho, você pode copiá-lo para uma matriz ... parece um trabalho, IList<T>mas minha classe tem um tamanho fixo e não suporta adicionar nem remover, por isso funciona mais como uma matriz do que como uma lista. Mas não IArray<T>no BCL .

  • A interface pertence a uma API que é portada para várias plataformas e, na implementação de uma plataforma específica, algumas partes dela não são suportadas. Idealmente, haveria alguma maneira de detectá-lo, para que o código portátil que usa essa API possa decidir o que quer ou não chamar essas partes ... mas se você chamá-las, é totalmente apropriado obtê-las NotSupportedException. Isso é particularmente verdade, se for uma porta para uma nova plataforma que não estava prevista no design original.


Considere também por que não é suportado?

Às vezes InvalidOperationExceptioné uma opção melhor. Por exemplo, mais uma maneira de adicionar polimorfismo em uma classe é ter várias implementações de uma interface interna e seu código está escolhendo qual instanciar, dependendo dos parâmetros fornecidos no construtor da classe. [Isso é particularmente útil se você souber que o conjunto de opções é fixo e não deseja permitir que classes de terceiros sejam introduzidas por injeção de dependência.] Fiz isso para suportar o ThreadLocal porque a implementação de rastreamento e não rastreamento é distante demais, e o que ThreadLocal.Valueslança na implementação sem rastreamento?InvalidOperationExceptionmesmo assim, não depende do estado do objeto. Nesse caso, eu mesmo apresentei a classe e sabia que esse método tinha que ser implementado apenas lançando uma exceção.

Às vezes, um valor padrão faz sentido. Por exemplo, no ICollection<T>.IsReadOnlymencionado acima, faz sentido retornar apenas "verdadeiro" ou "falso", dependendo do caso. Então ... qual é a semântica IFoo.Bar? talvez haja algum valor padrão sensato para retornar.


Adendo: se você está no controle da interface (e não precisa ficar com ela por compatibilidade), não deve haver um caso em que você precise jogar NotSupportedException. No entanto, pode ser necessário dividir a interface em duas ou mais interfaces menores para se adequar ao seu caso, o que pode levar à "poluição" em situações extremas.


0

Alguém já se deparou com uma situação semelhante antes?

Sim, os designers da biblioteca .Net fizeram. A classe Dictionary faz o que você está fazendo. Ele usa implementação explícita para ocultar efetivamente * alguns dos métodos IDictionary. É explicado melhor aqui , mas, para resumir, para usar os métodos Add, CopyTo ou Remove do Dictionary que levam KeyValuePairs, você deve primeiro converter o objeto em um IDictionary.

* Não está "ocultando" os métodos no sentido estrito da palavra "ocultar", como a Microsoft usa . Mas não conheço um termo melhor nesse caso.


... o que é terrível.
Lightness Races com Monica

Por que o voto negativo?
user2023861

2
Provavelmente porque você não respondeu à pergunta. Ish. Tipo de.
Lightness Races com Monica

@LightnessRacesinOrbit, atualizei minha resposta para torná-la mais clara. Espero que ajude o OP.
user2023861

2
Parece que responde a pergunta para mim. O fato de ser uma idéia boa ou ruim não parece estar dentro do escopo da pergunta, mas ainda pode ser útil para as respostas indicarem.
Ellesedil

-6

Você sempre pode implementar e retornar um valor benigno ou valor padrão. Afinal, é apenas uma propriedade. Pode retornar 0 (a propriedade padrão de int) ou qualquer valor que faça sentido na sua implementação (int.MinValue, int.MaxValue, 1, 42, etc ...)

//We don't officially implement this property
int IFoo.Bar
{
     { get; }
}

Lançar uma exceção parece uma má forma.


2
Por quê? Como é melhor retornar dados incorretos?
Lightness Races com Monica

11
Isso quebra o LSP: veja a resposta de Jimmy Hoffa para uma explicação do porquê.

5
Se não houver valores de retorno corretos possíveis, é melhor lançar uma exceção do que retornar um valor incorreto. Não importa o que você faça, seu programa funcionará mal se invocar acidentalmente essa propriedade. Mas se você lançar uma exceção, será óbvio por que está com defeito.
Tanner Swett

Agora eu não sei como o valor seria incorreto quando você está implementando a interface! É a sua implementação, pode ser o que você quiser.
Jon Raynor

E se o valor correto fosse desconhecido no tempo de compilação?
Andy
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.