Какова идея именования классов с суффиксом «Info», например: «SomeClass» и «SomeClassInfo»?

20

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

Учитывая фактические устройства (датчики и приемники) - это одно, а их представление в программном обеспечении - другое, я думаю о присвоении имен некоторым классам с помощью шаблона имени суффикса «Info».

Например, хотя a Sensorбудет классом для представления фактического датчика (когда он фактически подключен к какому-либо работающему устройству), SensorInfoон будет использоваться для представления только характеристик такого датчика. Например, при сохранении файла я бы сериализовал a SensorInfoв заголовок файла вместо сериализации a Sensor, что даже не имело бы смысла.

Но теперь я в замешательстве, потому что в жизненном цикле объектов есть середина, где я не могу решить, следует ли мне использовать тот или иной, или как получить один из другого, или даже нужно ли сворачивать оба варианта только в один класс.

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

Язык, с которым я работаю, - .NET, и этот шаблон именования, кажется, распространен во всей среде, например, с этими классами:

  • Directoryи DirectoryInfoклассы;
  • Fileи FileInfoклассы;
  • ConnectionInfoкласс (без соответствующего Connectionкласса);
  • DeviceInfoкласс (без соответствующего Deviceкласса);

Итак, мой вопрос: есть ли общее обоснование использования этого шаблона именования? Существуют ли случаи, когда имеет смысл иметь пары имен ( Thingи ThingInfo), и другие случаи, когда должен существовать только ThingInfoкласс или Thingкласс без его аналога?

heltonbiker
источник
5
Бен Ааронсон понял это правильно в своем ответе ниже; Infoсуффикс отличает staticкласс , содержащий вспомогательные методы от своего аналога с учетом состояния. Это не «лучшая практика» как таковая; Это просто способ, которым команда .NET придумала решение конкретной проблемы. Они могли бы так же легко придумать FileUtilityи File, но File.DoSomething()и , FileInfo.FileNameкажется, лучше читать.
Роберт Харви
1
Стоит отметить, что это общий шаблон, но с таким же количеством соглашений об именах, как и языков и API. Например, в Java, если у вас есть класс с состоянием Foo, у вас может быть неинстанцируемый служебный класс Foos. Когда дело доходит до именования, важна согласованность в API, а в идеале - в API на платформе.
Кевин Крумвиде,
1
В самом деле? «Никто бы не предложил назвать класс EmployeeInfo» ?? НИКТО? Это кажется немного сильным для чего-то не столь очевидного.
ThePopMachine
@ThePopMachine Я согласен, что слово «никто» может быть слишком сложным в этом случае, но Employeeпримеры найдены десятками, в Интернете или в классических книгах, хотя я еще не видел EmployeeInfo(возможно, потому что работник - живое существо, а не техническая конструкция, такая как соединение или файл). Но, согласился, если класс EmployeeInfoбудет предложен в проекте, я думаю, что он может иметь свое применение.
Хелтонбайкер

Ответы:

21

Я думаю, что "информация" является неправильным. У объектов есть состояние и действия: «информация» - это просто еще одно имя слова «состояние», которое уже встроено в ООП.

Что вы на самом деле пытаетесь смоделировать здесь? Вам нужен объект, который представляет аппаратное обеспечение в программном обеспечении, чтобы другой код мог его использовать.

Это легко сказать, но, как вы узнали, в этом есть нечто большее. «Представление оборудования» удивительно широк. У объекта, который делает это, есть несколько проблем:

  • Низкоуровневая связь с устройством, будь то связь через интерфейс USB, последовательный порт, TCP / IP или проприетарное соединение.
  • Управляющий государством. Устройство включено? Готовы поговорить с программным обеспечением? Занятый?
  • Обработка событий. Устройство генерирует данные: теперь нам нужно сгенерировать события для передачи другим интересующим их классам.

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

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

Вот пример того, как я бы разработал иерархию классов для датчика температуры:

  • ITemperaSource: интерфейс, представляющий все, что может генерировать данные о температуре: датчик, который может быть даже оберткой файла или жестко закодированными данными (например, фиктивное тестирование).

  • Acme4680Sensor: датчик ACME модель 4680 (отлично подходит для обнаружения, когда Roadrunner находится поблизости). Это может реализовать несколько интерфейсов: возможно, этот датчик обнаруживает как температуру, так и влажность. Этот объект содержит состояние программного уровня, такое как «датчик подключен?» и "что было последним чтением?"

  • Acme4680SensorComm: используется исключительно для связи с физическим устройством. Это не поддерживает много государства. Используется для отправки и получения сообщений. У него есть метод C # для каждого сообщения, которое понимает аппаратное обеспечение.

  • HardwareManager: используется для получения устройств. По сути, это фабрика, которая кэширует экземпляры: для каждого аппаратного устройства должен быть только один экземпляр объекта устройства. Он должен быть достаточно умен, чтобы знать, что если поток A запрашивает датчик температуры ACME, а поток B запрашивает датчик влажности ACME, это фактически один и тот же объект, и его следует возвращать в оба потока.


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

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

На один уровень ниже это фактические классы, которые реализуют эти интерфейсы: они представляют одно устройство, подобное Acme4680Sensor I, описанному выше. Любой конкретный класс может реализовывать несколько интерфейсов, если устройство может выполнять несколько функций.

Каждый класс устройств имеет свой собственный класс связи (связи), который обрабатывает низкоуровневую задачу общения с оборудованием.

Вне аппаратного модуля единственный видимый уровень - это интерфейсы / перечисление плюс HardwareManager. Класс HardwareManager - это фабричная абстракция, которая обрабатывает создание экземпляров классов устройств, кэширование экземпляров (вы на самом деле не хотите, чтобы два класса устройств общались с одним и тем же аппаратным устройством) и т. Д. Класс, которому требуется датчик определенного типа, просит HardwareManager получить устройство для конкретного перечисления, которое оно затем выясняет, если оно уже было создано, если нет, как его создать и инициализировать, и т. д.

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


Пример диаграммы классов UML, показывающий конструкцию, описанную в этом ответе

Примечание: существуют ассоциации между HardwareManager и каждым классом устройств, которые я не рисовал, потому что диаграмма превратилась бы в суп со стрелками.


источник
Очень интересный ответ. Я бы попросил быстрое редактирование: оставьте более явным, что является наследованием / сдерживанием / сотрудничеством между четырьмя классами, которые вы упомянули. Большое спасибо!
хелтонбайкер
Я добавил диаграмму классов UML. Это помогает?
2
Я бы сказал, что это убийственный ответ, и он не только мне сильно поможет, но я верю, что в будущем может помочь гораздо большему количеству людей. Кроме того, пример иерархии классов, который вы предоставляете, очень хорошо отображает наши области задач и решений. Большое спасибо еще раз!
Хелтонбайкер
Когда я прочитал вопрос, я понял, что это еще не все, поэтому я немного переборщил и поделился всем дизайном. Это больше, чем просто проблема именования, потому что имена указывают на дизайн. Я работал с системами, построенными таким образом, и они работали довольно хорошо.
11

Здесь может быть немного трудно найти единое объединяющее соглашение, потому что эти классы распределены по ряду пространств имен ( ConnectionInfoкажется, внутри CrystalDecisionsи DeviceInfoвнутри System.Reporting.WebForms).

Однако, глядя на эти примеры, можно увидеть два разных использования суффикса:

  • Различение класса, предоставляющего статические методы, с классом, предоставляющим методы экземпляра. Это тот случай, когда System.IOклассы подчеркнуты их описаниями:

    Каталог :

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

    DirectoryInfo :

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

    InfoЭто выглядит немного странным выбором, но это делает разницу относительно ясной: Directoryкласс может разумно либо представлять конкретный каталог, либо предоставлять общие вспомогательные методы, связанные с каталогом, без удержания какого-либо состояния, тогда как в DirectoryInfoдействительности может быть только первым.

  • Подчеркивая, что класс содержит только информацию и не обеспечивает поведения, которое можно разумно ожидать от имени без суффикса .

    Я думаю, что последняя часть этого предложения может быть частью головоломки, которая отличает, скажем, ConnectionInfoот EmployeeInfo. Если бы у меня был класс с именем Connection, я бы разумно ожидал, что он на самом деле предоставит мне функциональность, которая есть у соединения - я бы искал такие методы, как void Open()и т. Д. Однако никто в здравом уме не ожидал бы, что Employeeкласс может на самом деле делать то, что Employeeделает реальный , или искать методы, как void DoPaperwork()или bool TryDiscreetlyBrowseFacebook().

Бен Ааронсон
источник
1
Большое спасибо за ваш ответ! Я полагаю, что первая буква в вашем ответе определенно описывает, что происходит в классах .NET, но вторая имеет большую ценность (и, кстати, отличается), потому что она лучше раскрывает предполагаемую абстракцию: вещь, которая будет использоваться против вещь, которая будет описана. Трудно было выбрать, какой ответ принять.
Хелтонбайкер
4

Как правило, Infoобъект инкапсулирует информацию о состоянии объекта в определенный момент времени . Если бы я попросил систему взглянуть на файл и дать мне FileInfoобъект, связанный с его размером, я бы ожидал, что этот объект сообщит о размере файла во время запроса (или, точнее, о размере файла). файл в какой-то момент между тем, когда был сделан вызов и когда он вернулся). Если размер файла изменяется между временем возврата запроса и временем FileInfoпроверки объекта, я не ожидал бы, что такое изменение будет отражено в FileInfoобъекте.

Обратите внимание, что это поведение будет сильно отличаться от поведения Fileобъекта. Если запрос на открытие файла на диске в неисключительном режиме приводит к Fileобъекту, у которого есть Sizeсвойство, я ожидаю, что возвращаемое им значение изменится при изменении размера файла на диске, поскольку Fileобъект не просто представляет состояние файл - он представляет сам файл .

Во многих случаях объекты, которые подключаются к ресурсу, должны быть очищены, когда их службы больше не нужны. Поскольку *Infoобъекты не прикрепляются к ресурсам, они не требуют очистки. Как следствие, в случаях, когда Infoобъект будет удовлетворять требованиям клиента, может быть, лучше использовать код один, чем использовать объект, который будет представлять базовый ресурс, но чье соединение с этим ресурсом должно быть очищено.

Supercat
источник
1
Это не совсем так, но он не появляется , чтобы объяснить закономерности присвоения имен .NET очень хорошо на всех, так как Fileкласс не может быть создан и FileInfoобъекты сделать обновление с основной файловой системой.
Натан Тагги,
@NathanTuggy Я согласен, что это не очень хорошо согласуется с этим соглашением об именах в .NET, но что касается моей текущей проблемы, я считаю, что это очень актуально и может привести к последовательному решению. Я проголосовал за этот ответ, но трудно выбрать только один, чтобы принять ...
heltonbiker
@NathanTuggy: Я бы не рекомендовал использовать .NET в качестве модели какой-либо согласованности. Это хороший и полезный фреймворк, который воплощает в себе множество хороших идей, но несколько плохих идей также отражаются в миксе.
суперкат
@supercat: Конечно; просто кажется странным ответить на вопрос о том, «почему .NET так поступает?» с "было бы неплохо делать вещи% THIS_WAY%".
Натан Тагги
@NathanTuggy: Я не помню точных деталей рассматриваемых классов, но я думал, что FileInfoпросто держал статически захваченную информацию. Не так ли? Кроме того, у меня никогда не было случая открывать файлы в неисключительном режиме, но это должно быть возможно, и я ожидаю, что существует метод, который сообщит о текущем размере файла, даже если объект, используемый для открытия, не является называется File(обычно я просто использовать ReadAllBytes, WriteAllBytes, ReadAllText, WriteAllTextи т.д.).
суперкат
2

Учитывая фактические устройства (датчики и приемники) - это одно, а их представление в программном обеспечении - другое, я думаю о присвоении имен некоторым классам с помощью шаблона имени суффикса «Info».

Например, хотя a Sensorбудет классом для представления фактического датчика (когда он фактически подключен к какому-либо работающему устройству), SensorInfoон будет использоваться для представления только характеристик такого датчика. Например, при сохранении файла я бы сериализовал a SensorInfoв заголовок файла вместо сериализации a Sensor, что даже не имело бы смысла.

Мне не нравится это различие. Все объекты являются «представлением в программном обеспечении». Вот что означает слово «объект».

Теперь, возможно, имеет смысл отделить информацию о периферии от фактического кода, который взаимодействует с периферией. Так, например, у Sensor есть SensorInfo, который содержит большинство переменных экземпляра, а также некоторые методы, которые не требуют аппаратного обеспечения, в то время как класс Sensor отвечает за фактическое взаимодействие с физическим датчиком. У вас нет -a, Sensorесли у вашего компьютера нет датчика, но вы, вероятно, можете иметь -a SensorInfo.

Беда в том, что этот вид дизайна можно обобщить на (почти) любой класс. Поэтому вы должны быть осторожны. У вас, очевидно, не будет SensorInfoInfoкласса, например. И если у вас есть Sensorпеременная, вы можете столкнуться с нарушением закона Деметры , взаимодействуя с ее SensorInfoчленом. Конечно, все это не фатально, но дизайн API не только для авторов библиотек. Если вы сохраните свой собственный API чистым и простым, ваш код будет более понятным.

Ресурсы файловой системы, такие как каталоги, на мой взгляд, очень близки к этому краю. Есть некоторые ситуации, в которых вы хотите описать каталог, который не является локально доступным, правда, но обычный разработчик, вероятно, не находится в одной из этих ситуаций. Такое усложнение структуры классов, на мой взгляд, бесполезно. Противопоставьте подход Python pathlib: Существует один класс, который «скорее всего вам нужен», и различные вспомогательные классы, которые большинство разработчиков могут спокойно игнорировать. Однако, если они вам действительно нужны, они предоставляют в основном один и тот же интерфейс, просто с удалением конкретных методов.

Kevin
источник
Спасибо за ваш ответ и слава за вашу конструкцию "иметь-a";) Ваш ответ напоминает мне о дискомфорте, вызванном некоторыми из-за концепции "DTO" (Data Transfer Object) - или его родственного объекта Parameter Object - который осуждается более ортодоксальными фанатиками ОО, потому что он обычно не содержит методов (в конце концов, это объект передачи данных ). В этом смысле и суммируя также то, что сказали другие, я думаю, что SensorInfoэто будет своего рода DTO, и / или также как «снимок» или даже «спецификация», представляющая только часть данных / состояния реального Sensorобъекта что "может даже не быть".
Хелтонбайкер
Как и в случае с большинством проблем дизайна, здесь нет строгих правил. В приведенном мною примере pathlib PureWindowsPathон немного похож на объект Info, но у него есть методы для выполнения задач, не требующих системы Windows (например, создание подкаталога, выделение расширения файла и т. Д.). Это более полезно, чем просто предоставление прославленной структуры.
Кевин
1
Снова, спасибо за "прославленную структуру" часть :). Это напоминает мне похожую цитату из книги «Чистый код» дяди Боба, глава 6: «Зрелые программисты знают, что идея о том, что все является объектом, - это миф. Иногда вам действительно нужны простые структуры данных с процедурами, работающими на них».
Хелтонбайкер
2

Я бы сказал, что контекст / домен имеет значение, поскольку у нас есть код бизнес-логики высокого уровня и низкоуровневые модели, компоненты архитектуры и так далее ...

«Информация», «Данные», «Менеджер», «Объект», «Класс», «Модель», «Контроллер» и т. Д. Могут быть вонючими суффиксами, особенно на более низком уровне, поскольку каждый объект имеет некоторую информацию или данные, поэтому эта информация не нужна.

Имена классов бизнес-домена должны быть похожи на разговоры всех заинтересованных сторон, независимо от того, звучит ли это странно или не является на 100% правильным языком.

Хорошими суффиксами для структур данных являются, например, «Список», «Карта», а также для шаблонов подсказок «Декоратор», «Адаптер», если вы считаете, что это необходимо.

По сценарию с вашим датчиком я не ожидал SensorInfoбы сохранить то, чем является ваш датчик, но SensorSpec. Infoimho - это скорее производная информация, например, FileInfoразмер, который вы не сохраняете, или путь к файлу, который создается из пути и имени файла и т. д.

Еще один момент:

В информатике есть только две сложные вещи: аннулирование кэша и именование.

- Фил Карлтон

Это всегда напоминает мне о том, что я думаю о названии всего несколько секунд, и если я его не нашел, я использую странные имена и помечаю их «TODO». Я всегда могу изменить его позже, так как моя IDE обеспечивает поддержку рефакторинга. Это не лозунг компании, который должен быть хорошим, а просто какой-то код, который мы можем изменить каждый раз, когда захотим. Запомни.

крестцовой
источник
1

ThingInfo может служить отличным прокси-сервером только для чтения.

см. http://www.dofactory.com/net/proxy-design-pattern

Прокси: «Предоставьте суррогат или заполнитель для другого объекта для контроля доступа к нему».

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

Используйте ThingInfo всякий раз, когда это возможно, и ограничивайте использование фактического Thing теми временами, когда вам действительно нужно изменить объект Thing. Это делает чтение и отладку значительно быстрее, когда вы привыкнете использовать этот шаблон.

Shmoken
источник
Отлично. Ранее сегодня я анализировал свои архитектурные потребности в отношении классов, которые я использовал в вопросе, и пришел к такому же выводу. В моем случае у меня есть, Receiverкоторый получает потоки данных от многих Sensors. Идея такова: приемник должен абстрагировать фактические датчики. Но проблема: мне нужен датчик информации , так что я могу записать их в какой - то заголовок файла. Решение: у каждого IReceiverбудет список SensorInfo. Если я посылаю команды получателю, которые подразумевают изменение состояния датчика, эти изменения будут отражены (через геттер) на соответствующем SensorInfo.
Хелтонбайкер
(продолжение ...), то есть: я не говорю с самими датчиками, я говорю только с Приемником с помощью команд более высокого уровня, а Приемник, в свою очередь, разговаривает с каждым из Датчиков. Но в конце концов мне нужна информация от датчиков, которую я получаю от экземпляра Receiver через его List<SensorInfo>свойство только для чтения.
Хелтонбайкер
1

Пока что никто в этом вопросе, похоже, не понял реальной причины такого соглашения об именах.

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

Сравните это с классом по имени Employee. Это может быть объект сущности ORM, и это единственный объект, описывающий этого сотрудника. Если это был объект-значение без идентичности, он должен быть вызван EmployeeInfo. An Employeeдействительно представляет фактического работника. Ценностный класс DTO, подобный значению EmployeeInfo, явно не представляет сотрудника, а скорее описывает его или хранит данные о нем.

На самом деле в BCL есть пример, где существуют оба класса: A ServiceController- это класс, который описывает службу Windows. Таких контроллеров может быть любое количество для каждой услуги. A ServiceBase(или производный класс) - это фактический сервис, и концептуально не имеет смысла иметь несколько его экземпляров для каждого отдельного сервиса.

USR
источник
Это похоже на Proxyшаблон, упомянутый @Shmoken, не так ли?
heltonbiker
@heltonbiker это не обязательно должен быть прокси. Это может быть объект, который никак не связан с тем, что он описывает. Например, вы можете получить EmployeeInfoот веб-службы. Это не прокси. Еще один пример: у An AddressInfoдаже нет вещи, которую он может прокси, потому что адрес не является сущностью. Это отдельная ценность.
USR
1
Я вижу, это имеет смысл!
heltonbiker