Страницы

Поиск по вопросам

четверг, 31 января 2019 г.

Внутренний vs Анонимный vs Отдельный классы

В ходе изучения языка все реже возникают вопросы "Как решить задачу?" и все чаще встает вопрос "Какой способ более правильный с точки зрения хорошего тона программирования?"
К примеру, в ходе написания программы, реализующей возможность ведения складского учета, возник вопрос, какой класс использовать: анонимный, внешний или внутренний? Есть класс главного окна программы, реализующий 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) при прочих равных.
А вообще, это достаточно тонкая грань между хорошим кодом и плохим. Чувство прекрасного развивается при чтении чужого кода, где ты распутывая клубок алгоритма подмечаешь для себя какие-то элегантные решения и злишься на излишнюю запутанность других.

Комментариев нет:

Отправить комментарий