Подтвердить что ты не робот

Является ли класс .NET Stream плохо разработанным?

Я потратил немало времени на знакомство с классами .NET Stream. Обычно я многому научился, изучая классный дизайн профессиональных, коммерческих рамок, но я должен сказать, что здесь что-то не совсем пахнет.

System.IO.Stream - абстрактный класс, представляющий последовательность байтов. Он имеет 10 абстрактных методов/свойств: Read, Write, Flush, Length, SetLength, Seek, Position, CanRead, CanWrite, CanSeek. Так много абстрактных членов делает его громоздким, потому что вам нужно переопределить все эти методы, даже если большинство из них просто бросают NotImplemented.

Пользователи классов Stream, как ожидается, вызовут CanRead, CanWrite или CanSeek, чтобы узнать возможности Stream, или, я полагаю, просто продолжайте и вызовите Read, Write или Seek и посмотрите, выбрасывает ли он NotImplemented. Это только я, или это крахмальный дизайн?

Хотя есть много нит, которые я хотел бы выбрать с помощью дизайна класса Stream, основной вопрос, о котором я бы хотел спросить, заключается в следующем: почему они не использовали интерфейсы, такие как IReadable, IWriteable, ISeekable, вместо этого? Затем новый класс Stream может изящно выводиться из поддерживаемых им интерфейсов. Разве это объектно-ориентированный способ делать что-то? Или я чего-то не хватает?

Обновить. Было указано, что значение CanRead et al может измениться во время выполнения &mdash, например, если a FileStream закрыто — и точка взята. Однако я не убежден, что это хороший дизайн. Из того, откуда я, попытка прочитать из файла, который уже был закрыт, является ошибкой или, по крайней мере, исключительным условием. (И таким образом бросание исключения - естественный способ справиться с этой ситуацией.)

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

Обновление от 7 августа 2010. Консенсус здесь, похоже, в том, что дизайн Stream довольно хорош в его нынешнем виде. Но позвольте мне спросить еще раз, чтобы быть на 100% уверенным: люди пишут что-то подобное каждый раз, когда они читают из Stream?

//  s is a Stream

lock(s)
{
    if (s.CanRead)
    {
        s.Read(buf, 0, buf.Length);
    }
}
4b9b3361

Ответ 1

Я думаю, что классы разработаны красиво. Я бы предпочел проверить свойство, затем попытаться сделать что-то и поймать исключение. Интерфейсы не подходят для типов потоков, которые имеют несколько типов. Какой тип будет возвращен из метода, который будет доступен для чтения и записи? Я согласен, что дизайн не является объектно-ориентированным дизайном, но действительно ли вы хотите обрабатывать потоки таким образом? Некоторые из свойств могут измениться, если поток закрыт или что-то еще изменится, что произойдет в этом случае?

Я думаю, что этот вопрос вызывает интересный эксперимент, но почему бы не попытаться создать собственные классы, связанные с потоком. Опубликуйте свою редизайн по CodePlex или Google Code, это будет отличный опыт обучения и приведет к созданию потенциально полезной библиотеки для других пользователей.

Ответ 2

Использование интерфейсов означает, что значение "CanRead" не может быть изменено во время выполнения. Класс FileStream изменяет свойство CanRead на основе текущего состояния файла.

Ответ 3

Они, вероятно, не использовали интерфейсы, потому что в то время не было методов расширения. Если вы хотите, чтобы в каждом потоке были такие вещи, как метод ReadByte по умолчанию, вам нужно было использовать класс.

Я написал сообщение

Ответ 4

Интерфейсы могут быть чрезмерными, и это будет один из этих случаев. Я думаю, что нынешний дизайн велик. Тот факт, что потоки могут изменять возможности во время выполнения, означает, что IReadable/IWritable/ISeekable не избавит вас от необходимости CanRead, CanWrite и CanSeek, поэтому вы просто увеличиваете сложность без реального выигрыша, кроме устранения нескольких методов и свойств заглушки в ваших производных классах.

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

Ответ 5

Класс Stream использует дополнительный шаблон функции, вы можете прочитать здесь.

Ответ 6

Чтобы ответить на вопрос: возможно.

Я в значительной степени согласен с ответом @Strilanc, что Да, это плохо реализовано, но я думал, что продолжу и выскажу свои мысли.

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

Однако теперь, когда у нас есть действительно полезные конструкции, такие как Generics и методы расширения, я думаю, что пришло время пересмотреть многие исходные классы и пометить их ObsoleteAttribute. Конечно, сначала вам нужен API-интерфейс замены.

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

Это применимо ко всем не общим коллекциям (а не только в System.Collections пространстве имен) и "странные абстракции". System.Text.RegularExpressions * Классы коллекций являются хорошими примерами, но их гораздо больше в рамках фреймворка.

Я знаю, что мой комментарий "странные абстракции" чрезвычайно расплывчатый, но я думаю, что тест лакмусовой бумажки будет заключаться в том, что если вам придется бросать NotImplementedExceptions на все производные классы, возможно, лучший способ сделать что-то.

Итак, мне не нравится дизайн Stream, но пока не появится лучший API, вы, вероятно, просто имеете чтобы справиться с этим. Написание Adapters/Wrappers для существующего Stream, вероятно, будет вашим лучшим выбором на данный момент, и это то, что я сейчас делаю.

Ответ 7

Вы можете использовать (Java *) подход к написанию в основном пустого класса MyStream, который наследует базовый класс Stream, но предоставляет большинство методов-членов (например, CanSeek()) и наделяет их разумными по умолчанию (например, бросание NotImplemented). Тогда ваш реальный класс просто расширяет ваш класс MyStream, реализуя два или три оставшихся метода, которые вам действительно нужны.

При повторном использовании класса MyStream вы сэкономите много изобретательности колеса.

* Это называется абстрактным классом адаптера в библиотеках Java.

Ответ 8

Моя проблема с классом stream - свойство Length - он не указывает, как реализовать поток неизвестной, неопределенной или бесконечной длины!