Большие Внутренние классы и частные переменные - PullRequest
9 голосов
/ 20 октября 2008

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

/** Asset service keeps track of the metadata about assets that live on other
 * systems. Complications include the fact the assets have a lifecycle and their
 * physical representation lives on other systems that have to be polled to find
 * out if the Asset is still there. */
public class AssetService
{
  //...various private variables
  //...various methods

  public AssetService()
  {
    Job pollerJob = jobService.schedule( new AssetPoller() );
    Job lifeCycleJob = jobService.schedule( AssetLifecycleMonitor() );
  }

  class AssetPoller
  {
    public void run()
    { 
      // contact remote systems and update this service's private variables that
      // track the assets.
    }
  }

  class AssetLifecycleMonitor
  {
    public void run()
    {
      // look for assets that have meet criteria for a lifecycle shift
      // and update this service's private variables as relevant.
    }
  }
}

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

Я заканчиваю тем, что выставляю методы доступа уровня пакета, чтобы разбитые классы могли получить доступ к переменным, тогда как раньше я вообще не выставлял сеттеры, поскольку внутренние классы имели прямой доступ. Плюс, все становится немного более объемным, поскольку я постоянно вызываю методы доступа, а не лежащие в их основе переменные. Незначительная гнида, само собой разумеющееся. Удобные методы (например, checkAssetIsValid () или некоторые другие) теперь нуждаются в представлении на уровне пакета, чтобы вспомогательные классы могли вызывать их, где, как и прежде, как внутренние классы, они могут быть закрытыми. Хуже того, мне нужно передать класс реализации службы конструкторам вспомогательных классов, поскольку я не хочу показывать эти методы-помощники в интерфейсе, который реализует служба, потому что это заставляет их быть открытыми. Это может создать некоторые проблемы с модульным тестом / издевательством. Хуже того, любая синхронизация, которую я хотел сделать, просачивается через какой-то внешний удобный метод (например, lockDownAssets () во время обновления программы опроса). Раньше внутренние классы имели доступ к закрытым замкам.

Таким образом, короче говоря, разделение классов теряет некоторую инкапсуляцию, которая мне нравится. Но если оставить их, это может привести к большим файлам Java. Я еще не нашел хороший способ справиться с этим. В C ++ было понятие «друзья», которое я редко пропускал, но на самом деле помогло бы в этом случае.

Мысли

Ответы [ 5 ]

7 голосов
/ 21 октября 2008

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

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

  • Ваш внутренний класс имеет скрытую зависимость от родительского класса whole , который запутывает его входящий интерфейс. Если вы извлечете его как закрытый для пакета класс, у вас будет шанс улучшить свой дизайн и сделать его более понятным. Первоначально это более многословно, но часто вы обнаружите, что:
    • Вместо предоставления 10 методов доступа вы на самом деле хотите использовать один объект-значение. Часто вы обнаружите, что вам не нужна ссылка на весь внешний класс. Это также хорошо работает с IoC.
    • Вместо предоставления методов для явной блокировки удобнее инкапсулировать операцию с ее контекстом в отдельный класс (или переместить ее в один из двух классов - внешний или ранее внутренний).
    • Удобные методы принадлежат частным служебным классам пакета. Вы можете использовать статический импорт Java5, чтобы они выглядели как локальные.
  • Ваш внешний класс может обходить любые уровни защиты и напрямую получать доступ к закрытым членам вашего внутреннего класса. Это само по себе неплохо, но лишает вас одного из языковых средств выражения вашего замысла.
  • Поскольку ваш внутренний класс встроен ровно в один внешний класс, единственный способ использовать его повторно - это создать подкласс внешнего класса. Альтернативой может быть передача явной ссылки на частный интерфейс пакета, который реализует внешний класс. Это позволит вам издеваться над внешним и лучше тестировать внутренний класс.
  • Хотя недавние отладчики довольно хороши, у меня раньше были проблемы с отладкой внутренних классов (путаница в области условных точек останова, не останавливаясь на точках останова и т.
  • Приватные классы раздувают ваш байт-код. См. Мой первый абзац - часто есть API, который вы могли бы использовать и уменьшить количество искусственных отходов.

P.S. Я говорю о нетривиальных внутренних классах (особенно тех, которые не реализуют никаких интерфейсов). Три реализации слушателя строки хороши.

3 голосов
/ 20 октября 2008

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

Я думаю, что разумно иметь внешние вспомогательные служебные классы, которые используются во многих местах, если они не имеют побочных эффектов, и я не вижу проблем. Также разумно иметь статические вспомогательные классы, если они хорошо организованы и содержат часто используемые методы, такие как checkAssetIsValid (). Это предполагает, что checkAssetIsValid не должен обращаться к какому-либо внешнему состоянию, кроме объекта, который вы передаете ему.

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

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

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

Надеюсь, кое-что из этого будет полезно. Я просто болтаю здесь.

3 голосов
/ 21 октября 2008

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

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

Трудно ли управлять кодом, например, отладка и предотвращение непреднамеренных побочных эффектов становится все труднее.

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

1 голос
/ 21 октября 2008

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

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

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

0 голосов
/ 20 октября 2008

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

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

Полагаю, для этого нет простого решения.

...