2

I want to implement a TableModel, that will enable my existing data structure to be displayed by a JTable.
Say my data structure is a simple List<MyRow>, where MyRow is:

public class MyRow {
        String firstName;
        String secondName;      
}

My idea is that this class "should do its part" in being displayed, so i add it the following method, that simply maps each field to an index:

public Object getValueAt(int columnIndex) {
        switch (columnIndex) {
            case 0: return firstName;
            case 1: return secondName;
            default: return null;
        }
    }

At this point, the TableModel implementation will simply be:

public class MyModel extends AbstractTableModel {

    ArrayList<MyRow> list = new ArrayList<MyRow>();        

    public int getRowCount() {return list.size();}
    public int getColumnCount() {return 2;}
    public Object getValueAt(int rowIndex, int columnIndex) {
        return list.get(rowIndex).getValueAt(columnIndex);
    }
}

Neat, isn't it? :-)
Ok, but return 2; annoys me. In this "architecture" i have assigned to MyRow the rexposibility to represent a row, and so it should be MyRow that gives the number of columns. That because it provides the getValueAt(int columnIndex) method that is related to the number of columns: columnsIndex<columnCount.
This piece of information may be asked even if there are no rows in the List, so no instances of MyRow. So the method to add to MyRow should be a static method:

public class MyRow {
   ...
public static int getColumnCount() { return 2; }
   ...

And the getColumnCount() method of MyModel will be simply modified accordingly:

public int getColumnCount() {
    //return 2; //No more this *Encapsulation Violation*
    return MyRow.getColumnCount();
}

Now MyRow contains all the data needed to represent a row. It is easy to show that: adding, removing or changing columns is just question of modifying MyRow.
Clean, isn't it? :-) :-)

The problem is that with static methods, i can't provide an abstract super-class for MyRow. This should be obvious, but to be sure:

public abstract class MyAbstractRow {
    public static int getColumnCount() { return 0; }
}

then

public class MyRow extends MyAbstractRow{
    String firstName;
    String secondName;
    public static int getColumnCount() { return 2; }
}

then i implement a "generic" MyData, that uses a MyAbstractRow:

public int getColumnCount() {
    //return 2; //No more this *Encapsulation Violation*
    return MyAbstractRow.getColumnCount();
}

This is resolved on the static type (so returns 0) because the getColumnCount of MyData doesn't even have a reference to the type i actually want to use as my concrete implementation of MyAbstractRow.

How to solve this problem, mantaining the good level of encapsulation achieved so far?

AgostinoX
  • 7,477
  • 20
  • 77
  • 137
  • 2
    To make it more interesting, what will you do with the other column-related methods like `getColumnName` and `getColumnClass` – Robin Feb 05 '12 at 17:52
  • 1
    What do you gain by having this abstract class? Shouldn't you rather define your own MyAbstractTableModel that delegates to a MyAbstractRow for getValueAt(), but not for getColumnCount()? – JB Nizet Feb 05 '12 at 17:59
  • 1
    @AgostinoX are you really needed List, isn't better Vector> (implemented in JTable's API) or not_premature HashMap/ Map hmmm nice answer (by @trashgod) http://stackoverflow.com/a/9134371/714968, all these methods returns XxxTableModel by default – mKorbel Feb 05 '12 at 18:01
  • @Robin getColumnName and getColumnClass will be treated exactly like getColumnCount. if static methods were the solution, i would implement them with static methods too. – AgostinoX Feb 05 '12 at 19:39
  • @JBNizet the idea is bridging a collection (a List in this case) of plain objects to represent them on a JTable. After all, this is the power of TableModel: not implementing a redundant data structure, but connecting to an already existent one. Is frequent that an application has an already existent and well defined data set represented by some sort of collection. – AgostinoX Feb 05 '12 at 19:50
  • 4
    All the rows have the same number of columns. So this information doesn't belong to the row. Frankly, a row of data should thus be that: data. It shouldn't care how it's displayed in the GUI, which could be a web GUI, a tabular GUI, a tree GUI, or anything else. The table model is just an adapter around raw data, I don't think you would gain anything by making it work in any other way. Making an abstract table model class to wrap a list and factor common list-based methods is OK. I wouldn't go further than that. – JB Nizet Feb 05 '12 at 19:53
  • @JB Nizet: "All the rows have the same number of columns". Yep, this is exactly the point. The first, naive, idea i had was make those information static, so not instance-related but class-related. -I agree with you that moving the getValueAt inside the data object violates single responsibility of the data object itself, that may be just a so-called "POJO". – AgostinoX Feb 05 '12 at 20:42
  • 1
    static is _not_ the answer for OO, never. So your perceived "problem" is home-brewn :-) There are approaches for automagically building tableModels by reflection, search for BeansTableModel or use a binding framework, like f.i. Better/BeansBinding – kleopatra Feb 06 '12 at 09:37

1 Answers1

4

You are mixing model and view concepts. Your model MyRow should not know how it will be represented and so it should not know how many columns there are, what they contain, etc... This is why you run into this problem (using a static method to know the number of columns).

One way to do this more cleanly is to have your TableModel return the number of columns and also implement the getValueAt by invoking the members of your MyRow class. Did you take a look at the default implementation (DefaultTableModel).

Now I don't understand why you need to override static methods (which is impossible). If you need to have different table models for different objects that inherit each other, then you might consider creating different table models that inherit each other in the same way.

Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
  • Ok, there is an approach error in mixing view and model. as far as the static method, it is just a naive refactoring attempt, aimed at bridging a tablemodel to a list of pojo. from this the distinction between the row-level info and the "whole collection" level info such as getColumnCount. As kleopatra pointed out, static is not the answer, but another class should contain this info. why not the table model itself. that is return back to the plain and simple implementation of table model, and considering this refactoring attempt failed. – AgostinoX Feb 07 '12 at 13:35