Я читаю C # Design Pattern Essentials . В настоящее время я читаю о шаблоне итератора.
Я полностью понимаю, как реализовать, но я не понимаю важность или вижу вариант использования. В книге приведен пример, где кто-то должен получить список объектов. Они могли бы сделать это, выставив публичную собственность, такую как IList<T>
или Array
.
Книга пишет
Проблема с этим заключается в том, что внутреннее представление в обоих этих классах подвергалось внешним проектам.
Что такое внутреннее представление? Факт это array
или IList<T>
? Я действительно не понимаю, почему это плохо для потребителя (программиста, вызывающего это) знать ...
Затем в книге говорится, что этот шаблон работает, выставляя его GetEnumerator
функцию, поэтому мы можем вызывать GetEnumerator()
и выставлять «список» таким образом.
Я предполагаю, что этот шаблон имеет место (как и все) в определенных ситуациях, но я не вижу, где и когда.
источник
F
который дает мне знания (методы)a
,b
иc
. Все хорошо, может быть много разных вещей, но толькоF
для меня. Думайте о методе как о «ограничениях» или предложениях контракта, которыеF
обязывают классы делать. Предположим, что мы добавилиd
хотя, потому что нам это нужно. Это добавляет дополнительное ограничение, каждый раз, когда мы делаем это, мы навязываем все больше и большеF
. В конце концов (в худшем случае) может быть только одна вещь,F
поэтому мы можем не иметь ее. ИF
ограничивает настолько, что есть только один способ сделать это.Ответы:
Программное обеспечение - игра обещаний и привилегий. Никогда не стоит обещать больше, чем вы можете предоставить, или больше, чем нужно вашему сотруднику.
Это особенно относится к типам. Смысл написания итеративной коллекции в том, что ее пользователь может перебирать ее - не больше, не меньше. Предоставление конкретного типа
Array
обычно создает много дополнительных обещаний, например, что вы можете отсортировать коллекцию по функции по вашему выбору, не говоря уже о том факте, что нормальArray
, вероятно, позволит соавтору изменять данные, хранящиеся в нем.Даже если вы думаете, что это хорошо («Если средство визуализации замечает, что новый параметр экспорта отсутствует, он может просто вставить его прямо в! Опрятно!»), В целом это снижает согласованность кодовой базы, делая ее труднее рассуждать о том, чтобы сделать код легким для рассуждения, является главной целью разработки программного обеспечения.
Теперь, если вашему соавтору необходим доступ к нескольким вещам, чтобы они гарантированно не пропустили ни одного из них, вы реализуете
Iterable
интерфейс и выставляете только те методы, которые этот интерфейс объявляет. Таким образом, в следующем году, когда в стандартной библиотеке появится значительно лучшая и более эффективная структура данных, вы сможете переключиться на базовый код и извлечь из него выгоду, не исправляя свой клиентский код повсюду . Есть и другие преимущества не обещать больше, чем нужно, но одно это настолько велико, что на практике другие не нужны.источник
Exposing the concrete type Array usually creates many additional promises, e.g. that you can sort the collection by a function of your own choosing...
- Отлично. Я просто не думал об этом. Да, это возвращает «итерацию» и только итерацию!Сокрытие реализации является основным принципом ООП и хорошей идеей во всех парадигмах, но это особенно важно для итераторов (или как их там называют на этом конкретном языке) в языках, которые поддерживают ленивую итерацию.
Проблема с представлением конкретного типа итераций - или даже интерфейсов, подобных
IList<T>
- не в объектах, которые их представляют, а в методах, которые их используют. Например, допустим, у вас есть функция для печати спискаFoo
s:Вы можете использовать эту функцию только для печати списков foo - но только если они реализуют
IList<Foo>
Но что, если вы хотите напечатать каждый четный элемент списка? Вам нужно будет создать новый список:
Это довольно долго, но с помощью LINQ мы можем сделать это с одной строкой, которая на самом деле более читабельна:
Теперь, вы заметили,
.ToList()
в конце концов? Это преобразует отложенный запрос в список, поэтому мы можем передать егоPrintFoos
. Это требует выделения второго списка и двух проходов по элементам (один в первом списке, чтобы создать второй список, и другой во втором списке, чтобы напечатать его). Кроме того, что если у нас есть это:Что делать, если
foos
есть тысячи записей? Нам придется пройти через все из них и выделить огромный список, чтобы напечатать 6 из них!Enter Enumerators - версия шаблона итератора для C #. Вместо того, чтобы наша функция принимала список, мы заставляем его принимать
Enumerable
:Теперь
Print6Foos
можно лениво перебирать первые 6 пунктов списка, и нам не нужно прикасаться к остальному.Не разоблачение внутреннего представления - вот ключ. Когда был
Print6Foos
принят список, мы должны были предоставить ему список - что-то, что поддерживает произвольный доступ - и поэтому мы должны были выделить список, потому что подпись не гарантирует, что он будет только перебирать его. Скрывая реализацию, мы можем легко создать более эффективныйEnumerable
объект, который поддерживает только то, что действительно нужно функции.источник
Раскрытие внутреннего представления практически никогда не является хорошей идеей. Это не только делает код сложнее рассуждать, но и сложнее поддерживать. Представьте, что вы выбрали какое-то внутреннее представление - скажем,
IList<T>
- и раскрыли это внутреннее. Любой, кто использует ваш код, может получить доступ к списку, и код может полагаться на то, что внутреннее представление является списком.По какой-то причине вы решаете изменить внутреннее представление
IAmWhatever<T>
на более поздний момент времени. Вместо простого изменения внутренних элементов вашего класса вам придется изменять каждую строку кода и метода, полагаясь на тип внутреннего представленияIList<T>
. Это громоздко и в лучшем случае склонно к ошибкам, когда вы единственный, кто использует класс, но это может нарушить код других людей, использующий ваш класс. Если вы только что представили набор открытых методов без предоставления каких-либо внутренних компонентов, вы могли бы изменить внутреннее представление без какой-либо строки кода вне вашего класса, обращая внимание, работая так, как будто ничего не изменилось.Вот почему инкапсуляция является одним из наиболее важных аспектов любого нетривиального дизайна программного обеспечения.
источник
Чем меньше вы говорите, тем меньше вы должны продолжать говорить.
Чем меньше вы спрашиваете, тем меньше вам нужно сказать.
Если ваш код только предоставляет
IEnumerable<T>
, который поддерживает толькоGetEnumrator()
его, он может быть заменен любым другим кодом, который может поддерживатьIEnumerable<T>
. Это добавляет гибкости.Если ваш код использует только
IEnumerable<T>
его, он может поддерживаться любым кодом, который реализуетIEnumerable<T>
. Опять же, есть дополнительная гибкость.Например, все linq-to-objects зависят только от
IEnumerable<T>
. В то время как он ускоряет поиск некоторых конкретных реализаций,IEnumerable<T>
он делает это способом тестирования и использования, который всегда может вернуться к простому использованиюGetEnumerator()
иIEnumerator<T>
реализации, которая возвращается. Это дает ему гораздо больше гибкости, чем если бы он был построен поверх массивов или списков.источник
Формулировка мне нравится использовать зеркала @CaptainMan комментарий «s: „Это хорошо для потребителя , чтобы знать внутренние детали Это плохо для клиента. Необходимость знать внутренние детали.“
Если клиент должен ввести
array
илиIList<T>
в своем коде, когда эти типы были внутренними деталями, потребитель должен понять их правильно. Если они ошибаются, потребитель может не скомпилировать или даже получить неверные результаты. Это приводит к тому же поведению, что и другие ответы «всегда скрывайте детали реализации, потому что вы не должны их раскрывать», но начинает копаться в преимуществах отсутствия разоблачения. Если вы никогда не раскрываете детали реализации, ваш клиент никогда не сможет связать себя, используя ее!Мне нравится думать об этом в этом свете, потому что он раскрывает концепцию сокрытия реализации под оттенки смысла, а не драконовское «всегда скрывайте детали реализации». Существует множество ситуаций, когда выставление реализации полностью допустимо. Рассмотрим случай с драйверами устройств реального времени, где возможность пропускать прошлые уровни абстракции и вставлять байты в правильные места может быть разницей между выбором времени или нет. Или рассмотрите среду разработки, в которой у вас нет времени на создание «хороших API-интерфейсов», и вам нужно использовать их немедленно. Часто разоблачение базовых API в таких средах может быть разницей между установлением крайнего срока или нет.
Однако, по мере того как вы оставляете эти особые случаи позади и смотрите на более общие случаи, становится все более и более важным скрывать реализацию. Выявление деталей реализации похоже на веревку. При правильном использовании вы можете делать с ним все, например, масштабировать высокие горы. Используется неправильно, и это может связать вас в петлю. Чем меньше вы знаете о том, как будет использоваться ваш продукт, тем более осторожным вы должны быть.
Ситуационное исследование, которое я вижу снова и снова, - это то, где разработчик создает API, который не очень тщательно скрывает детали реализации. Второй разработчик, используя эту библиотеку, обнаруживает, что в API отсутствуют некоторые ключевые функции, которые они хотели бы. Вместо того чтобы писать запрос функции (который может иметь срок обработки в месяцы), они вместо этого решают злоупотребить своим доступом к скрытым деталям реализации (что занимает секунды). Это само по себе неплохо, но часто разработчик API никогда не планировал, что это будет частью API. Позже, когда они улучшают API и изменяют эти базовые детали, они обнаруживают, что они прикованы к старой реализации, потому что кто-то использовал его как часть API. Худшая версия этого - когда кто-то использовал ваш API для обеспечения ориентированной на клиента функции, а теперь ваша компания ». s paycheck привязан к этому некорректному API! Просто, раскрыв подробности реализации, когда вы недостаточно знаете о своих клиентах, оказалось, что достаточно веревки, чтобы связать петлю вокруг вашего API!
источник
Вы не обязательно знаете, каково внутреннее представление ваших данных - или может стать в будущем.
Предположим, у вас есть источник данных, который вы представляете как массив, вы можете перебирать его с помощью обычного цикла for.
Теперь скажите, что вы хотите рефакторинг. Ваш начальник попросил, чтобы источником данных был объект базы данных. Кроме того, он хотел бы иметь возможность извлекать данные из API, или из хеш-карты, или из связанного списка, или из вращающегося магнитного барабана, или из микрофона, или из подсчета стружки яков в реальном времени, обнаруженной во Внешней Монголии.
Хорошо, если у вас есть только один цикл for, вы можете легко изменить свой код, чтобы получить доступ к объекту данных так, как он ожидает к нему доступ.
Если у вас есть 1000 классов, пытающихся получить доступ к объекту данных, теперь у вас большая проблема с рефакторингом.
Итератор - это стандартный способ доступа к источнику данных на основе списка. Это общий интерфейс. Использование этого сделает ваш код независимым от источника данных.
источник