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

Я хочу задать следующий вопрос:

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

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

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

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

Также существует четыре производных класса: 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 маленькие точки большого размера.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
0
1 502
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

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

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

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

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

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

Matthew Scharley 17.09.2008 15:13
Ответ принят как подходящий

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

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

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

по ошибке.

Кстати, это то, что я фактически делал, за исключением того, что я использовал new для переопределения вместо virtual / override. Глупо, глупо. Гораздо чище и приятнее, спасибо!

Matthew Scharley 17.09.2008 15:42

«новый» на самом деле не отменяет. Вы просто получаете 2 метода с одинаковым именем - если кто-то применяет к базовому классу, а затем вызывает метод, они получат исходный метод базового класса, а НЕ переопределенный

Orion Edwards 18.09.2008 04:10

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

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

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

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

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

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

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

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

На мой взгляд, не все 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, так что это все равно никому не поможет в долгосрочной перспективе для данного приложения.

Matthew Scharley 17.09.2008 15:12

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

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

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

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

ЕЕК! Хорошо, это работает для этого (очень) конкретного примера ... но на самом деле это древовидная структура переменных ... torrent["info"]["files"] действителен. Но torrent["announce-list"][0][0] тоже. Ваши дженерики просто сломались :(

Matthew Scharley 17.09.2008 15:34

Это только я

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]; }}

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

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

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

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

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

Matthew Scharley 17.09.2008 16:22

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

Однако я бы нашел более разумным api, подобный следующему:

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

Decode и DecodeFile возвращают BItems, это просто случай в (допустимом) торрент-файле, что корневой элемент на самом деле является словарем и, следовательно, может быть безопасно приведен сразу (если это недопустимый файл, исключение приведения перехватывается общим Обработка исключений)

Matthew Scharley 18.09.2008 03:54

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

Thomas Eyde 18.09.2008 23:49

Для протокола, я. Но мне все еще нужен способ синтаксического анализа и представления данных, поскольку Bencoding используется во всем протоколе, а не просто для кодирования торрент-файлов.

Matthew Scharley 19.09.2008 10:07

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