Страницы

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

суббота, 4 января 2020 г.

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

#java #шаблоны_проектирования


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

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

    


Ответы

Ответ 1



Анонимные классы удобны там, где цель существования типа - применение в одной единственной точке на всё приложение. Внутренний (нестатический) класс существует внутри внешнего и может быть там скрыт. Кроме того сам имеет доступ к скрытому состоянию родителя. Если такие "суперспособности" не принесут ощутимой пользы, то стоит склоняться к отдельным классам. Отдельные классы читаются лучше (и соответствуют Single responsibility) при прочих равных. А вообще, это достаточно тонкая грань между хорошим кодом и плохим. Чувство прекрасного развивается при чтении чужого кода, где ты распутывая клубок алгоритма подмечаешь для себя какие-то элегантные решения и злишься на излишнюю запутанность других.

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

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