Разложение большого класса в Java - PullRequest
11 голосов
/ 24 февраля 2009

Я только начал изучать Java, и мне любопытно, есть ли хорошая практика в Java для хорошей декомпозиции объектов? Позвольте мне описать проблему. В большом программном проекте это всегда большие классы, такие как «core» или «ui», которые имеют много методов и предназначены в качестве посредников между небольшими классами. Например, если пользователь нажимает кнопку в каком-либо окне, класс этого окна отправляет сообщение в класс 'ui'. Этот класс 'ui' ловит это сообщение и действует соответствующим образом, делая что-то с пользовательским интерфейсом приложения (посредством вызова метода одного из его объектов-членов) или отправляя сообщение в «ядро» приложения, если это что-то вроде «выхода из приложения» или «запуска сети». подключение.

Такие объекты очень трудно разбить на части, поскольку они являются просто посредниками между множеством небольших прикладных объектов. Но иметь в приложении классы с сотнями и тысячами методов не очень удобно, если такие методы представляют собой тривиальное делегирование задачи от одного объекта к другому. C # решает такую ​​проблему, позволяя разбить реализацию класса на несколько исходных файлов: вы можете разделить объект бога любым способом, который вы выберете, и он будет работать.

Есть ли какие-либо практики по разделению таких объектов в Java?

Ответы [ 5 ]

15 голосов
/ 24 февраля 2009

Один из способов начать разбивать такой большой объект на части - это сначала найти хорошее подмножество полей или свойств, которыми управляет большой объект, которые связаны друг с другом и не взаимодействуют с другими полями или свойствами объекта. Затем создайте новый, меньший объект, используя только эти поля. То есть переместите всю логику из большого класса в новый меньший класс. В исходном большом классе создайте метод делегирования, который просто передает запрос. Это хороший первый шаг, который включает в себя только изменение большого объекта. Это не уменьшает количество методов, но может значительно уменьшить количество логики, необходимой в большом классе.

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

См. Шаблон делегирования Википедии обсуждение, например.

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

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

...
public boolean isManagement() { ... }
public boolean isExecutive() { ... }
public int getYearsOfService() { ... }
public Date getHireDate() { ... }
public int getDepartment() { ... }
public BigDecimal getBasePay() { ... }
public BigDecimal getStockShares() { ... }
public boolean hasStockSharePlan() { ... }
...

тогда этот большой объект может в своем конструкторе создать вновь созданный объект StaffType и вновь созданный объект PayInformation и вновь созданный объект StaffInformation, и первоначально эти методы в большом объекте будут выглядеть следующим образом:

// Newly added variables, initialized in the constructor (or as appropriate)
private final StaffType      staffType;
private final StaffInformation staffInformation;
private final PayInformation payInformation;

...

public boolean isManagement() { return staffType.isManagement(); }
public boolean isExecutive() { return staffType.isExecutive(); }
public int getYearsOfService() { return staffInformation.getYearsOfService(); }
public Date getHireDate() { return staffInformation.getHireDate(); }
public int getDepartment() { return staffInformation.getDepartment(); }
public BigDecimal getBasePay() { return payInformation.getBasePay(); }
public BigDecimal getStockShares() { return payInformation.getStockShares(); }
public boolean hasStockSharePlan() { return payInformation.hasStockSharePlan(); }
...

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

1 голос
/ 24 февраля 2009

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

Преимущества этого - уменьшение зависимости и производительности.

  1. Нет необходимости импортировать весь пакет / BigClass, чтобы получить несколько методы.
  2. Код меняется на связанный функциональность не требует перекомпилировать / перераспределить весь пакет / BigClass.
  3. меньше используемой памяти для размещения / освобождения объектов, так как вы используете меньшие классы.
1 голос
/ 24 февраля 2009

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

BigPart1 // all methods dealing with topic #1
BigPart2 extends BigPart1 // all methods dealing with topic #2
...
Big extends BigPart4 // all methods dealing with the last topic.

Если вы действительно можете наслоить вещи так, чтобы разрыв имел смысл (Part2 фактически использует материал из Part1, но не наоборот и т. Д.), Тогда, возможно, это имеет какой-то смысл.

Место, где я видел это, находится в WebWorks, где у одного класса было множество методов getter / setter - сеттеры, используемые для внедрения зависимостей (например, URL-аргументы, передаваемые объекту при выполнении) и геттеры для сделать значения доступными для различных шаблонов страниц (я думаю, это были JSP).

Итак, разбивка сгруппировала вещи логически, например, предполагая, что класс был назван MyAction, был MyActionBasicArgs (поля и установщики для основных аргументов CGI), расширенный MyActionAdvancedArgs (аргументы расширенной опции), расширенный MyActionExposedValues ​​(геттерами) расширенный MyActionDependencies (сеттеры, используемые инъекцией зависимостей Spring, не-CGI-аргументы), расширенный MyAction (который содержит метод execute ()).

Из-за того, как инъекция зависимостей в WebWorks работает (или, по крайней мере, когда-то работала), это должен был быть один огромный класс, поэтому его разбиение делало вещи более удобными в обслуживании. Но сначала, пожалуйста, пожалуйста, посмотрите, сможете ли вы просто избежать одного огромного класса; тщательно продумайте свой дизайн.

0 голосов
/ 24 февраля 2009

Я не знаю, почему у вас такой большой класс.

Полагаю, если вы использовали генерацию кода в GUI Builder и ленились об этом, вы могли бы в конечном итоге оказаться в такой ситуации, но Codegen обычно заканчивал противно, если вы не взяли контроль над собой.

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

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

Старайтесь никогда не редактировать сгенерированный код, если вы можете избежать этого. Поместить бизнес-логику в генрированную «рамку» - это просто ужасный шаблон дизайна. Большинство генераторов кода не очень помогают в этом, поэтому попробуйте просто сделать одно минимальное редактирование, чтобы получить то, что вам нужно от внешних классов (например, MVC, где genned-код - это ваш View, а код, который вы редактируете, должен быть в вашей модели). и контроллер).

Иногда вы можете просто представить метод getComponents из объекта Frame, извлечь все компоненты, перебирая контейнеры, а затем динамически связать их с данными и кодом (часто хорошо работает привязка к свойству name), я был таким образом можно безопасно использовать редакторы форм, и весь код привязки очень легко абстрагируется и используется повторно.

Если вы не говорите о сгенерированном коде - ну, в вашем классе "Бог", он выполняет ровно одну небольшую работу и делает это хорошо? Если нет, вытащите «Job», поместите его в свой собственный класс и делегируйте ему.

Ваш класс БОГА полностью учтен? Когда я видел такие огромные классы, я обычно видел много строк копирования / вставки / редактирования. Если сходства достаточно для копирования, прохождения и редактирования какого-либо раздела, то этого достаточно для того, чтобы разделить эти строки на один фрагмент кода.

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

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

0 голосов
/ 24 февраля 2009

Да, C # предоставляет частичные классы. Я предполагаю, что это то, что вы имеете в виду, когда говорите:

C # решает такую ​​проблему, позволяя разбить реализацию класса на несколько источников файлы: вы можете разделить объект бога любым способом по вашему выбору, и он будет работать.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...