2012-04-18 2 views
0

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

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

Человек, который был обеспокоен этим, думал, что это была упомянутая плохая практика, и старался не делать этого вообще, хотя я «Мы редко видели серверы, особенно большие, которые сохраняли всю обработку пакетов внутри класса клиента. Кроме того, вы можете пойти дальше, чем обработчик, и вызвать больше классов. Это беспорядок, чтобы держать все это внутри Клиента. Итак ... это действительно плохая практика?

Если да, то как бы обойти его? Чтобы это сделать, вам понадобится более или менее сложный сбор объектов в Клиенте, который вы можете передать без необходимости повторять вызовы функций клиента ...

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

+0

F # делает циркулярные ссылки на класс реальной забавой. – ChaosPandion

+0

Просмотрите список связанных вопросов. ---------> похоже, есть несколько, которые могли бы помочь ответить на вопрос. –

+0

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

ответ

5

Когда люди говорят о круговых ссылках, они обычно говорят о ссылках проекта/dll. Это проблематично, когда дело доходит до разрешения ссылок, а Visual Studio не позволит вам добавлять циклические ссылки.

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

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

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

Давайте рассмотрим очень урезанную гипотетическую ситуацию, основанную на вашем OP, которая имеет циклические ссылки: Клиент вызывает метод Send() обработчика пакета. Обработчик пакетов теперь запускает соединение, а затем обнаруживает, что ему нужно имя пользователя/пароль. Он вызывает метод на клиенте для его получения, затем отправляет его на сервер, получает ответ, затем обращается к клиенту, чтобы вернуть его.

В этом случае обработчик пакетов теперь привязан к реализации клиента. Для этого клиент должен иметь метод GetCredentials() и метод MessageReceived, чтобы перезвонить ему. Теперь представьте более развязанный сценарий:

Клиент сначала регистрируется для события ResponseReceived обработчика. Теперь клиент вызывает метод Send() обработчика пакета. Обработчик пакетов нуждается в аутентификации, поэтому он терпит неудачу - он выдает исключение или возвращает код ошибки, говорящий «не удается подключиться». Клиент получает этот ответ и снова звонит, на этот раз используя метод «Отправить (имя пользователя, пароль)». Он преуспевает, получает ответ и вызывает событие ResponseReceived, отправляя ответ на того, кто подписался на него.

Это позволяет повторному использованию обработчика пакетов в других контекстах от другого клиента. Это позволяет делать изменения внутри клиента или обработчика с меньшим воздействием на другие компоненты. Это упрощает и упрощает работу с кодом. И это хорошо. :)

0

Вы можете попытаться извлечь функции, которые A называет B, и те, которые B называет A, а затем инкапсулировать их в библиотеку классов C, которые могут быть вызваны другими библиотеками без проблем. Очевидно, что C не должен иметь ссылки ни на A, ни на B.

3

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

Но это точка, где вы должны остановиться и подумать. Особенно в следующих ситуациях (есть больше, но они находятся в верхней части моей головы):

  • Когда вы уведомляя класс некоторые действие СЛУЧИЛОСЬ -> сделать это событие.
  • Если это отношение один к одному -> следует объединить типы в один.
  • Когда ваш класс расширяет функциональные возможности другого -> унаследовал от него вместо передачи ссылки.

В вашей ситуации клиент < -> обработчик пакетов. Я, вероятно, не буду хранить циркулярную ссылку. Я, скорее всего, напишу контроллер для обоих классов, который действует как прокси для информации между ними. Я также не буду хранить идентификатор учетной записи в объекте клиента. Это немного противоречит принципу единой ответственности, к которому я люблю придерживаться. Я, скорее всего, в конечном итоге с чем-то вроде этого:

  • Client -> гнездо (отвечает за сетевой материал)
  • ClientHandler (ответственный за управление потоком)
    • Client
    • PacketHandler
    • Дополнительная информация
  • PacketHandler
2

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

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

Смежные вопросы