В ходе изучения языка все реже возникают вопросы "Как решить задачу?" и все чаще встает вопрос "Какой способ более правильный с точки зрения хорошего тона программирования?"
К примеру, в ходе написания программы, реализующей возможность ведения складского учета, возник вопрос, какой класс использовать: анонимный, внешний или внутренний? Есть класс главного окна программы, реализующий JDesktopPane. Есть отдельный класс "Каталог товаров", реализующий JInternalFrame. Из последнего, нажатием кнопок, открывается окно Карточки товара" (также класс extends JInternalFrame). В свою очередь кликом по кнопкам он реализует открытие окон выбора группы товара, единицы измерения, категории и т.д. Все это тоже JInternalFrame
Вот теперь стоит вопрос, какой тип класса "эстетичнее" использовать? Логика подсказывает делать все классы ниже "Каталога товаров" вложенными в него, а все слушатели нажатия кнопок - анонимными. Но тогда класс получается под тысячу строк кода, что влияет на читаемость. Или это нормально? В общем я как тот осел, между двумя стогами сена.
Вот, к примеру, такой участок кода. Оптимален ли он, или стоит изменить структуру?
package catalogs;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.beans.PropertyVetoException;
import java.io.File;
import java.util.ArrayList;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.ListSelectionModel;
import javax.swing.event.TableModelListener;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.TableModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import main.Global;
/**
* Класс окна справочника товара. Выводит окно каталога товаров с возможностью добавления/удаления/редактирования групп товаров и самих
* товаров. Представляет собой древо групп товаров, таблицу товаров, кнопки добавления/удаления/редактирования.
*/
public class ProductCatalog extends JInternalFrame{
private static final long serialVersionUID = 1L;
private JDesktopPane mainPane;
public JTree treeGroup;
private GroupTreeModel treeGroupModel;
private JScrollPane treeGroupScrlPane;
private JButton btnAddGroup;
private JButton btnDelGroup;
private JButton btnEditGroup;
public JTable tblProduct;
public ProdTableModel tblProductModel;
private JScrollPane tblProductScrlPane;
private JButton btnAddProduct;
private JButton btnDelProduct;
private JButton btnEditProduct;
/**
* Конструктор, принимает ссылку на JDesctopPane главного окна.
*/
public ProductCatalog(JDesktopPane mainPane){
this.mainPane = mainPane;
paintComponents();
}
/**
* Метод отрисовки компонент окна.
*/
private void paintComponents(){
treeGroupModel = new GroupTreeModel();
treeGroup = new JTree(treeGroupModel);
treeGroup.setSelectionRow(0);
treeGroup.addTreeSelectionListener(new TreeSelectionListener(){
@Override
public void valueChanged(TreeSelectionEvent selection) {
if(!treeGroup.isSelectionEmpty()){
tblProductModel.buildTable(treeGroup.getLastSelectedPathComponent());
tblProduct.revalidate();
tblProduct.repaint();
}
}
});
treeGroupScrlPane = new JScrollPane(treeGroup);
treeGroupScrlPane.setBounds(10, 10, 150, 260);
btnAddGroup = new JButton();
ImageIcon btnAddGroupIcon = new ImageIcon(Global.getAppPath()+File.separator+"images"+File.separator+"btnAddGroup.png");
btnAddGroup.setIcon(btnAddGroupIcon);
btnAddGroup.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
addGroup();
}
});
btnAddGroup.setBounds(10, 280, 30, 30);
btnDelGroup = new JButton();
ImageIcon btnDelGroupIcon = new ImageIcon(Global.getAppPath()+File.separator+"images"+File.separator+"btnDelGroup.png");
btnDelGroup.setIcon(btnDelGroupIcon);
btnDelGroup.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
delGroup();
}
});
btnDelGroup.setBounds(50, 280, 30, 30);
btnEditGroup = new JButton();
ImageIcon btnEditGroupIcon = new ImageIcon(Global.getAppPath()+File.separator+"images"+File.separator+"btnEditGroup.png");
btnEditGroup.setIcon(btnEditGroupIcon);
btnEditGroup.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
editGroup();
}
});
btnEditGroup.setBounds(90, 280, 30, 30);
tblProductModel = new ProdTableModel();
tblProduct = new JTable(tblProductModel);
tblProductModel.buildTable(treeGroup.getLastSelectedPathComponent());
tblProduct.setAutoCreateRowSorter(true);
tblProduct.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
tblProduct.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
tblProduct.getColumnModel().getColumn(0).setPreferredWidth(25);
tblProduct.getColumnModel().getColumn(1).setPreferredWidth(200);
tblProduct.getColumnModel().getColumn(2).setPreferredWidth(25);
tblProduct.getColumnModel().getColumn(3).setPreferredWidth(50);
tblProductScrlPane = new JScrollPane(tblProduct);
tblProductScrlPane.setBounds(170, 10, 500, 260);
btnAddProduct = new JButton();
ImageIcon btnAddProductIcon = new ImageIcon(Global.getAppPath()+File.separator+"images"+File.separator+"btnAddProduct.png");
btnAddProduct.setIcon(btnAddProductIcon);
btnAddProduct.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
addProduct();
}
});
btnAddProduct.setBounds(170, 280, 30, 30);
btnDelProduct = new JButton();
ImageIcon btnDelProductIcon = new ImageIcon(Global.getAppPath()+File.separator+"images"+File.separator+"btnDelProduct.png");
btnDelProduct.setIcon(btnDelProductIcon);
btnDelProduct.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
delProduct();
}
});
btnDelProduct.setBounds(210, 280, 30, 30);
btnEditProduct = new JButton();
ImageIcon btnEditProductIcon = new ImageIcon(Global.getAppPath()+File.separator+"images"+File.separator+"btnEditProduct.png");
btnEditProduct.setIcon(btnEditProductIcon);
btnEditProduct.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
editProduct();
}
});
btnEditProduct.setBounds(250, 280, 30, 30);
this.setTitle("Каталог товаров");
this.addComponentListener(new ComponentListener(){
@Override
public void componentResized(ComponentEvent e) {
treeGroupScrlPane.setBounds(10, 10, 150, ProductCatalog.this.getHeight()-87);
btnAddGroup.setBounds(10, ProductCatalog.this.getHeight()-67, 30, 30);
btnDelGroup.setBounds(50, ProductCatalog.this.getHeight()-67, 30, 30);
btnEditGroup.setBounds(90, ProductCatalog.this.getHeight()-67, 30, 30);
tblProductScrlPane.setBounds(170, 10, ProductCatalog.this.getWidth()-200, ProductCatalog.this.getHeight()-87);
btnAddProduct.setBounds(170, ProductCatalog.this.getHeight()-67, 30, 30);
btnDelProduct.setBounds(210, ProductCatalog.this.getHeight()-67, 30, 30);
btnEditProduct.setBounds(250, ProductCatalog.this.getHeight()-67, 30, 30);
}
@Override
public void componentHidden(ComponentEvent arg0) {}
@Override
public void componentMoved(ComponentEvent arg0) {}
@Override
public void componentShown(ComponentEvent arg0) {}
});
this.setClosable(true);
this.setMaximizable(true);
this.setIconifiable(true);
this.setResizable(true);
this.getContentPane().setLayout(null);
this.setSize(700, 347);
this.setVisible(true);
this.add(treeGroupScrlPane);
this.add(btnAddGroup);
this.add(btnDelGroup);
this.add(btnEditGroup);
this.add(tblProductScrlPane);
this.add(btnAddProduct);
this.add(btnDelProduct);
this.add(btnEditProduct);
}
/**
* Рекурсивный метод построения массива элементов древа групп товаров.
*/
ArrayList arr = new ArrayList();
public ArrayList getComboBoxTree(Object parent, String separator){
Object[][] buf = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `parent`='"+parent+"'");
for(int i=0; i0) getComboBoxTree(buf[i][0], " "+separator);
}
return arr;
}
/**
* Метод, реализующий добавление группы товаров в БД.
*/
private void addGroup(){
String name = JOptionPane.showInputDialog(null, "Введите название новой подгруппы
в группе \""+ treeGroup.getLastSelectedPathComponent() +"\":", "");
if(name!=null){
Object[][] buf = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `name`='"+ name +"'");
if(buf.length==0){
if(name.length()>0){
Object[][] parentGroup = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `name`='"+ treeGroup.getLastSelectedPathComponent() +"'");
Global.sqlQueryVoid("INSERT INTO `prod_group` (`name`, `parent`) VALUES ('"+ name +"', '"+ parentGroup[0][0] +"')");
treeGroup.updateUI();
}else JOptionPane.showMessageDialog(null, "Наименование группы товаров не может быть пустым!", "Ошибка!", JOptionPane.ERROR_MESSAGE);
}else JOptionPane.showMessageDialog(null, "Группа товаров с таким наименованием уже существует!", "Ошибка!", JOptionPane.ERROR_MESSAGE);
}
}
/**
* Метод, реализующий удаление группы товаров из БД.
*/
private void delGroup(){
boolean isEmpty = false;
Object[][] buf = Global.sqlQueryResult("SELECT `id` FROM `prod_group` WHERE `name`='"+ treeGroup.getLastSelectedPathComponent() +"'");
Object[][] buf2 = Global.sqlQueryResult("SELECT * FROM `product` WHERE `group`='"+ buf[0][0] +"'");
Object[][] buf3 = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `parent`='"+ buf[0][0] +"'");
if(buf2.length==0 && buf3.length==0) isEmpty = true;
if(!treeGroup.getLastSelectedPathComponent().equals(null)){ //Если выделена группа,
if(isEmpty){ //она пуста
if(JOptionPane.showConfirmDialog(null, "Вы уверены?", "Подтверждение удаления", JOptionPane.YES_NO_OPTION)==0){ //и мы подтверждаем удаление, тогда удаляем.
Global.sqlQueryVoid("DELETE FROM `prod_group` WHERE `name`='"+ treeGroup.getLastSelectedPathComponent() +"'");
treeGroup.updateUI();
}
}else JOptionPane.showMessageDialog(null, "Нельзя удалить эту папку, т.к. она не пуста!", "Ошибка!", JOptionPane.ERROR_MESSAGE);
}else JOptionPane.showMessageDialog(null, "Вы не выбрали ни одну папку!", "Ошибка!", JOptionPane.ERROR_MESSAGE);
}
/**
* Метод, реализующий редактирование группы товаров в БД.
*/
private void editGroup(){
if(treeGroup.getSelectionRows()[0] != 0){ //Если выбрана не корневая группа, переименовываем группу товаров.
final JInternalFrame ifEditGroup;
final JComboBox cbParent;
final JLabel lblName;
final JTextField tfName;
final JButton btnOK;
final JButton btnCancel;
final ArrayList arr;
ifEditGroup = new JInternalFrame();
JLabel lblParent = new JLabel("Родительская группа:");
lblParent.setBounds(10, 10, 150, 20);
cbParent = new JComboBox();
cbParent.setBounds(170, 10, 250, 20);
arr = getComboBoxTree(0, "");
for(int i=0; i ifEditGroup.getContentPane().setLayout(null);
ifEditGroup.add(lblParent);
ifEditGroup.add(cbParent);
ifEditGroup.add(lblName);
ifEditGroup.add(tfName);
ifEditGroup.add(btnOK);
ifEditGroup.add(btnCancel);
ifEditGroup.setTitle("Редактирование группы товаров");
ifEditGroup.setSize(450, 130);
ifEditGroup.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
ifEditGroup.setVisible(true);
boolean isOpened = false;
Object[] ifArr = mainPane.getAllFrames();
for(int i=0; i/**
* Метод, реализующий добавление товара в БД.
*/
private void addProduct(){
boolean isOpened = false;
Object[] ifArr = mainPane.getAllFrames();
for(int i=0; i/**
* Метод, реализующий удаление товара из БД.
*/
private void delProduct(){
if(JOptionPane.showConfirmDialog(null, "Вы уверены?", "Подтверждение удаления", JOptionPane.YES_NO_OPTION)==0){
String code = ProductCatalog.this.tblProduct.getValueAt(ProductCatalog.this.tblProduct.getSelectedRow(), 0).toString();
Global.sqlQueryVoid("DELETE FROM `product` WHERE `id`='"+code+"'");
Global.sqlQueryVoid("DELETE FROM `prod_barcode` WHERE `prod_id`='"+code+"'");
ProductCatalog.this.tblProductModel.buildTable(ProductCatalog.this.treeGroup.getLastSelectedPathComponent());
ProductCatalog.this.tblProduct.revalidate();
ProductCatalog.this.repaint();
}
}
/**
* Метод, реализующий редактирование товара в БД.
*/
private void editProduct(){
boolean isOpened = false;
Object[] arr = mainPane.getAllFrames();
for(int i=0; i/**
* Модель дерева.
*/
private class GroupTreeModel implements TreeModel{
@Override
public Object getChild(Object parent, int index) {
Object[][] buf = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `name`='"+ parent +"'");
Object parentCode = buf[0][0];
Object[][] res = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `parent`='"+ parentCode +"'");
return res[index][1];
}
@Override
public int getChildCount(Object parent) {
Object[][] buf = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `name`='"+ parent +"'");
Object parentCode = buf[0][0];
Object[][] res = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `parent`='"+ parentCode +"'");
return res.length;
}
@Override
public int getIndexOfChild(Object parent, Object child) {
Object[][] buf = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `name`='"+ parent +"'");
Object parentCode = buf[0][0];
Object[][] res = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `parent`='"+ parentCode +"'");
int index = 0;
for(int i =0; i
/**
* Модель таблицы.
*/
public class ProdTableModel implements TableModel{
private Object[][] sample = null;
public void buildTable(Object group){ //Метод построения двумерного массива данных, на основе которого отрисовывается таблица товаров.
Object[][] buf = Global.sqlQueryResult("SELECT `id` FROM `prod_group` WHERE `name`='"+ group +"'");
sample = Global.sqlQueryResult("SELECT `product`.`id`, `product`.`name`, `prod_unit`.`short_name`, `product`.`comments` "
+ "FROM `product` LEFT JOIN `prod_unit` ON `product`.`unit`=`prod_unit`.`id`"
+ " WHERE `product`.`group`='"+ buf[0][0] +"'");
}
@Override
public Class getColumnClass(int colInd) {
return String.class;
}
@Override
public int getColumnCount() {
return 4;
}
@Override
public String getColumnName(int colInd) {
String colName ="";
switch(colInd){
case 0: colName = "Код"; break;
case 1: colName = "Наименование"; break;
case 2: colName = "Ед. изм."; break;
case 3: colName = "Комментарий"; break;
}
return colName;
}
@Override
public int getRowCount() {
return sample.length;
}
@Override
public Object getValueAt(int rowInd, int colInd) {
return sample[rowInd][colInd];
}
@Override
public void addTableModelListener(TableModelListener arg0) {}
@Override
public boolean isCellEditable(int arg0, int arg1) {return false;}
@Override
public void removeTableModelListener(TableModelListener arg0) {}
@Override
public void setValueAt(Object arg0, int arg1, int arg2) {}
}
}
Ответ
Анонимные классы удобны там, где цель существования типа - применение
в одной единственной точке на всё приложение.
Внутренний (нестатический) класс существует внутри внешнего и может
быть там скрыт. Кроме того сам имеет доступ к скрытому состоянию
родителя.
Если такие "суперспособности" не принесут ощутимой пользы, то стоит склоняться к отдельным классам.
Отдельные классы читаются лучше (и соответствуют Single
responsibility) при прочих равных.
А вообще, это достаточно тонкая грань между хорошим кодом и плохим. Чувство прекрасного развивается при чтении чужого кода, где ты распутывая клубок алгоритма подмечаешь для себя какие-то элегантные решения и злишься на излишнюю запутанность других.