How do you design one screen to handle many (simil

2019-05-26 03:37发布

问题:

Preface: I am trying to solve my problem without the need for boilerplate code. I could easily solve this problem by copy/pasting and refactoring code, but this kind of problem should be solvable with a model-driven approach because of the obvious recurrence of the design.

I have a database application with four different object types that will be used as parameters for another, central object.

All four fields are structurally the same: one auto-generated primary key, and one VARCHAR field. These fields need to be editable by the end-user, so I designed this screen:

Rather than making four different screens for this, I thought it would be good to make this one screen that is used by the four different object types. To accomplish this, I (obviously, right?) need to make an abstract Object (what I called) StringField which can be implemented by four subtypes: Status, Type, Classification, and Designation. As mentioned before, because every object is structurally identical and will read/write to the database the same exact way, an abstract object seemed logical. So, I made this object:

public abstract class StringField {

    private final int id;
    private final String value;
    private final String fieldname;
    private final String tablename;

    public StringField(int id, String value, String fieldname, String tablename) {
        this.id = id;
        this.value = value;
        this.fieldname = fieldname;
        this.tablename = tablename;
    }

    public String getValue() {
        return value;
    }

    public int getId() {
        return id;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 59 * hash + this.id;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        return this.id == ((StringField)obj).id;
    }

    @Override
    public String toString() {
        return value;
    }

    public int insertValue(DB db, String value) throws WrappedSQLException {

        ... // DB code here
    }

    public void updateValue(DB db, String value, int id) throws WrappedSQLException {

        ... // DB code here
    }

    public void deleteValue(DB db, int id) throws WrappedSQLException {

        ... // DB code here
    }
}

With subclasses implementing as follows (for example):

public class Type extends StringField {

    public Type(int id, String value) {
        super(id, value, "type", "customer_type");
    }
}

public class Classification extends StringField {

    public Classification (int id, String value) {
        super(id, value, "classification", "customer_classification");
    }
}

An aside: the reason for String fieldname and String tablename in the constructor is that Java does not allow for static abstract fields. So, they'll need to be supplied manually by the classes which use the super type. This, unfortunately, prevents the insert, update, and delete methods from being declared static as well. If Java allowed static abstract, I doubt I'd be asking this question at all because I would just make the insert, update, and delete methods static with fieldname and tablename as abstract static fields, which would be forced to be implemented by the subtypes in static fashion and could then be referenced statically by the superclass, which could then be called statically through each subtype. Basically, it could look like this:

Supertype:

public abstract class StringField {

    private final int id;
    private final String value;

    public StringField(int id, String value) {
        this.id = id;
        this.value = value;
    }

    public static abstract String fieldname;
    public static abstract String tablename;

    public static int insertValue(String value) {

        // Insert code, returns auto-generated primary key
        // ...
        // This code will use the static fields `fieldname` and `tablename`.
        // ...
        return -1;
    }

    public static void updateValue(String value, int id) {

        // Update code...
        // ...
        // This code will use the static fields `fieldname` and `tablename`.
        // ...
    }

    public static void deleteValue(int id) {

        // Delete code...
        // ...
        // This code will use the static fields `fieldname` and `tablename`.
        // ...
    }
}

Subtype:

static class Type extends StringField {

    public Type(int id, String value) {
        super(id, value);
    }

    public static String fieldname = "type";
    public static String tablename = "customer_type";
}

And used like:

Type.insertValue(newValue);

Ok, enough complaining about how Java doesn't allow static abstract. Here is the full, (almost) compile-able code I have so far that illustrates my approach:

@SuppressWarnings("serial")
public class StringFieldEditor extends JPanel {

    final Collection<StringField> fields;
    String fieldname;
    public StringFieldEditor(Collection<StringField> fields, String fieldname) {
        super(new GridBagLayout());
        this.fields = fields;
        this.fieldname = fieldname;
        GridBagConstraints gridBagConstraints;

        JButton addNew_button = new JButton();
        JButton delete_button = new JButton();
        JButton edit_button = new JButton();
        JLabel jLabel1 = new JLabel();
        JList<StringField> jList = new JList<>();
        jList.setModel(new DefaultListModel<>());
        jList.setListData(fields.toArray(new StringField[fields.size()]));
        JScrollPane jScrollPane1 = new JScrollPane();
        jScrollPane1.setViewportView(jList);

        jLabel1.setText("Customer " + fieldname + " Editor");
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new Insets(11, 10, 0, 0);
        add(jLabel1, gridBagConstraints);


        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 5;
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagConstraints.ipadx = 178;
        gridBagConstraints.ipady = 206;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        gridBagConstraints.insets = new Insets(6, 10, 0, 10);
        add(jScrollPane1, gridBagConstraints);

        addNew_button.setText("Add New");
        addNew_button.addActionListener((ActionEvent evt) -> {
            // Add New Task
        });
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new Insets(6, 10, 11, 0);
        add(addNew_button, gridBagConstraints);

        edit_button.setText("Edit");
        edit_button.addActionListener((ActionEvent evt) -> {
            // Edit Task
        });
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.gridwidth = 3;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new Insets(6, 6, 11, 0);
        add(edit_button, gridBagConstraints);

        delete_button.setText("Delete");
        delete_button.addActionListener((ActionEvent evt) -> {
            // Delete Task
        });
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new Insets(6, 6, 11, 10);
        add(delete_button, gridBagConstraints);
    }

    static class Type extends StringField {

        public Type(int id, String value) {
            super(id, value, "type", "customer_type");
        }
    }

    public abstract class StringField {

        private final int id;
        private final String value;
        private final String fieldname;
        private final String tablename;

        public StringField(int id, String value, String fieldname, String tablename) {
            this.id = id;
            this.value = value;
            this.fieldname = fieldname;
            this.tablename = tablename;
        }

        public String getValue() {
            return value;
        }

        public int getId() {
            return id;
        }

        @Override
        public int hashCode() {
            int hash = 5;
            hash = 59 * hash + this.id;
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            return this.id == ((StringField)obj).id;
        }

        @Override
        public String toString() {
            return value;
        }

        public int insertValue(String value) {

            // Insert code, returns auto-generated primary key
            // ...
            // ...
            return -1;
        }

        public void updateValue(String value, int id) {

            // Update code...
            // ...
            // ...
        }

        public void deleteValue(int id) {

            // Delete code...
            // ...
            // ...
        }
    }

    public static void main(String[] args) {
        LinkedList<Type> types = new LinkedList<>();
        types.add(new Type(1, "Type 1"));
        types.add(new Type(2, "Type 2"));
        types.add(new Type(3, "Type 3"));

        JFrame frame = new JFrame();
        frame.add(new StringFieldEditor(types, "type"));
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}

This, however, throws an error:

incompatible types: LinkedList<Type> cannot be converted to Collection<StringFieldEditor.StringField>

But changing:

    final Collection<StringField> fields;
    String fieldname;
    public StringFieldEditor(Collection<StringField> fields, String fieldname) {

to

    final Collection<? extends StringField> fields;
    String fieldname;
    public StringFieldEditor(Collection<? extends StringField> fields, String fieldname) {

causes issues when trying to load the data into the JList (toArray requires a type T value, but if I don't know the exact type I'm working with, I can't supply it a new T type array.

Also, I forsee the problem of trying to reference the non-static insert, update, and delete methods in a non-subclass specific way.

My questions:

  1. Is this approach viable? Am I taking this whole model-driven development approach too far?
  2. Is there a better way to achieve this functionality without creating boilerplate code?
  3. Is there a good workaround to the fact that there are no static abstract methods/fields in Java for this?
  4. Is there a way to supply the Collections::toArray(T[]) method a usable array when you don't know which subtype you'll be using?