Несколько классов с одинаковым именем, но разными пространствами имен?

11

Я столкнулся с некоторым кодом (C #, если это имеет значение), который имеет классы, которые имеют одинаковые имена, но различаются по своим пространствам имен. Все они, как правило, представляют одну и ту же логическую вещь, но часто являются разными «взглядами» на один и тот же объект. Разные пространства имен иногда являются частью одного и того же решения и даже одной и той же библиотеки DLL.

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

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

Каковы основные принципы этой практики?

Telastyn
источник
1
В ООП есть много исключений, но сначала я рассматриваю этот плохой дизайн, особенно если все классы имеют одинаковый уровень доступности. Когда я читаю имя класса в любом файле, я хочу знать, в каком пространстве имен класс расположен немедленно, без необходимости ссылаться на директивы пространства имен.
Леопольд Аспергер
Существует допустимый случай наличия нескольких связанных объектов, имеющих дело с одним и тем же состоянием, но обычно они разделяются по функциям. Может быть, у вас есть один объект, который адаптирует модель для представления. Другой может выполнить расчет на объекте. Другой может быть адаптер или фасад. Таким образом , вы могли бы FooAdapter, FooViewer, FooCalculatorи т.д. , но , конечно , не назвал то же самое. Однако, без дополнительной информации о конкретных классах, о которых вы говорите, трудно дать ответ.
@JohnGaughan - рассмотрим Employeeв качестве примера. Есть сотрудник, который предназначен для других сотрудников, один для HR, один для внешнего воздействия. Все они называются «работники», и у них всех есть различные помощники (EmployeeRepository, EmployeeView и т. Д.). Все они находятся в одной и той же dll, и хотя они имеют некоторые общие свойства (имя, должность), все они различаются (номер телефона, зарплата).
Теластин
Поработав с системами, которые моделируют Employeeнесколько раз, звучит так, будто вам нужна модель с объединением всех атрибутов и включением атрибута typeили department. В противном случае происходит ненужное дублирование кода.
5
Разве это не было одной из причин для пространства имен для начала? Разрешить конфликт имен?
Дэн Пичельман

Ответы:

5

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

Читаемость кода

Прежде всего, учтите, что читабельность кода важна, и эта практика противоречит этому. Кто-то читает кусок кода, и давайте просто скажем, что у него есть функция doSomething(Employee e). Это больше не доступно для чтения, потому что из-за 10 различных Employeeклассов, существующих в разных пакетах, вам придется сначала прибегнуть к объявлению импорта, чтобы выяснить, что вы на самом деле делаете.

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

Вещи распадаются, как только вы покидаете эти пакеты. Если вы работаете в другом модуле / пакете / и т. Д. И вам необходимо работать с сотрудником, вы уже жертвуете удобочитаемостью, если не полностью определяете его тип. Кроме того, наличие 10 различных Employeeклассов означает, что автозаполнение IDE больше не будет работать, и вам придется вручную выбирать тип сотрудника.

Дублирование кода

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

Сложность кода

Что может быть таким сложным, спросите вы? В конце концов, каждый из классов может держать это так просто, как он хочет. Что действительно становится проблемой, так это то, как вы распространяете изменения. В достаточно многофункциональном программном обеспечении вы можете изменять данные о сотрудниках - и вы хотите отразить это повсюду. Скажите, что одна женщина только что вышла замуж, и вы должны переименовать ее XвYблагодаря этому. Достаточно сложно сделать это правильно повсюду, но еще сложнее, когда у вас есть все эти разные классы. В зависимости от вашего другого выбора дизайна, это может легко означать, что каждый из классов должен реализовать свой собственный тип слушателя или что-то подобное, чтобы получать уведомления об изменениях - что в основном означает фактор, применяемый к числу классов, с которыми вам приходится иметь дело , И, конечно, больше дублирования кода и меньшая читаемость кода. Подобные факторы могут не учитываться при анализе сложности, но они, несомненно, раздражают применительно к размеру вашей кодовой базы.

Код связи

Помимо вышеупомянутых проблем со сложностью кода, которые также связаны с выбором дизайна, вы также снижаете четкость проектирования на более высоком уровне и теряете способность правильно общаться с экспертами в предметной области. Когда вы обсуждаете архитектуру, дизайн или требования, вы больше не можете делать простые заявления вроде given an employee, you can do .... Разработчик больше не будет знать, что на employeeсамом деле означает в этот момент. Хотя специалист по домену это будет знать конечно. Все мы делаем. вроде. Но с точки зрения программного обеспечения это не так легко общаться больше.

Как избавиться от проблемы

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

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

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

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

Короче говоря: эта «практика» технически обоснована, но она полна скрытых затрат, которые произойдут только в будущем. Хотя вы, возможно, и не сможете сразу избавиться от проблемы, вы можете немедленно начать с сдерживания ее вредных последствий.

Фрэнк
источник
Наличие базового класса кажется хорошим решением. Другие классы могут расширяться от этого. Что касается IDE, автозаполнение достаточно умен, чтобы знать, какой класс предложить вам лучше всего соответствовать контексту. В целом, я не понимаю, почему это такая большая проблема, особенно если кодовая база используется внутри.
ИнформированоA
1
Это недостаточно умно, когда вы находитесь вне одного контекста. Рассмотрим класс, который на самом деле нуждается в обоих проблемных классах - автозаполнение не поможет вообще. И это даже не будет отличать их от других десяти классов. Но настоящая проблема в том, что новый член команды, который даже не знает, что на самом деле представляют собой все эти домены пакета, и какой он должен выбрать.
Фрэнк
Если несколько классов с одинаковыми именами используются в одном контексте, то это сложно. Я не видел этот случай. Я видел несколько классов с одним и тем же именем, но они не часто используются в том же контексте, как вы упоминали.
ИнформированоA
@randomA - к сожалению, этот случай довольно распространен в этой кодовой базе.
Теластин
Хорошие моменты, но вы на самом деле не дали решение основной проблемы, только методологию после того, как основная проблема решена («что такое Сотрудник на самом деле»). Единственное очевидное «решение» («объединение всех отдельных атрибутов в одного служителя»), от которого вы отказались. Скажем, у вас есть DB.Employee, Marshalling.Employee, ViewModels.Employee, GUI.Views.Employee и т. Д., Каково ваше решение?
Пабло Х
9

Scala Collection Framework является хорошим примером этого. Существуют изменяемые и неизменные коллекции, а также последовательные и параллельные (и в будущем, возможно, также распространяемые) коллекции. Итак, есть, например, четыре Mapчерты:

scala.collection.Map
scala.collection.immutable.Map
scala.collection.mutable.Map
scala.collection.concurrent.Map

плюс три ParMapс:

scala.collecion.parallel.ParMap
scala.collecion.parallel.immutable.ParMap
scala.collecion.parallel.mutable.ParMap

Еще интереснее

scala.collection.immutable.Map            extends scala.collection.Map
scala.collection.mutable.Map              extends scala.collection.Map
scala.collection.concurrent.Map           extends scala.collection.mutable.Map

scala.collecion.parallel.ParMap           extends scala.collection.Map
scala.collecion.parallel.immutable.ParMap extends scala.collecion.parallel.ParMap
scala.collecion.parallel.mutable.ParMap   extends scala.collecion.parallel.ParMap

Итак, ParMapрасширяется ParMapрасширяется Mapи Mapрасширяется Mapрасширяется Map.

Но давайте посмотрим правде в глаза: как еще вы бы их назвали? Это имеет смысл. Вот для чего нужны пространства имен!

Йорг Миттаг
источник
3
«Вот для чего нужны пространства имен!» => +1
svidgen
Мне это нравится, но в мире C # / .NET, когда я переместил параллельные классы в подпространство имен Parallel, это затем вступает в конфликт с вспомогательным классом System.Threading.Parallel, который я часто использую для получения параллелизма. Я не хотел использовать пространство имен 'Concurrent' вместо этого, поскольку оно уже используется для обозначения 'одновременного доступа' в классах коллекции. В качестве компромисса я выбрал подпространство имен 'Parallelized'.
Redcalx
2

Это действительно интересный вопрос, в котором оба принципиально противоположных ответа верны. Вот реальный пример этой дискуссии, которую мы ведем по моему проекту.

Я думаю, что на данный момент есть два очень важных соображения, которые заставили бы вас пойти в одном или другом направлении, опираясь на два ответа @Jorg и @Frank:

  1. Если вы предвидите ситуацию, когда в одном и том же контексте кода может потребоваться использование нескольких классов с одинаковыми именами, это причина не использовать одно и то же имя. То есть, если вам нужно работать, hr.Employeeно в то же время нужно смотреть через разрешения через security.Employee, это очень быстро раздражает.
  2. И наоборот, если все ваши классы с одинаковыми именами имеют одинаковые или очень похожие API, это хороший пример того, когда вам следует использовать одно и то же имя с разными пространствами имен. Именно такой пример Scala, где все Mapклассы реализуют один и тот же интерфейс или являются подклассами друг друга. В этом случае двусмысленность или «путаницу» можно назвать хорошей вещью, потому что пользователь по праву может относиться ко всем реализациям класса или интерфейса одинаково ... что является точкой интерфейсов и подклассов.
S'pht'Kr
источник