"Более свободный" ввод текста на C # путем отбрасывания дерева наследования

StackOverflow https://stackoverflow.com/questions/81972

Вопрос

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

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

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

Недавно я внедрил Бенкодирование по протоколу 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 маленьких точечных элемента размером.

Это было полезно?

Решение

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

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

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

по ошибке.

Другие советы

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

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

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

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

Насколько я понимаю, не все 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 универсальным классом.

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

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

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

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

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

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

  • Если у вас есть полный контроль над вашей кодовой базой и вашим мыслительным процессом, непременно сделайте это.
  • Если нет, вы пожалеете об этом в тот день, когда какой-нибудь новый человек введет производную от BItem, которую вы не предвидели ваш БЛист или BDictionary.

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

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

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

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

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

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

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

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

Это только из-за меня

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]{&nbsp; &nbsp; get { return ((BList)this)[index]; }}public BItem this[string index]{&nbsp; &nbsp; get { return ((BDictionary)this)[index]; }}

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top