Исходный код
public void updateTable() {
/**
* Удаляем лишние строки таблицы, оставшиейся от предыдущего списка
*/
while (viewingData.getResult().getRowDataItems().size() + 1 < dataTable.getRowCount()) {
dataTable.removeRow(dataTable.getRowCount()-1);
}
/**
* Отображаем заголовки таблицы
*/
int titleColumnIndex=0;
String[] columnIds = newString[viewingData.getViewingColumns().size()];
for (String viewingColumnId : viewingData.getViewingColumns().keySet()) {
columnIds[titleColumnIndex] = viewingColumnId;
ViewingColumnData viewingColumn= viewingData.getViewingColumns().get(viewingColumnId);
HorizontalPanel titlePanel=new HorizontalPanel();
titlePanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
Widget columnTitleButton;
if (viewingColumn.isSortable()) {
columnTitleButton = newButton(viewingColumn.getTitle());
((Button)columnTitleButton).addClickListener(newSortDataClickListener(viewingColumnId));
} else {
columnTitleButton = newLabel(viewingColumn.getTitle());
}
titlePanel.add(columnTitleButton);
columnTitleButton.setStyleName(TABLE_STYLE);
if (viewingData.getOrderColumnId() != null && viewingData.getOrderColumnId().equals( viewingColumnId) ) {
Image orderDirectionImage;
if (viewingData.getOrderDesc()) {
orderDirectionImage = new Image(GWT.getModuleBaseURL() + UP_ARROW_IMG);
} else {
orderDirectionImage = new Image(GWT.getModuleBaseURL() + DOWN_ARROW_IMG);
}
titlePanel.add(orderDirectionImage);
} else {
}
dataTable.setWidget(0, titleColumnIndex, titlePanel);
FlexTable.FlexCellFormatter titlePanelFormatter = dataTable.getFlexCellFormatter();
titlePanelFormatter.setStyleName(0, titleColumnIndex, "tableTitle");
if (viewingColumn.getWidth() > 0) {
titlePanelFormatter.setWidth(0, titleColumnIndex, viewingColumn.getWidth() + "%");
}
titlePanelFormatter.setHorizontalAlignment(0, titleColumnIndex, HasHorizontalAlignment.ALIGN_CENTER);
titleColumnIndex++;
}
/**
* Удаляем лишние столбцы в заголовке
*/
intcolumnToRemove= dataTable.getCellCount(0) - viewingData.getViewingColumns().size();
dataTable.removeCells(0, viewingData.getViewingColumns().size(), columnToRemove);
/**
* Добавляем чекбокс
*/
addTitleCheckbox();
/**
* Отображаем значения раскрытых групп
*/
if (viewingData.getGroupingFilterItems() != null) {
for (intgroupingIndex=0; groupingIndex < viewingData.getGroupingFilterItems().size(); groupingIndex++ ) {
String groupingFilterValue = viewingData.getGroupingFilterItems().get(groupingIndex);
Label valueLabel = new Label(groupingFilterValue);
Image img = new Image(LEFT_ARROW_IMG);
HorizontalPanel valuePanel = new HorizontalPanel();
valuePanel.add(img);
valuePanel.add(valueLabel);
valuePanel.setWidth("100%");
valuePanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
ClickListener hideGroupClickListener = new HideGroupClickListener(groupingIndex);
valueLabel.addClickListener(hideGroupClickListener);
img.addClickListener(hideGroupClickListener);
String cellStyle;
if (groupingIndex % 2 == 0) {
cellStyle = "tableRow";
} else {
cellStyle = "tableRowOdd";
}
for (int column=0; column < viewingData.getViewingColumns().size(); column++) {
String text=null;
/**
*
*/
if (groupingIndex == viewingData.getGroupingFilterItems().size()-1 && viewingData.getFunctionData().containsKey(columnIds[column])) {
text = "(" + viewingData.getFunctionData().get(columnIds[column]) + ")";
} else {
Как делать рефакторинг «извлечение метода»
Ищем короткие фрагменты, которые некие маленькие, законченные и осмысленные действия, и переносим эти фрагменты в отдельные методы. Это могут быть методы в том же самом или в совсем других классах.
Обычно фрагмент определяется тем, какие переменные в нём задействованы.
Удаление лишних строк из таблицы
Вот такой фрагмент есть, выполняет осмысленное и обособленное действие:
while (viewingData.getResult().getRowDataItems().size() + 1 < dataTable.getRowCount()) {
dataTable.removeRow(dataTable.getRowCount()-1);
}
Это действие сильно завязано на экземпляр dataTable
, поэтому логично перенести код в метод этого экземпляра.
public void shrinkRowsTo(maxRowsCount)
{
while (dataTable.getRowCount() > maxRowsCount) {
dataTable.removeRow(dataTable.getRowCount() - 1);
}
}
Код метода максимально обобщённый, не привязанный ни к каким другим объектам. Задача — удалить лишние строки, а откуда берётся количество, сколько строк оставить — методу не важно.
Исходный фрагмент будет выглядеть теперь так:
dataTable.shrinkRowsTo(viewingData.getResult().getRowDataItems().size() + 1);
Создание кнопки-заголовка
Widget columnTitleButton;
if (viewingColumn.isSortable()) {
columnTitleButton = new Button(viewingColumn.getTitle());
((Button)columnTitleButton).addClickListener(newSortDataClickListener(viewingColumnId));
} else {
columnTitleButton = new Label(viewingColumn.getTitle());
}
titlePanel.add(columnTitleButton);
columnTitleButton.setStyleName(TABLE_STYLE);
В этом коде заключено сразу два знания: как создать кнопку-заголовок, и куда потом её добавить.
Первое знание как раз следует извлечь в новый метод. Так как реализация крутится в основном вокруг экземпляра viewingColumn
, то и метод должен стать его.
public Widget createColumnTitleButton()
{
Widget columnTitleButton;
if (isSortable()) {
columnTitleButton = newButton(getTitle());
((Button)columnTitleButton).addClickListener(newSortDataClickListener(getId()));
} else {
columnTitleButton = newLabel(getTitle());
}
columnTitleButton.setStyleName(TABLE_STYLE);
return columnTitleButton;
}
titlePanel.add(viewingColumn.createColumnTitleButton());
Создание кнопки с иконкой направления сортировки
if (viewingData.getOrderColumnId() != null && viewingData.getOrderColumnId().equals( viewingColumnId) ) {
Image orderDirectionImage;
if (viewingData.getOrderDesc()) {
orderDirectionImage = new Image(GWT.getModuleBaseURL() + UP_ARROW_IMG);
} else {
orderDirectionImage = new Image(GWT.getModuleBaseURL() + DOWN_ARROW_IMG);
}
titlePanel.add(orderDirectionImage);
}
В этом фрагменте кода, опять же, заключены несколько знаний: надо ли добавлять иконку, как создать иконку, куда её добавить.
Первые два знания тесно связаны с экземпляром viewingData
, поэтому перенесу их туда.
public boolean isSortedColumn(String columnId)
{
return getOrderColumnId() != null && getOrderColumnId().equals(columnId);
}
public Image createDirectionImage()
{
returnnewImage(GWT.getModuleBaseURL() + (getOrderDesc() ? UP_ARROW_IMG : DOWN_ARROW_IMG));
}
Заодно избавился от небольшого дублирования реализации при создании экземпляра иконки.
В результате исходный код рефакторится следующим образом:
if (viewingData.isSortedColumn(viewingColumnId)) {
titlePanel.add(viewingData.createDirectionImage());
}
Создание форматтера
Следующий автономный фрагмент посвящён созданию некоего форматтера:
FlexTable.FlexCellFormattertitlePanelFormatter= dataTable.getFlexCellFormatter();
titlePanelFormatter.setStyleName(0, titleColumnIndex, "tableTitle");
if (viewingColumn.getWidth() > 0) {
titlePanelFormatter.setWidth(0, titleColumnIndex, viewingColumn.getWidth() + "%");
}
titlePanelFormatter.setHorizontalAlignment(0, titleColumnIndex, HasHorizontalAlignment.ALIGN_CENTER);
Хотя совершенно непонятно, как он цепляется к таблице. Предположу, что когда-то эта функциональность была нужна, а потом её частично выпилили и забыли.
При любом раскладе этот код надо извлечь в отдельный метод. Я бы отнёс этот код к экземпляру dataTable
:
public FlexTable.FlexCellFormatter createFlexColumnFormatter(int titleColumnIndex, ViewingColumnData viewingColumn)
{
FlexTable.FlexCellFormattertitlePanelFormatter= dataTable.getFlexCellFormatter();
titlePanelFormatter.setStyleName(0, titleColumnIndex, "tableTitle");
if (viewingColumn.getWidth() > 0) {
titlePanelFormatter.setWidth(0, titleColumnIndex, viewingColumn.getWidth() + "%");
}
titlePanelFormatter.setHorizontalAlignment(0, titleColumnIndex, HasHorizontalAlignment.ALIGN_CENTER);
return titlePanelFormatter;
}
Удаление лишних колонок из таблицы
int columnToRemove = dataTable.getCellCount(0) - viewingData.getViewingColumns().size();
dataTable.removeCells(0, viewingData.getViewingColumns().size(), columnToRemove);
Смысл этого кода в том, чтобы оставить в заголовке dataTable
столько колонок, сколько есть в viewingData
.
Здесь тоже два знания: как удалять колонки и где взять количество, сколько штук оставить.
Так как действие касается экземпляра dataTable
, есть резон создать в нём отдельный метод для укорачивания колонок:
public void shrinkTitleColumns(int shrinkTo)
{
removeCells(0, shrinkTo, getCellCount(0) - shrinkTo);
}
Как и в прошлый раз со строками, метод максимально обобщённый, работает просто с количеством колонок, независимо от того, откуда оно взялось.
Исходный примет следующий вид:
dataTable.shrinkTitleColumns(viewingData.getViewingColumns().size());
Теперь здесь осталось только одно знание: где взять нужное количество колонок; о подробностях собственно удаления знает только метод shrinkTitleColumns
.
Создание полей значений
Дальше идёт довольно переплетённый фрагмент кода:
String groupingFilterValue= viewingData.getGroupingFilterItems().get(groupingIndex);
LabelvalueLabel=newLabel(groupingFilterValue);
Imageimg=newImage(LEFT_ARROW_IMG);
HorizontalPanelvaluePanel=newHorizontalPanel();
valuePanel.add(img);
valuePanel.add(valueLabel);
valuePanel.setWidth("100%");
valuePanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
ClickListenerhideGroupClickListener=newHideGroupClickListener(groupingIndex);
valueLabel.addClickListener(hideGroupClickListener);
img.addClickListener(hideGroupClickListener);
Его апофеозом является создание экземпляра valuePanel
, хотя последовательность действий неочевидна. Поскольку всё начинается с viewingData
, пусть извлечённый метод относится к нему. Заодно попробую немного переупорядочить строки так, чтобы стали лучше видны взаимозависимости между объектами.
public HorizontalPanel createValuePanel(int groupingIndex)
{
HorizontalPanel valuePanel = new HorizontalPanel();
ClickListener hideGroupClickListener = new HideGroupClickListener(groupingIndex);
String groupingFilterValue = getGroupingFilterItems().get(groupingIndex);
Label valueLabel = newLabel(groupingFilterValue);
valueLabel.addClickListener(hideGroupClickListener);
valuePanel.add(valueLabel);
Image img=newImage(LEFT_ARROW_IMG);
img.addClickListener(hideGroupClickListener);
valuePanel.add(img);
valuePanel.setWidth("100%");
valuePanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
return valuePanel;
}
Теперь фрагменты реализаций, нацеленные на формирование и добавление метки и иконки, максимально сгруппированы.
В этом фрагменте по-прежнему содержатся много знаний: как формировать метку, как добавлять метку, как формировать иконку, как добавлять иконку, как форматировать виджет — но критической необходимости разделять эти знания пока нет.
Как будет выглядеть отрефакторенный код
public void updateTable() {
dataTable.shrinkRowsTo(viewingData.getResult().getRowDataItems().size() + 1);
int titleColumnIndex=0;
String[] columnIds = newString[viewingData.getViewingColumns().size()];
for (String viewingColumnId : viewingData.getViewingColumns().keySet()) {
columnIds[titleColumnIndex] = viewingColumnId;
ViewingColumnData viewingColumn= viewingData.getViewingColumns().get(viewingColumnId);
HorizontalPanel titlePanel = new HorizontalPanel();
titlePanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
titlePanel.add(viewingColumn.createColumnTitleButton());
if (viewingData.isSortedColumn(viewingColumnId)) {
titlePanel.add(viewingData.createDirectionImage());
}
dataTable.setWidget(0, titleColumnIndex, titlePanel);
dataTable.createFlexColumnFormatter();
titleColumnIndex++;
}
dataTable.shrinkTitleColumns(viewingData.getViewingColumns().size());
addTitleCheckbox();
if (viewingData.getGroupingFilterItems() != null) {
for (intgroupingIndex=0; groupingIndex < viewingData.getGroupingFilterItems().size(); groupingIndex++ ) {
HorizontalPanelvaluePanel= viewingData.createValuePanel(groupingIndex);
// ...
Теперь можно сделать ещё несколько итераций рефакторинга, извлекая в новые методы код для формирования titlePanel
, всего набора колонок, фильтров и т. п.
Теория
- Принцип единственного знания
- Рефакторинг «извлечение метода»
- Завистливые методы
- GRASP
- Паттерн «Информационный эксперт»