是否有任何面向对象的方法或模式来处理表格数据?
Is there any object-oriented approach or pattern to work with tabular data?
表格数据所需的操作:
- 用行切换列
- 在特定位置添加columns/rows
- 将 columns/rows/cells 标记为属于同一组并获取特定组
- 更改column/rows顺序
- 计算总数为rows/columns
- 转换为枢轴table
- 输出结果为HTML/JSON
处理表格数据的面向对象的好方法是什么?
我认为这里首先需要的是一个好的/适当的数据结构来保存/操作 table 数据。
这取决于您的具体要求,例如 table 的大小、性能要求等
假设您将使用一些 Matrix
class,它提供对 table 的低级操作(设置单元格值、add/remove 行、转置等)。
此 class 将使用基本数据结构进行操作,例如,它可能具有 get_row
方法,该方法将 return 一个 list
数字。
例如,现在我们可以获取此列表中值的摘要,但我们不能仅更改某些列表项并将此更改反映在父 Matrix
中(行数据与父矩阵结构断开连接).
现在我们可以在此 Matrix
class 上构建我们的结构,我们的两个目标是:
1)为了方便用户,部分"conveniences"可以是:
- 行、列和单元格对象都连接到父
Table
对象(如果我们修改行项,它将反映在父table和其他行/列/单元格对象中).
- 我们可以生成相同数据的不同视图(例如 pivot tables)
- 我们可以使用特殊的 column/row 寻址系统(例如使用 1、2、3... 表示行,使用 A、B、C... 表示列)
2) 隐藏我们用来存储 table 数据的实际数据结构
- 这样我们就可以在不破坏用户代码的情况下独立地
Matrix
实现
- 我们甚至可以用不同的结构替换它(也许我们会找到更快的东西或占用更少内存的东西),或者我们可以针对不同的情况(例如桌面和移动应用程序)有不同的实现
这是我开始使用的近似 classes 结构(python-like 伪代码):
class Matrix:
"""The base data structure, implementation detail."""
get_cell(int x, int y):
"""Returns cell value by x/y column and row indexes."""
set_cell(int x, int y, value):
"""Sets cell value by x/y column and row indexes."""
get_row(int index) -> list:
"""Returns `list` of values in the `index` row."""
get_column(int index) -> list;
"""Returns `list` of values in the `index` column."""
Matrix
class 是低级数据结构,它不应该是 public 接口的一部分。
public 接口由 Table
class 和下面的其他相关 class 表示:
class Table:
"""The user-level interface to work with table data."""
constructor():
"""Initializes Matrix object."""
# The "_data" object is private, only to be used internally.
self._data = Matrix()
row(int number) -> Row:
"""Returns `Row` object by row number (1, 2, 3, ...)."""
row = Row(self, number)
self.attach(row)
return row
column(string name) -> Column:
"""Returns `Column` object by string name (A, B, C, ...)."""
column = Column(self, name)
self.attach(column)
return column
cell(int row_number, string col_name) -> Cell:
"""Returns `Cell` object by string name (A, B, C, ...)."""
cell = Cell(self, row_number, col_name)
self.attach(cell)
return column
attach(Observer observer):
"""Register an observer to be notified when Table state was changed."""
self.observers.append(observer)
_notify():
"""Notify all dependent objects about the state change."""
for observer in self.observers:
observer.update()
...
要保持 Table
和 Row /
Column /
Cell
对象同步,我们可以使用 Observer 模式。
这里的 Table
是 Subject
而 Row
/ Column
/ Cell
是 Observers
。
一旦 Table
(和底层数据)的状态发生变化,我们就可以更新所有依赖对象。
class Row(Observable):
"""Table row object."""
constructor(Table parent, int index):
self.parent = parent
self.index = index
self._data = None
self.update()
update()
"""Update row data.
Fetches the `list` or row values from the `Matrix` object.
"""
# Note: we have two choices here - the `Row`, `Column` and `Cell` objects
# can either access `Table._data` property directly, or `Table` can provide
# proxy methods to modify the data (like `_get_value(x, y)`); in both cases
# there is a private interface to work with data used by `Table`, `Row`,
# `Column` and `Cell` classes and the implementation depends on the language,
# in C++ these classes can be friends, in python this can be just a documented
# agreement on how these classes should work.
# See also the comment in the `remove` method below.
self._data = parent._data.get_row(index)
sum():
"""Returns sum of row items."""
sum = 0
for value in self._data:
sum += value
return sum
cell(string col_name):
"""Returns cell object."""
return parent.cell(self.index, col_name)
remove():
"""Removes current row."""
# Here we access `parent._data` directly, so we also have to
# call `parent._notify` here to update other objects.
# An alternative would be a set of proxy methods in the `Table` class
# which would modify the data and then call the `_notify` method, in such case
# we would have something like `self.parent._remove_row(self.index)` here.
self.parent._data.remove_row(self.index)
self.parent._notify()
self.parent.detach(self)
Column
和 Cell
class 类似,Column
将保存列数据,而 Cell
将包装单元格值。
用户级用法可能如下所示:
table = Table()
# Update table data
table.cell(1, "A").set(10)
table.cell(1, "B").set(20)
table.row(1).cell("C").set(30)
# Get row sum
sum = table.row(1).sum()
# Get the table row
row = table.row(1)
# The `remove` operation removes the row from the table and `detaches` it,
# so it will no longer observe the `table` changes.
row.remove()
# Now we have the detached row and we can put it into another table,
# so basically we cut-and-pasted the row from one table to another
another_table.add_row(row)
使用这种方法,您可以非常轻松地实现复制、剪切、粘贴等操作。您也可以在此处应用 Command pattern 并将这些操作提取到较小的 classes 中。这样也很容易实现撤销和重做。
PivotTable
table也可以是一种特殊的Observable
。
根据枢轴 table 的功能要求,您可能会发现 Builder pattern 对配置枢轴 table 很有用。
像这样:
pivotBuilder = PivotBuilder(table)
# Group by column "A" and use `SumAggregator` to aggregate grouped values.
pivotBuilder.group_by_column("A", SumArggregator()) # or maybe pivotBuilder.groupBy(table.column("A"))
pivotTable := pivotBuilder.get_result()
将 table 导出为不同格式的 classes 可能不必是可观察的,因此它们只是包装 Table
对象并将其转换为适当的格式:
json_table = JsonTable(table)
data = json_table.export()
当然,以上只是众多可能的实现方案中的一种,根据您的具体需求,将它们视为一些有用(或无用)的想法。
您可能会在 GoF patterns book 中找到更多想法。
表格数据所需的操作:
- 用行切换列
- 在特定位置添加columns/rows
- 将 columns/rows/cells 标记为属于同一组并获取特定组
- 更改column/rows顺序
- 计算总数为rows/columns
- 转换为枢轴table
- 输出结果为HTML/JSON
处理表格数据的面向对象的好方法是什么?
我认为这里首先需要的是一个好的/适当的数据结构来保存/操作 table 数据。 这取决于您的具体要求,例如 table 的大小、性能要求等
假设您将使用一些 Matrix
class,它提供对 table 的低级操作(设置单元格值、add/remove 行、转置等)。
此 class 将使用基本数据结构进行操作,例如,它可能具有 get_row
方法,该方法将 return 一个 list
数字。
例如,现在我们可以获取此列表中值的摘要,但我们不能仅更改某些列表项并将此更改反映在父 Matrix
中(行数据与父矩阵结构断开连接).
现在我们可以在此 Matrix
class 上构建我们的结构,我们的两个目标是:
1)为了方便用户,部分"conveniences"可以是:
- 行、列和单元格对象都连接到父
Table
对象(如果我们修改行项,它将反映在父table和其他行/列/单元格对象中). - 我们可以生成相同数据的不同视图(例如 pivot tables)
- 我们可以使用特殊的 column/row 寻址系统(例如使用 1、2、3... 表示行,使用 A、B、C... 表示列)
2) 隐藏我们用来存储 table 数据的实际数据结构
- 这样我们就可以在不破坏用户代码的情况下独立地
Matrix
实现 - 我们甚至可以用不同的结构替换它(也许我们会找到更快的东西或占用更少内存的东西),或者我们可以针对不同的情况(例如桌面和移动应用程序)有不同的实现
这是我开始使用的近似 classes 结构(python-like 伪代码):
class Matrix:
"""The base data structure, implementation detail."""
get_cell(int x, int y):
"""Returns cell value by x/y column and row indexes."""
set_cell(int x, int y, value):
"""Sets cell value by x/y column and row indexes."""
get_row(int index) -> list:
"""Returns `list` of values in the `index` row."""
get_column(int index) -> list;
"""Returns `list` of values in the `index` column."""
Matrix
class 是低级数据结构,它不应该是 public 接口的一部分。
public 接口由 Table
class 和下面的其他相关 class 表示:
class Table:
"""The user-level interface to work with table data."""
constructor():
"""Initializes Matrix object."""
# The "_data" object is private, only to be used internally.
self._data = Matrix()
row(int number) -> Row:
"""Returns `Row` object by row number (1, 2, 3, ...)."""
row = Row(self, number)
self.attach(row)
return row
column(string name) -> Column:
"""Returns `Column` object by string name (A, B, C, ...)."""
column = Column(self, name)
self.attach(column)
return column
cell(int row_number, string col_name) -> Cell:
"""Returns `Cell` object by string name (A, B, C, ...)."""
cell = Cell(self, row_number, col_name)
self.attach(cell)
return column
attach(Observer observer):
"""Register an observer to be notified when Table state was changed."""
self.observers.append(observer)
_notify():
"""Notify all dependent objects about the state change."""
for observer in self.observers:
observer.update()
...
要保持 Table
和 Row /
Column /
Cell
对象同步,我们可以使用 Observer 模式。
这里的 Table
是 Subject
而 Row
/ Column
/ Cell
是 Observers
。
一旦 Table
(和底层数据)的状态发生变化,我们就可以更新所有依赖对象。
class Row(Observable):
"""Table row object."""
constructor(Table parent, int index):
self.parent = parent
self.index = index
self._data = None
self.update()
update()
"""Update row data.
Fetches the `list` or row values from the `Matrix` object.
"""
# Note: we have two choices here - the `Row`, `Column` and `Cell` objects
# can either access `Table._data` property directly, or `Table` can provide
# proxy methods to modify the data (like `_get_value(x, y)`); in both cases
# there is a private interface to work with data used by `Table`, `Row`,
# `Column` and `Cell` classes and the implementation depends on the language,
# in C++ these classes can be friends, in python this can be just a documented
# agreement on how these classes should work.
# See also the comment in the `remove` method below.
self._data = parent._data.get_row(index)
sum():
"""Returns sum of row items."""
sum = 0
for value in self._data:
sum += value
return sum
cell(string col_name):
"""Returns cell object."""
return parent.cell(self.index, col_name)
remove():
"""Removes current row."""
# Here we access `parent._data` directly, so we also have to
# call `parent._notify` here to update other objects.
# An alternative would be a set of proxy methods in the `Table` class
# which would modify the data and then call the `_notify` method, in such case
# we would have something like `self.parent._remove_row(self.index)` here.
self.parent._data.remove_row(self.index)
self.parent._notify()
self.parent.detach(self)
Column
和 Cell
class 类似,Column
将保存列数据,而 Cell
将包装单元格值。
用户级用法可能如下所示:
table = Table()
# Update table data
table.cell(1, "A").set(10)
table.cell(1, "B").set(20)
table.row(1).cell("C").set(30)
# Get row sum
sum = table.row(1).sum()
# Get the table row
row = table.row(1)
# The `remove` operation removes the row from the table and `detaches` it,
# so it will no longer observe the `table` changes.
row.remove()
# Now we have the detached row and we can put it into another table,
# so basically we cut-and-pasted the row from one table to another
another_table.add_row(row)
使用这种方法,您可以非常轻松地实现复制、剪切、粘贴等操作。您也可以在此处应用 Command pattern 并将这些操作提取到较小的 classes 中。这样也很容易实现撤销和重做。
PivotTable
table也可以是一种特殊的Observable
。
根据枢轴 table 的功能要求,您可能会发现 Builder pattern 对配置枢轴 table 很有用。
像这样:
pivotBuilder = PivotBuilder(table)
# Group by column "A" and use `SumAggregator` to aggregate grouped values.
pivotBuilder.group_by_column("A", SumArggregator()) # or maybe pivotBuilder.groupBy(table.column("A"))
pivotTable := pivotBuilder.get_result()
将 table 导出为不同格式的 classes 可能不必是可观察的,因此它们只是包装 Table
对象并将其转换为适当的格式:
json_table = JsonTable(table)
data = json_table.export()
当然,以上只是众多可能的实现方案中的一种,根据您的具体需求,将它们视为一些有用(或无用)的想法。
您可能会在 GoF patterns book 中找到更多想法。