Digitação 'mais solta' em C #, derrubando a árvore de herança
-
09-06-2019 - |
Pergunta
A pergunta que quero fazer é a seguinte:
Está derrubando a árvore de herança (ou seja,em direção a uma classe mais especializada) de dentro de uma classe abstrata desculpável, ou até mesmo uma coisa boa, ou é sempre uma má escolha com melhores opções disponíveis?
Agora, o exemplo de por que acho que pode ser usado para o bem.
Eu implementei recentemente Bencoding do protocolo BitTorrent em C#.Um problema bastante simples, como representar os dados.Eu escolhi fazer assim,
Nós temos um abstract BItem
classe, que fornece algumas funcionalidades básicas, incluindo o static BItem Decode(string)
que é usado para decodificar uma string Bencoded na estrutura necessária.
Existem também quatro classes derivadas, BString
, BInteger
, BList
e BDictionary
, representando os quatro tipos de dados diferentes que serão codificados.Agora, aqui está a parte complicada. BList
e BDictionary
ter this[int]
e this[string]
acessadores respectivamente para permitir acesso às qualidades semelhantes a array desses tipos de dados.
A parte potencialmente horrível está chegando agora:
BDictionary torrent = (BDictionary) BItem.DecodeFile("my.torrent");
int filelength = (BInteger)((BDictionary)((BList)((BDictionary)
torrent["info"])["files"])[0])["length"];
Bem, você entendeu...Ai, isso é difícil para os olhos, sem falar no cérebro.Então, introduzi algo extra na classe abstrata:
public BItem this[int index]
{
get { return ((BList)this)[index]; }
}
public BItem this[string index]
{
get { return ((BDictionary)this)[index]; }
}
Agora poderíamos reescrever esse código antigo como:
BDictionary torrent = (BDictionary)BItem.DecodeFile("my.torrent");
int filelength = (BInteger)torrent["info"]["files"][0]["length"];
Uau, ei pronto, código MUITO mais legível.Mas acabei de vender parte da minha alma por implicar conhecimento de subclasses na classe abstrata?
EDITAR:Em resposta a algumas das respostas recebidas, você está completamente fora do caminho para esta questão específica, uma vez que a estrutura é variável, por exemplo, meu exemplo de torrent["info"]["files"][0]["length"]
é válido, mas também é torrent["announce-list"][0][0]
, e ambos estariam em 90% dos arquivos torrent existentes.Genéricos não são a melhor opção, pelo menos com esse problema :(.Clique na especificação que vinculei, tem apenas 4 pequenos pontos grandes.
Solução
Eu acho que eu faria o presente [int] e isso [cadeia] acessores virtual e substituí-los em BList / BDictionary. Classes onde os assessores não faz sentido deve lançar um NotSupportedException () (talvez por ter uma implementação padrão em Bitem).
Isso faz com que o seu código de trabalho da mesma forma e dá-lhe um erro mais legível no caso de você deve escrever
(BInteger)torrent["info"][0]["files"]["length"];
por engano.
Outras dicas
Você realmente não deve acessar quaisquer classes derivadas da classe base como É muito bonito quebra a idéia de OOP. Readibility certamente vai um longo caminho, mas eu não trocaria isso por reutilização. Considere o caso quando você vai precisar adicionar outra subclasse -. Você também vai precisar atualizar a classe base em conformidade
Se tamanho do arquivo é algo que você recuperar, muitas vezes, por que não implementar uma propriedade na classe BDictionary ... assim que você código torna-se (?):
BDictionary torrent = BItem.DecodeFile("my.torrent");
int filelength = torrent.FileLength;
Dessa forma, os detalhes de implementação são escondidos do usuário.
A forma como eu vejo, nem todos os BItems são coleções, assim, nem todos os BItems têm indexadores, para que o indexador não deve estar em Bitem. Eu iria derivar outra classe abstrata de Bitem, vamos nomeá-la BCollection, e colocar os indexadores lá, algo como:
abstract class BCollection : BItem {
public BItem this[int index] {get;}
public BItem this[string index] {get;}
}
e fazer BList e BDictionary Herdar do BCollection. Ou você poderia ir a milha e fazer BCollection extra de uma classe genérica.
A minha recomendação seria a introdução de mais abstrações. Acho que é confuso que uma Bitem tem uma DecodeFile () que retorna um BDictionary. Esta pode ser uma coisa razoável a fazer no domínio torrent, eu não sei.
No entanto, eu iria encontrar uma API como o seguinte mais razoável:
BFile torrent = BFile.DecodeFile("my.torrent");
int filelength = torrent.Length;
Você considerou a análise de um simples "caminho" para que você poderia escrevê-lo desta maneira:
BDictionary torrent = BItem.DecodeFile("my.torrent");
int filelength = (int)torrent.Fetch("info.files.0.length");
Talvez não seja a melhor maneira, mas a legibilidade aumenta (um pouco)
- Se você tem o controle completo de sua base de código e o seu processo de pensamento, por todos os meios fazer.
- Se não, você vai se arrepender disso o dia algumas novas pessoa injeta uma derivação Bitem que você não viu entrando em o BList ou BDictionary.
Se você tiver que fazer isso, pelo menos envolvê-la (controle de acesso à lista) em uma classe que tem digitado fortemente assinaturas de método.
BString GetString(BInteger);
SetString(BInteger, BString);
aceitar e retornar BStrings mesmo que você armazená-lo internamente em um BList de BItems. (deixe-me dividir antes de eu fazer o meu 2 B ou não 2 B)
Hmm. Eu realmente argumentar que a primeira linha de código é mais legível do que o segundo - é preciso um pouco mais de tempo para descobrir o que está acontecendo, mas é mais aparente que você está tratando os objetos como BList ou BDictionary. Aplicando os métodos para as peles classe abstrata que detalhe, o que pode tornar mais difícil para descobrir o que seu método está realmente fazendo.
Se você introduzir os genéricos, você pode evitar casting.
class DecodedTorrent : BDictionary<BDictionary<BList<BDictionary<BInteger>>>>
{
}
DecodedTorrent torrent = BItem.DecodeFile("mytorrent");
int x = torrent["info"]["files"][0]["length"];
Hmm, mas que provavelmente não vai funcionar, como os tipos podem depender do caminho que você tomar através da estrutura.
É só eu
BDictionary torrent = BItem.DecodeFile("my.torrent");int filelength = (BInteger)((BDictionary)((BList)((BDictionary) torrent["info"])["files"])[0])["length"];
Você não precisa do BDictionary elenco 'torrent' é declarado como um BDictionary
public BItem this[int index]{ get { return ((BList)this)[index]; }}public BItem this[string index]{ get { return ((BDictionary)this)[index]; }}
Estes não acheive o resultado desejado como o tipo de retorno ainda é a versão abstrat, assim você ainda tem que elenco.
O código reescrito teria que ser
BDictionary torrent = BItem.DecodeFile("my.torrent");int filelength = (BInteger)((BList)((BDictionary)torrent["info"]["files"])[0])["length"];
Qual é a tão mau como o primeiro lote