使用多个自定义表模型避免重复代码

2024-02-16

我正在从事一个项目,其中我们有几个领域类来对业务数据进行建模。这些类是简单的 POJO,我必须使用它们来显示几个表。例如,考虑这个类:

public class Customer {

    private Long id;
    private Date entryDate;
    private String name;
    private String address;
    private String phoneNumber;

    public Customer(Long id, Date entryDate, String name, String address, String phoneNumber) {
        this.id = id;
        this.entryDate = entryDate;
        this.nombre = name;
        this.domicilio = address;
        this.telefono = phoneNumber;
    }

    // Getters and setters here
}

然后我创建了我自己的表格模型,从抽象表模型 http://docs.oracle.com/javase/8/docs/api/javax/swing/table/AbstractTableModel.html为了直接与Customer class:

public class CustomerTableModel extends AbstractTableModel {

    private final List<String> columnNames;
    private final List<Customer> customers;

    public CustomerTableModel() {
        String[] header = new String[] {
            "Entry date",
            "Name",
            "Address",
            "Phone number"
        };
        this.columnNames = Arrays.asList(header);
        this.customers = new ArrayList<>();
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        switch (columnIndex) {
            case 0: return Date.class;
            case 1: return String.class;
            case 2: return String.class;
            case 3: return String.class;
                default: throw new ArrayIndexOutOfBoundsException(columnIndex);
        }
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Customer customer = getCustomer(rowIndex);
        switch (columnIndex) {
            case 0: return customer.getEntryDate();
            case 1: return customer.getName();
            case 2: return customer.getAddress();
            case 3: return customer.getPhoneNumber();
                default: throw new ArrayIndexOutOfBoundsException(columnIndex);
        }
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return true;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        if (columnIndex < 0 || columnIndex >= getColumnCount()) {
            throw new ArrayIndexOutOfBoundsException(columnIndex);
        } else {
            Customer customer = getCustomer(rowIndex);
            switch (columnIndex) {
                case 0: customer.setEntryDate((Date)aValue); break;
                case 1: customer.setName((String)aValue); break;
                case 2: customer.setAddress((String)aValue); break;
                case 3: customer.setPhoneNumber((String)aValue); break;
            }
            fireTableCellUpdated(rowIndex, columnIndex);
        }
    }

    @Override
    public int getRowCount() {
        return this.customers.size();
    }

    @Override
    public int getColumnCount() {
        return this.columnNames.size();
    }

    @Override
    public String getColumnName(int columnIndex) {
        return this.columnNames.get(columnIndex);
    }

    public void setColumnNames(List<String> columnNames) {
        if (columnNames != null) {
            this.columnNames.clear();
            this.columnNames.addAll(columnNames);
            fireTableStructureChanged();
        }
    }

    public List<String> getColumnNames() {
        return Collections.unmodifiableList(this.columnNames);
    }

    public void addCustomer(Customer customer) {
        int rowIndex = this.customers.size();
        this.customers.add(customer);
        fireTableRowsInserted(rowIndex, rowIndex);
    }

    public void addCustomers(List<Customer> customerList) {
        if (!customerList.isEmpty()) {
            int firstRow = this.customers.size();
            this.customers.addAll(customerList);
            int lastRow = this.customers.size() - 1;
            fireTableRowsInserted(firstRow, lastRow);
        }
    }

    public void insertCustomer(Customer customer, int rowIndex) {
        this.customers.add(rowIndex, customer);
        fireTableRowsInserted(rowIndex, rowIndex);
    }

    public void deleteCustomer(int rowIndex) {
        if (this.customers.remove(this.customers.get(rowIndex))) {
            fireTableRowsDeleted(rowIndex, rowIndex);
        }
    }

    public Customer getCustomer(int rowIndex) {
        return this.customers.get(rowIndex);
    }

    public List<Customer> getCustomers() {
        return Collections.unmodifiableList(this.customers);
    }

    public void clearTableModelData() {
        if (!this.customers.isEmpty()) {
            int lastRow = customers.size() - 1;
            this.customers.clear();
            fireTableRowsDeleted(0, lastRow);
        }
    }
}

到现在为止一切都很好。然而这种方法至少存在两个问题:

  1. 由于我必须为每个类实现一个表模型,因此我将生成大量重复代码来基本上执行三件事:定义适当的表头、向底层结构(列表)添加/删除对象、覆盖两者setValueAt() and getValueAt()处理用户定义对象的方法。

  2. 假设我有完全相同的客户列表,但我必须将其呈现在两个不同的表中,并具有不同的标题或数据。我必须对我的表模型进行子类化并重写它需要重写的任何内容才能满足此要求。感觉一点也不优雅。

问题:有没有办法摆脱样板代码,使我的表模型灵活且可重用?


与其他 Swing 模型一样(即:默认组合框模型 http://docs.oracle.com/javase/8/docs/api/javax/swing/DefaultComboBoxModel.html, 默认列表模型 http://docs.oracle.com/javase/8/docs/api/javax/swing/DefaultListModel.html) 我们可以用Generics http://docs.oracle.com/javase/tutorial/java/generics/为了创建灵活且可重用的表模型,还提供了一个 API 来与用户定义的 POJO 一起使用。

该桌子模型将具有以下特殊功能:

  • 它延伸自AbstractTableModel利用表模型事件处理。
  • Unlike CustomerTableModel如上所示,该表模型必须是抽象的,因为它不能覆盖getValueAt()方法:仅仅因为我们不知道该表模型将处理的类或数据类型,所以重写上述方法的任务就留给了子类。
  • 它继承空setValueAt()实施自AbstractTableModel。这是有道理的,因为isCellEditable()也是从该类继承并始终返回false.
  • 默认实现getColumnClass()也是继承的并且总是返回Object.class.

这些功能使这个表模型非常容易根据我们的要求实现:

  • 如果我们需要显示一个只读表,那么我们必须重写 top 的 2 个方法:getValueAt() and getColumnClass()(最后一项是推荐的,但不是强制性的)。
  • 如果我们的表格需要可编辑,那么我们必须重写顶部的 4 个方法:上面提到的两个加上isCellEditable() and setValueAt().

让我们看一下表模型的代码:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.table.AbstractTableModel;

/**
 * Abstract base class which extends from {@code AbstractTableModel} and 
 * provides an API to work with user-defined POJO's as table rows. Subclasses 
 * extending from {@code DataObjectTableModel} must implement 
 * {@code getValueAt(row, column)} method. 
 * <p />
 * By default cells are not editable. If those have to be editable then 
 * subclasses must override both {@code isCellEditable(row, column)} and 
 * {@code setValueAt(row, column)} methods.
 * <p />
 * Finally, it is not mandatory but highly recommended to override 
 * {@code getColumnClass(column)} method, in order to return the appropriate 
 * column class: default implementation always returns {@code Object.class}.
 * 
 * @param <T> The class handled by this TableModel.
 * @author dic19
 */
public abstract class DataObjectTableModel<T> extends AbstractTableModel {

    private final List<String> columnNames;
    private final List<T> data;

    public DataObjectTableModel() {
        this.data = new ArrayList<>();
        this.columnNames = new ArrayList<>();
    }

    public DataObjectTableModel(List<String> columnIdentifiers) {
        this();
        if (columnIdentifiers != null) {
            this.columnNames.addAll(columnIdentifiers);
        }
    }

    @Override
    public int getRowCount() {
        return this.data.size();
    }

    @Override
    public int getColumnCount() {
        return this.columnNames.size();
    }

    @Override
    public String getColumnName(int columnIndex) {
        return this.columnNames.get(columnIndex);
    }

    public void setColumnNames(List<String> columnNames) {
        if (columnNames != null) {
            this.columnNames.clear();
            this.columnNames.addAll(columnNames);
            fireTableStructureChanged();
        }
    }

    public List<String> getColumnNames() {
        return Collections.unmodifiableList(this.columnNames);
    }

    public void addDataObject(T dataObject) {
        int rowIndex = this.data.size();
        this.data.add(dataObject);
        fireTableRowsInserted(rowIndex, rowIndex);
    }

    public void addDataObjects(List<T> dataObjects) {
        if (!dataObjects.isEmpty()) {
            int firstRow = data.size();
            this.data.addAll(dataObjects);
            int lastRow = data.size() - 1;
            fireTableRowsInserted(firstRow, lastRow);
        }
    }

    public void insertDataObject(T dataObject, int rowIndex) {
        this.data.add(rowIndex, dataObject);
        fireTableRowsInserted(rowIndex, rowIndex);
    }

    public void deleteDataObject(int rowIndex) {
        if (this.data.remove(this.data.get(rowIndex))) {
            fireTableRowsDeleted(rowIndex, rowIndex);
        }
    }

    public void notifyDataObjectUpdated(T domainObject) {
        T[] elements = (T[])data.toArray();
        for (int i = 0; i < elements.length; i++) {
            if(elements[i] == domainObject) {
                fireTableRowsUpdated(i, i);
            }
        }
    }

    public T getDataObject(int rowIndex) {
        return this.data.get(rowIndex);
    }

    public List<T> getDataObjects(int firstRow, int lastRow) {
        List<T> subList = this.data.subList(firstRow, lastRow);
        return Collections.unmodifiableList(subList);
    }

    public List<T> getDataObjects() {
        return Collections.unmodifiableList(this.data);
    }

    public void clearTableModelData() {
        if (!this.data.isEmpty()) {
            int lastRow = data.size() - 1;
            this.data.clear();
            fireTableRowsDeleted(0, lastRow);
        }
    }
}

所以,采用这个表模型Customer类,完整的实现如下所示:

String[] header = new String[] {"Entry date", "Name", "Address", "Phone number"};
DataObjectTableModel<Customer> model = new DataObjectTableModel<>(Arrays.asList(header)) {
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        switch (columnIndex) {
            case 0: return Date.class;
            case 1: return String.class;
            case 2: return String.class;
            case 3: return String.class;
                default: throw new ArrayIndexOutOfBoundsException(columnIndex);
        }
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Customer customer = getDataObject(rowIndex);
        switch (columnIndex) {
            case 0: return customer.getEntryDate();
            case 1: return customer.getName();
            case 2: return customer.getAddress();
            case 3: return customer.getPhoneNumber();
                default: throw new ArrayIndexOutOfBoundsException(columnIndex);
        }
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return true;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        if (columnIndex < 0 || columnIndex >= getColumnCount()) {
            throw new ArrayIndexOutOfBoundsException(columnIndex);
        } else {
            Customer customer = getDataObject(rowIndex);
            switch (columnIndex) {
                case 0: customer.setEntryDate((Date)aValue); break;
                case 1: customer.setName((String)aValue); break;
                case 2: customer.setAddress((String)aValue); break;
                case 3: customer.setPhoneNumber((String)aValue); break;
            }
            fireTableCellUpdated(rowIndex, columnIndex);
        }
    }
};

正如我们所看到的,在几行代码(LOC


它可以与 JPA 实体一起使用吗?

只要实体具有公共 getter 和 setter,它就可以。与 JPA 实现不同,此表模型不支持反射,因此我们必须使用类的公共接口来访问对象属性来实现getValueAt() and setValueAt()方法。

它可以与 JDBC 一起使用吗?

不,没有。我们必须将结果集包装到域类中并使用类提供的接口,如上所述。

它可以与 Java 默认类一起使用吗?

是的,它确实。再次使用类提供的接口。例如,让我们采取java.io.File类中,我们可以有下面的表模型实现:

String[] header = new String[] {
    "Name",
    "Full path",
    "Last modified",
    "Read",
    "Write",
    "Execute",
    "Hidden",
    "Directory"
};

DataObjectTableModel<File> model = new DataObjectTableModel<File>(Arrays.asList(header)) {
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        switch (columnIndex) {
            case 0: return String.class;
            case 1: return String.class;
            case 2: return Date.class;
            case 3: return Boolean.class;
            case 4: return Boolean.class;
            case 5: return Boolean.class;
            case 6: return Boolean.class;
            case 7: return Boolean.class;
                default: throw new ArrayIndexOutOfBoundsException(columnIndex);
        }
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        File file = getDataObject(rowIndex);
        switch (columnIndex) {
            case 0: return file.getName();
            case 1: return file.getAbsolutePath();
            case 2: return new Date(file.lastModified());
            case 3: return file.canRead();
            case 4: return file.canWrite();
            case 5: return file.canExecute();
            case 6: return file.isHidden();
            case 7: return file.isDirectory();
                default: throw new ArrayIndexOutOfBoundsException(columnIndex);
        }
    }
};
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用多个自定义表模型避免重复代码 的相关文章

随机推荐

  • 使用 Nginx + Gzip + Unicorn 时缺少 Content-Length 标头

    我不知道为什么当我在 nginx 中使用 gzip 时 http 响应会丢失 Content Length header 我真的陷入困境 请有人帮助我 非常感谢 这是我的配置文件 nginx conf 用户无人无人 工作进程 8 event
  • 使用 Dynamorm 在 Python 中构建 AWS SAM 嵌套应用程序

    我在用AWS SAM https aws amazon com serverless sam 构建无服务器应用程序 我按照说明构建嵌套应用程序 https aws amazon com blogs compute announcing ne
  • CakePHP Cache::clear 不起作用

    我的 bootstrap php 文件中有一个缓存配置 Cache config long array engine gt File duration gt 1 week probability gt 100 mask gt 0666 pa
  • 调用android中隐藏的API来关闭屏幕

    我正在考虑使用隐藏的 api 来关闭我的应用程序中的屏幕 setScreenState from https android googlesource com platform frameworks base eclair release
  • 是否有适用于 iPhone、Android 和 Windows Phone 7 的增强现实框架?

    我想开发一个在 iPhone Android Windows Phone 7 智能手机上运行的应用程序 能够识别标记并在其上放置增强现实内容 我可以使用任何通用框架来完成该任务吗 这可能是您得到的最接近的结果 http nyatla jp
  • ASP.NET WebApi 中异步调用的本地化

    嗨 我计划基于 Accept Language 标头 通过基于该标头设置 CurrentUICulture 来处理错误字符串等的服务器端本地化 但显然它不会通过异步调用 下面是示例代码来说明问题 是否有处理异步调用本地化的默认方法 publ
  • 如何查找指向 Oracle 中一条记录的外键依赖关系?

    我有一个非常大的 Oracle 数据库 有很多表和数百万行 我需要删除其中之一 但希望确保删除它不会破坏任何其他指向它作为外键记录的依赖行 有没有办法获取指向该行的所有其他记录的列表 或者至少是表模式 我知道我可以尝试自己删除它并捕获异常
  • 每行上的模态按钮显示从数据库获取详细信息的模态

    我已经使用表从两个表中获取所有记录 效果很好 我在显示的每一行末尾添加了一个按钮Modal 在此模式中 我想根据每个行 ID 显示数据 我的代码有一个 while 循环 它显示表中的记录 在该 while 循环中 我又编写了一个 sql 语
  • 如何在使用 XMLHttpRequest() 时在 python 中接收 POST 数据

    我有两个关于使用 XMLHttpRequest 时接收数据的问题 客户端是用javascript编写的 服务器端是用python写的 如何在 python 端接收 处理数据 如何响应 HTTP 请求 客户端 var http new XML
  • powershell - 使用文件名列表删除文件

    我从堆栈溢出中得到了以下代码 它工作正常 TargetFolder Pathofyourfolder Files Get ChildItem TargetFolder Exclude gc List txt Recurse foreach
  • 使用 AJAX 观察 SQL 数据库的更改 [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我的数据库中有一个列将随机更新 每当该列更新时 我都需要刷新页面上的内容 如何使用 AJAX jQuery 仅对数据库更改执行操作 你
  • 错误您已经拥有该商品

    我有一个新的 Android 应用程序 其中添加了应用程序内计费功能 我感到沮丧得抓狂 我已上传签名的 APK 并发布到 alpha 我创建了一组应用内产品并将其全部激活 我创建了一个新的 Gmail 帐户 并将其定义为应用程序 apk 页
  • Java 中的架构约束 [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我想确保我的项目不包含包之间不必要的依赖关系 例如 我想确保该项目具有分层结构 IE 模型低于一切 业务
  • 从 HDC 句柄创建 QPaintDevice

    我有一个来自外部库的 Windows HDC 句柄 我想使用 QPainter 功能来绘图 Qt 中有什么方法可以从 HDC 句柄创建 QPaintDevice 吗 这样做的一种方法 使用 Windows API 从 HDC 获取 HWND
  • 使用 jQuery 操作 TinyMCE 内容

    使用 TinyMCE 我可以轻松操作内容并将其发送回编辑器 如下所示 get content from tinyMCE var content tinyMCE get content getContent manipulate conten
  • 使用 NSMutableString 追加到文件末尾

    我有一个日志文件 我试图将数据附加到其末尾 我有一个NSMutableString textToWrite变量 我正在执行以下操作 textToWrite writeToFile filepath atomically YES encodi
  • 从 ASP.NET MVC2 升级到 MVC3 的原因 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 使用此关键字并揭示模块模式的严格违规

    无法让以下内容通过 jslint jshint jshint strict true var myModule function use strict var privVar true pubVar false function privF
  • MFC/WinAPI 的大问题

    我需要创建一个带有两个选项卡的表单视图的 SDI 表单 其中封装了多个对话框作为选项卡内容 但表格必须有彩色背景 诸如此类的事情让我讨厌编程 首先 我通过资源编辑器尝试了 CTabControl 尝试了不同的事情 但未记录的行为和没有答案的
  • 使用多个自定义表模型避免重复代码

    我正在从事一个项目 其中我们有几个领域类来对业务数据进行建模 这些类是简单的 POJO 我必须使用它们来显示几个表 例如 考虑这个类 public class Customer private Long id private Date en