Должна ли согласованность быть предпочтительнее соглашения о программировании?

14

При разработке класса следует ли согласованность поведения предпочтительнее обычной практики программирования? Чтобы привести конкретный пример:

Общее соглашение таково: если классу принадлежит объект (например, он его создал), он отвечает за его очистку после завершения. Конкретным примером может быть .NET, если ваш класс владеет IDisposableобъектом, он должен утилизировать его в конце своей жизни. И если у вас нет этого, не трогайте его.

Теперь, если мы посмотрим на StreamWriterкласс в .NET, мы можем найти в документации, что он закрывает основной поток, когда он закрывается / удаляется. Это необходимо в тех случаях, когда StreamWriterсоздается экземпляр путем передачи имени файла, поскольку модуль записи создает базовый файловый поток и, следовательно, должен его закрывать. Однако можно также передать во внешний поток, который автор также закрывает.

Это раздражало меня кучу раз (да, я знаю, что вы можете сделать не закрывающуюся оболочку, но это не главное), но, очевидно, Microsoft приняла решение, что более последовательно всегда закрывать поток независимо от того, откуда он пришел.

Когда я сталкиваюсь с таким шаблоном в одном из моих классов, я обычно создаю ownsFooBarфлаг, который устанавливается в false в тех случаях, когда FooBarвводится через конструктор, и в true в противном случае. Таким образом, ответственность за его очистку передается вызывающей стороне, когда он передает экземпляр явно.

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

Изменить для уточнения

Под «согласованностью» я подразумеваю: согласованное поведение класса, всегда берущее на себя ответственность (и закрывающее поток), против «наилучшей практики» - владеть объектом только в том случае, если вы его создали или явно передали право владения.

Как для примера, где это enoying:

Предположим, у вас есть два заданных класса (из какой-то сторонней библиотеки), которые принимают поток, чтобы что-то с ним делать, например, создавать и обрабатывать некоторые данные:

 public class DataProcessor
 {
     public Result ProcessData(Stream input)
     {
          using (var reader = new StreamReader(input))
          {
              ...
          }
     }
 }

 public class DataSource
 {
     public void GetData(Stream output)
     {
          using (var writer = new StreamWriter(output))
          {
               ....
          }
     }
 }

Теперь я хочу использовать это так:

 Result ProcessSomething(DataSource source)
 {
      var processor = new DataProcessor();
      ...
      var ms = new MemoryStream();
      source.GetData(ms);
      return processor.ProcessData(ms);
 }

Это не удастся, за исключением Cannot access a closed streamобработки данных. Это немного сконструировано, но должно проиллюстрировать это. Существуют различные способы исправить это, но, тем не менее, я чувствую, что работаю над тем, что мне не нужно.

ChrisWue
источник
Не могли бы вы остановиться на том, ПОЧЕМУ иллюстрированное поведение StreamWriter причиняет вам боль? Вы хотите использовать тот же поток в другом месте? Почему?
Работа
@Job: я уточнил свой вопрос
ChrisWue

Ответы:

9

Я тоже использую эту технику, я называю это «передачей», и я считаю, что она заслуживает статуса паттерна. Когда объект A принимает одноразовый объект B в качестве параметра времени строительства, он также принимает логическое значение с именем 'handoff' (по умолчанию false), и если оно истинно, то удаление A каскадируется для удаления B.

Я не поддерживаю распространение неудачных решений других людей, поэтому я бы никогда не принял плохую практику Microsoft как устоявшуюся конвенцию, и я бы не стал считать слепое следование другими лицами неудачных решений Microsoft «последовательным поведением» в любой форме, форме или форме. ,

Майк Накис
источник
Примечание: я написал формальное определение этого шаблона в посте на моем блоге: michael.gr: Шаблон "Handoff" .
Майк Накис
В качестве дополнительного расширения handOffшаблона, если такое свойство установлено в конструкторе, но существует другой метод для его изменения, этот другой метод также должен принять handOffфлаг. Если handOffбыл указан для текущего элемента, он должен быть утилизирован, тогда новый элемент должен быть принят, а внутренний handOffфлаг обновлен, чтобы отразить статус нового элемента. Метод не должен проверять на совпадение старые и новые ссылки, поскольку, если исходная ссылка была «передана», все ссылки, хранящиеся у исходного вызывающего, должны быть отброшены.
суперкат
@supercat Я согласен, но у меня есть проблема с последним предложением: если передача хэндовера является истинной для имеющегося в данный момент предмета, но вновь предоставленный предмет равен ссылке на имеющийся в настоящее время предмет, тогда выбрасываемый в настоящее время предмет фактически утилизирует вновь поставленный предмет. Я думаю, что повторная поставка того же предмета должна быть незаконной.
Майк Накис
Повторное предоставление одного и того же предмета, как правило, должно быть незаконным, если только объект не является неизменным, на самом деле не содержит никаких ресурсов и не заботится о его утилизации. Например, если Disposeзапросы к системным кистям игнорировались, метод "color.CreateBrush" может на досуге либо создать новую кисть (которая потребует удаления), либо вернуть системную кисть (которая будет игнорировать удаление). Самый простой способ предотвратить повторное использование объекта, если он заботится об удалении, но разрешить повторное использование, если это не так, значит избавиться от него
суперкат
3

Я думаю, что вы правы, если честно. Я думаю, что Microsoft перепутала класс StreamWriter, в частности, по причинам, которые вы описываете.

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

прецизионный самописец
источник
2

Я бы пошел с последовательностью. Как вы упомянули для большинства людей, имеет смысл, что если ваш объект создает дочерний объект, он будет владельцем и уничтожит его. Если ему дается ссылка извне, в какой-то момент времени «извне» также содержит тот же объект, и он не должен принадлежать. Если вы хотите передать владение извне в ваш объект, используйте методы Attach / Detach или конструктор, который принимает логическое значение, которое дает понять, что владение передано.

Напечатав все это для полного ответа, я думаю, что большинство общих шаблонов проектирования не обязательно попадут в ту же категорию, что и классы StreamWriter / StreamReader. Я всегда думал, что причина, по которой MS выбрала StreamWriter / Reader для автоматического захвата владения, заключается в том, что в этом конкретном случае совместное владение не имеет большого смысла. Вы не можете одновременно читать / записывать два разных объекта из одного потока. Я уверен, что вы могли бы написать какую-то детерминистическую долю времени, но это был бы адский паттерн. Поэтому, вероятно, поэтому они и сказали, так как нет смысла делиться потоком, давайте всегда будем его принимать. Но я бы не стал считать, что такое поведение когда-либо стало общим соглашением для всех классов.

Вы можете считать Streams последовательным шаблоном, в котором передаваемый объект не является разделяемым с точки зрения проектирования, и поэтому без каких-либо дополнительных флагов устройство чтения / записи просто переходит во владение.

DXM
источник
Я уточнил свой вопрос - я последовательно имел в виду поведение всегда брать на себя ответственность.
ChrisWue
2

Быть осторожен. То, что вы считаете «последовательностью», может быть просто случайным набором привычек.

«Координация пользовательских интерфейсов для согласованности», SIGCHI Bulletin 20, (1989), 63-65. Извините, нет ссылки, это старая статья. «... двухдневный семинар из 15 экспертов не смог дать определение последовательности». Да, речь идет об «интерфейсе», но я считаю, что если вы некоторое время подумаете о «согласованности», то обнаружите, что тоже не можете ее определить.

Брюс Эдигер
источник
Вы правы, если эксперты собираются из разных кругов, но при разработке кода мне было легко следовать некоторому существующему шаблону (или привычке, если хотите), с которым моя команда уже знакома. Например, на днях один из наших ребят задал вопрос о некоторых асинхронных операциях, которые мы выполняли, и когда я сказал ему, что они ведут себя так же, как Win32 Overlapped I / O, через 30 секунд он понял весь интерфейс ... в этом случае оба я и он пришли с того же сервера фон Win32 фон. Согласованность :)
ДХМ
@ Брюс, я понял твою точку зрения - я думал, что было ясно, что я имел в виду с последовательностью, но, видимо, это не так.
ChrisWue