Question

La question que je veux poser est la suivante:

Est-ce que rejeter l’arbre d’héritage (c.-à-d. vers une classe plus spécialisée) à partir d’une classe abstraite excusable, voire même une bonne chose, ou est-ce toujours un mauvais choix avec de meilleures options disponibles?

Maintenant, l'exemple de la raison pour laquelle je pense qu'il peut être utilisé pour de bon.

J'ai récemment implémenté le codage à partir du protocole BitTorrent en C #. Un problème assez simple, comment représenter les données. J'ai choisi de le faire de cette façon,

Nous avons une classe abstract BItem , qui fournit certaines fonctionnalités de base, y compris le statique BItem Decode (chaîne) utilisée pour décoder une chaîne bencodée dans la structure nécessaire. .

Il existe également quatre classes dérivées, BString , BInteger , BList et BDictionary , représentant les quatre types différents. types de données à encoder. Maintenant, voici la partie la plus délicate. BList et BDictionary ont this [int] et this [chaîne] respectivement pour permettre l'accès au tableau- comme les qualités de ces types de données.

La partie potentiellement horrible arrive maintenant:

BDictionary torrent = (BDictionary) BItem.DecodeFile("my.torrent");
int filelength = (BInteger)((BDictionary)((BList)((BDictionary)
             torrent["info"])["files"])[0])["length"];

Eh bien, vous obtenez la photo ... Ouch, c'est dur pour les yeux, pour ne pas mentionner le cerveau. J'ai donc ajouté quelque chose de plus dans la classe abstraite:

public BItem this[int index]
{
    get { return ((BList)this)[index]; }
}
public BItem this[string index]
{
    get { return ((BDictionary)this)[index]; }
}

Nous pouvons maintenant réécrire cet ancien code comme suit:

BDictionary torrent = (BDictionary)BItem.DecodeFile("my.torrent");
int filelength = (BInteger)torrent["info"]["files"][0]["length"];

Wow, hé hop, BEAUCOUP de code plus lisible. Mais est-ce que je viens de vendre une partie de mon âme pour impliquer la connaissance des sous-classes dans la classe abstraite?

EDIT: En réponse à certaines des réponses, vous êtes complètement à l’écart de cette question, car la structure est variable, par exemple mon exemple de torrent ["info"] ["fichiers" ;] [0] ["longueur"] est valide, mais torrent ["liste-d'annonces"] [0] [0] l'est également, et les deux seraient en 90 % de fichiers torrent sur le marché. Les génériques ne sont pas la solution, avec ce problème au moins :(. Passez à la spécification que j'ai liée, il ne s'agit que de 4 petits points de grande taille.

Était-ce utile?

La solution

Je pense que je voudrais rendre virtuels les accesseurs this [int] et this [string] et les remplacer dans BList / BDictionary. Les classes où les accesseurs n’ont pas de sens devraient transtyper une NotSupportedException () (peut-être en ayant une implémentation par défaut dans BItem).

Cela fait que votre code fonctionne de la même manière et vous donne une erreur plus lisible au cas où vous devriez écrire

 (BInteger)torrent["info"][0]["files"]["length"];

par erreur.

Autres conseils

Vous ne devriez vraiment pas accéder aux classes dérivées de la classe de base car cela casse l'idée de la POO. La lisibilité va certainement un long chemin, mais je ne l'échangerais pas pour la réutilisabilité. Pensez au cas où vous devrez ajouter une autre sous-classe. Vous devrez également mettre à jour la classe de base en conséquence.

Si vous récupérez souvent la longueur de fichier, pourquoi ne pas implémenter une propriété dans la classe BDictionary (?) ... afin que votre code devienne:

BDictionary torrent = BItem.DecodeFile("my.torrent");
int filelength = torrent.FileLength;

De cette manière, les détails de la mise en œuvre sont masqués à l'utilisateur.

À mon avis, tous les BItems ne sont pas des collections. Par conséquent, tous les BItems ne disposent pas d'indexeurs. Par conséquent, l'indexeur ne doit pas être dans BItem. Je tirerais une autre classe abstraite de BItem, nommons-la BCollection et y placera les indexeurs, quelque chose comme:

abstract class BCollection : BItem {

      public BItem this[int index] {get;}
      public BItem this[string index] {get;}
}

et faites en sorte que BList et BDictionary héritent de BCollection. Vous pouvez également faire un effort supplémentaire et faire de BCollection une classe générique.

Ma recommandation serait d'introduire plus d'abstractions. Je trouve déroutant qu'un BItem ait un DecodeFile () qui retourne un BDictionary. Cela peut être une chose raisonnable à faire dans le domaine torrent, je ne sais pas.

Cependant, je trouverais une API comme celle-ci plus raisonnable:

BFile torrent = BFile.DecodeFile("my.torrent");
int filelength = torrent.Length;

Avez-vous envisagé d’analyser un simple "chemin"? afin que vous puissiez l'écrire de cette façon:

BDictionary torrent = BItem.DecodeFile ("my.torrent");

int filelength = (int) torrent.Fetch ("info.files.0.length");

Peut-être pas la meilleure façon, mais la lisibilité augmente (un peu)

  • Si vous avez le contrôle complet de votre base de code et de votre processus de réflexion, faites-le.
  • Sinon, vous le regretterez le jour où une nouvelle personne injectera une dérivation BItem que vous n'avez pas vue entrer dans votre bliste ou BDictionary.

Si vous devez le faire, enveloppez-le au moins (contrôlez l'accès à la liste) dans une classe qui a des signatures de méthode fortement typées.

BString GetString(BInteger);
SetString(BInteger, BString);

Acceptez et renvoyez BStrings même si vous le stockez en interne dans une BList of BItems. (laissez-moi me séparer avant de faire mes 2 B ou non 2 B)

Hmm. Je dirais en fait que la première ligne de code est plus lisible que la seconde - il faut un peu plus de temps pour comprendre ce qui se passe, mais il est plus évident que vous traitiez des objets comme des listes BList ou BDictionary. L’application des méthodes à la classe abstraite masque ces détails, ce qui peut compliquer la tâche de comprendre ce que fait réellement votre méthode.

Si vous introduisez des génériques, vous pouvez éviter le casting.

class DecodedTorrent : BDictionary<BDictionary<BList<BDictionary<BInteger>>>>
{
}
DecodedTorrent torrent = BItem.DecodeFile("mytorrent");
int x = torrent["info"]["files"][0]["length"];

Hmm, mais cela ne fonctionnera probablement pas, car les types peuvent dépendre du chemin emprunté dans la structure.

Est-ce juste moi

BDictionary torrent = BItem.DecodeFile("my.torrent");int filelength = (BInteger)((BDictionary)((BList)((BDictionary)             torrent["info"])["files"])[0])["length"];

Vous n'avez pas besoin de la fonte BDictionary 'torrent' est déclarée comme BDictionary

public BItem this[int index]{&nbsp; &nbsp; get { return ((BList)this)[index]; }}public BItem this[string index]{&nbsp; &nbsp; get { return ((BDictionary)this)[index]; }}

Ceux-ci n'obtiennent pas le résultat souhaité car le type de retour est toujours la version abstrat, vous devez donc toujours lancer le casting.

Le code réécrit devrait être

BDictionary torrent = BItem.DecodeFile("my.torrent");int filelength = (BInteger)((BList)((BDictionary)torrent["info"]["files"])[0])["length"];

Qui est le aussi mauvais que le premier lot

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top