Является ли класс .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);
}
}
Ответы
Ответ 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 - он не указывает, как реализовать поток неизвестной, неопределенной или бесконечной длины!