«Слабее» печатать на C #, отбрасывая дерево наследования - PullRequest
5 голосов
/ 17 сентября 2008

Вопрос, который я хочу задать, таков:

Является ли приведение дерева наследования (т. Е. К более специализированному классу) из абстрактного класса извиняемым или даже хорошим делом, или это всегда плохой выбор с лучшими доступными опциями?

Теперь пример того, почему я думаю, что это можно использовать навсегда.

Я недавно реализовал Бенкодирование из протокола BitTorrent в C #. Достаточно простая проблема, как представить данные. Я решил сделать это так,

У нас есть класс abstract BItem, который обеспечивает некоторые базовые функциональные возможности, включая static BItem Decode(string), который используется для декодирования зашифрованной строки в необходимую структуру.

Существует также четыре производных класса: BString, BInteger, BList и BDictionary, представляющих четыре различных типа данных, которые должны быть закодированы. Теперь вот сложная часть. BList и BDictionary имеют аксессоры this[int] и this[string] соответственно для обеспечения доступа к свойствам массива этих типов данных.

Потенциально ужасная часть идет сейчас:

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

Ну, вы поняли ... Ой, это тяжело для глаз, не говоря уже о мозге. Итак, я ввел что-то дополнительное в абстрактный класс:

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

Теперь мы можем переписать этот старый код как:

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

Ух ты, эй престо, НАМНОГО более читаемый код. Но продал ли я часть своей души за то, что подразумевал знание подклассов в абстрактном классе?

РЕДАКТИРОВАТЬ: В ответ на некоторые ответы приходят, вы совершенно не в курсе этого конкретного вопроса, так как структура является переменной, например, мой пример torrent["info"]["files"][0]["length"] является действительным, но так же, как и torrent["announce-list"][0][0], и и то и другое будет в 90% торрент-файлов. Дженерики - не лучший способ решить эту проблему, по крайней мере :(. Перейдите по ссылке на спецификацию, которую я связал, она всего 4 маленьких точки.

Ответы [ 10 ]

5 голосов
/ 17 сентября 2008

Я думаю, я бы сделал виртуальные методы доступа this [int] и this [string] и переопределил бы их в BList / BDictionary. Классы, в которых средства доступа не имеют смысла, должны вызывать NotSupportedException () (возможно, с реализацией по умолчанию в BItem).

Это делает ваш код таким же образом и дает вам более читаемую ошибку в случае, если вы должны написать

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

по ошибке.

3 голосов
/ 17 сентября 2008

Вы действительно не должны обращаться к каким-либо производным классам из базового класса, поскольку это в значительной степени нарушает идею ООП. Конечно, читаемость имеет большое значение, но я бы не променял ее на повторное использование. Рассмотрим случай, когда вам нужно будет добавить другой подкласс - вам также необходимо соответствующим образом обновить базовый класс.

1 голос
/ 17 сентября 2008

Я бы рекомендовал ввести больше абстракций. Я нахожу сбивающим с толку, что у BItem есть DecodeFile (), который возвращает BDictionary. Это может быть разумно сделать в домене торрента, я не знаю.

Тем не менее, я нашел бы API, как следующий, более разумным:

BFile torrent = BFile.DecodeFile("my.torrent");
int filelength = torrent.Length;
1 голос
/ 17 сентября 2008

На мой взгляд, не все BItems являются коллекциями, поэтому не все BItems имеют индексаторы, поэтому индексатор не должен быть в BItem. Я бы вывел еще один абстрактный класс из BItem, назовем его BCollection и поместил туда индексаторы, что-то вроде:

abstract class BCollection : BItem {

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

, а BList и BDictionary наследуются от BCollection. Или вы можете пройти лишнюю милю и сделать BCollection общим классом.

1 голос
/ 17 сентября 2008

Если длина файла - это то, что вы часто получаете, почему бы не реализовать свойство в классе BDictionary (?) ..., чтобы ваш код стал:

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

Таким образом, детали реализации скрыты от пользователя.

0 голосов
/ 17 сентября 2008

Это только у меня

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

Вам не нужно, чтобы приведение BDictionary 'torrent' было объявлено как BDictionary

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

Они не достигают желаемого результата, так как тип возвращаемого значения по-прежнему является абстрактной версией, поэтому вам все равно придется выполнить приведение.

Переписанный код должен быть

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

Что так же плохо, как и первый лот

0 голосов
/ 17 сентября 2008

Если вы вводите дженерики, вы можете избежать кастинга.

class DecodedTorrent : BDictionary<BDictionary<BList<BDictionary<BInteger>>>>
{
}

DecodedTorrent torrent = BItem.DecodeFile("mytorrent");
int x = torrent["info"]["files"][0]["length"];

Хм, но это, вероятно, не сработает, поскольку типы могут зависеть от пути, по которому вы проходите через структуру.

0 голосов
/ 17 сентября 2008

Хм. Я бы на самом деле утверждал, что первая строка кода более удобочитаема, чем вторая - требуется немного больше времени, чтобы выяснить, что происходит, но более вероятно, что вы рассматриваете объекты как BList или BDictionary. Применение методов к абстрактному классу скрывает эти детали, что может затруднить понимание того, что на самом деле делает ваш метод.

0 голосов
/ 17 сентября 2008
  • Если вы полностью контролируете свою кодовую базу и свой мыслительный процесс, непременно сделайте.
  • Если нет, то вы пожалеете об этом в тот день, когда какой-то новый человек вводит деривацию BItem, которую вы не видели, входящую в ваш BList или BDictionary.

Если вам нужно сделать это, по крайней мере, оберните его (управляйте доступом к списку) в классе, который имеет строго типизированные сигнатуры методов.

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

Принимать и возвращать BStrings, даже если вы внутренне сохраняете их в BList of BItems. (позвольте мне разделиться, прежде чем я сделаю 2 B или нет 2 B)

0 голосов
/ 17 сентября 2008

Вы решили проанализировать простой "путь", чтобы вы могли написать его так:

BDictionary torrent = BItem.DecodeFile("my.torrent");<br /> int filelength = (int)torrent.Fetch("info.files.0.length");

Возможно, не лучшим образом, но читаемость увеличивается (немного)

...