一个实例处理数据库表或视图中的所有行的业务逻辑。
![](https://p-blog.csdn.net/images/p_blog_csdn_net/ntwo1980/Table%20Module.png)
面向对象的重要一点就是将数据和使用它们的行为捆绑在一起。传统的面向对象方法是基于有标识的对象的,使用域模型。因此,如果我有一个雇员类,它的任何实例都对应于一个特定的雇员。这个架构工作地很好,因为一旦我们建立了对一个雇员的引用,我们执行操作、跟踪关系和获取它的数据。
域模型的一个问题是与关系型数据库的接口。
表模块以数据库中的每张表为单位将域逻辑组织到一个类中,类的单一实例包括操作数据的各种过程。它与域模型的主要区别是,如果你有多个订单,域模型对每个订单都有一个对象,而表模块只会有一个对象处理所有的订单。
它如何工作
表模块的强大之处在于它允许将数据和行为同时打包,同时使用关系数据库的强大功能。表面上,表模块看起来与常规对象什么相似。主要的不同是完全不明白它处理的对象的标识。因此,如果你想要获得一个雇员的地址,你需要使用类似anEmployeeModule.getAddress(long employeeID)的方法。每次你想对每个特定的雇员做一些操作的时候,你必须传入某种标识引用。通常这是数据库中的主键。
通常在背后的数据结构是面向表的时使用表模块。列数据通常是一个SQL调用的结果,它存储在一个记录集(Record Set)中,而这个记录集就是SQL表模仿。表模块给你一个清楚的基于方法的接口用来操作数据。将行为以表为单位进行组织给了你很多这种封装的好处:在其中,行为接近它操作的数据。
通常你需要多个表模块中的行为去完成一些有用的工作。你可以经常看到多个表模块对一个记录集进行操作。
![](https://p-blog.csdn.net/images/p_blog_csdn_net/ntwo1980/table%20module%20recordset.png)
表模块最明显的例子是数据库中的每一张表。然而,如果你在数据库中有一些有趣的查询和视图,你也可以使用表模块。
表模块可能是一个实例或可能一个静态方法集合。实例的优势是它允许你使用一个已存在的数据集初始化表模块,数据集可能是查询的结果。然后,你可以使用实例操作数据集中的行。实例也使得继承成为可能,因此我们编写一个相对于常规合同对象包含额外行为的合同模块。
表模块可以以工厂方法的方式包含查询。另一个选择是表数据网关(Table Data Gateway),但这种方式的缺点就是在设计中需要额外的表数据网关类和机制。优点是你可以使用一个表模块处理来自不同数据源的数据,因为你可以针对不同的数据源使用不同的表数据网关。
当你使用表数据网关时,应用程序首先使用表数据网关聚集数据集中数据。你可以使用数据集作为参数创建表模块。如果你需要使用不同表模块中的行为,你可以相同的数据集创建它们。表模块可以对数据集进行业务逻辑操作,然后将修改后的数据集传递给显示层用于显示,并使用表组件进行编辑。在GUI中修改后,数据集返回表模块在保存到数据库前进行验证。这种方式的优点之一就是你可以通过在内存中创建数据集而不用通过数据库测试表模块。
![](https://p-blog.csdn.net/images/p_blog_csdn_net/ntwo1980/table%20module%20interactions.png)
模式名称中的单词“表”暗示数据库中每个表都有一个表模块。这是正确的,但也不是完全正确。表模块也可以用于视图和查询。实际上,表模块的架构并不真正依赖于数据库中的表结构,更多地依赖于应用程序需要的虚表,包括视图和查询。
何时使用
表模块是基于面向表数据的,所以,很明显当你使用数据集访问列数据时使用它是有意义的。另外,当你希望通过非常直接的方式访问数据的时候,你也可以使用表模块。
但是,表模块没有给你对象组织域逻辑的强大能力。你不能创建实例-实例关系,多态也工作得不好。因此,为了处理复杂的域逻辑,域模型是更好的选择。你应该交替使用域模型的处理复杂逻辑的能力和表模块的与底层面向表的数据结构易于集成的特点。
如果域模型中的对象和数据库表非常相似,最好使用域模型,而域模型使用Active Record。当应用程序中的其它部分是基于普通面向表数据结构时,表模块要比域模型和Active Record的结合工作地好。这就是为什么你很少在Java环境中看到表模块,虽然可以变化为成为更广泛应用的行数据集。
我所遇到这个模式的一个著名的情况就是在Microsoft COM设计中。在COM(和.NET)中,数据集(Record Set)是应用程序中主要的数据存储器。数据集可以传递给UI,在那里数据组件显示信息。Microsoft的ADO库给你一个访问关系型数据作为数据集的好的机制。在这种情况下,表模块允许你以一种良好的方式在应用程序中组织域逻辑。
例子:使用表模块的收入确认(C#)
表模块是基于某种数据架构的,通常是关系数据模型。
![](https://p-blog.csdn.net/images/p_blog_csdn_net/ntwo1980/database%20schema.png)
操作这个数据的类与之非常相似;第张表一个表模块类。在.NET架构中,数据集对象提供了一个内存中的数据结构。因此创建操作数据集的类是有意义的。每个表模块类都有一个数据表中的数据成员。这个读取表的能力对于所有的表模块都是共同的,所以可以出现在Layer Supertype中。
class
TableModule...
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
protected
DataTable table;
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
protected
TableModule(DataSet ds, String tableName)
...
{
table = ds.Tables[tableName];
}
子类构造函数使用正确的表名调用超类构造函数。
class
Contract...
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
public
Contract (DataSet ds) :
base
(ds,
"
Contracts
"
)
...
{}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
这允许你仅通过传递一个数据集给表模块构造器就可以创建一个新的表模块。
contract
=
new
Contract(dataset);
C# 索引器的一个有用的特性可以根据一个给定的主键取得数据表中的一行。
class
Contract...
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
public
DataRow
this
[
long
key]
...
{
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
get ...{
String filter = String.Format("ID = {0}", key);
return table.Select(filter)[0];
}
}
功能的第一部分计算合同的确认收入,更新收入确认表。确认额依赖于产品的类型。因为行为主要使用合同表中的数据,我决定向合同类加入一个方法。
class
Contract...
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
public
void
CalculateRecognitions (
long
contractID)
...
{
DataRow contractRow = this[contractID];
Decimal amount = (Decimal)contractRow["amount"];
RevenueRecognition rr = new RevenueRecognition (table.DataSet);
Product prod = new Product(table.DataSet);
long prodID = GetProductId(contractID);
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
if (prod.GetProductType(prodID) == ProductType.WP) ...{
rr.Insert(contractID, amount, (DateTime) GetWhenSigned(contractID));
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
}else if (prod.GetProductType(prodID) == ProductType.SS) ...{
Decimal[] allocation = allocate(amount,3);
rr.Insert(contractID, allocation[0], (DateTime) GetWhenSigned(contractID));
rr.Insert(contractID, allocation[1], (DateTime) GetWhenSigned(contractID).
AddDays(60));
rr.Insert(contractID, allocation[2], (DateTime) GetWhenSigned(contractID).
AddDays(90));
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
}else if (prod.GetProductType(prodID) == ProductType.DB) ...{
Decimal[] allocation = allocate(amount,3);
rr.Insert(contractID, allocation[0], (DateTime) GetWhenSigned(contractID));
rr.Insert(contractID, allocation[1], (DateTime) GetWhenSigned(contractID).
AddDays(30));
rr.Insert(contractID, allocation[2], (DateTime) GetWhenSigned(contractID).
AddDays(60));
}else throw new Exception("invalid product id");
}
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
private
Decimal[] allocate(Decimal amount,
int
by)
...
{
Decimal lowResult = amount / by;
lowResult = Decimal.Round(lowResult,2);
Decimal highResult = lowResult + 0.01m;
Decimal[] results = new Decimal[by];
int remainder = (int) amount % by;
for (int i = 0; i < remainder; i++) results[i] = highResult;
for (int i = remainder; i < by; i++) results[i] = lowResult;
return results;
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
产品需要能告诉我们它的类型。我们可以使用一个枚举用于产品类型和查找方法。
public
enum
ProductType
...
{WP, SS, DB}
;
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
class
Product...
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
public
ProductType GetProductType (
long
id)
...
{
String typeCode = (String) this[id]["type"];
return (ProductType) Enum.Parse(typeof(ProductType), typeCode);
}
插入新收入确认记录的方法:
class
RevenueRecognition...
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
public
long
Insert (
long
contractID, Decimal amount, DateTime date)
...
{
DataRow newRow = table.NewRow();
long id = GetNextID();
newRow["ID"] = id;
newRow["contractID"] = contractID;
newRow["amount"] = amount;
newRow["date"]= String.Format("{0:s}", date);
table.Rows.Add(newRow);
return id;
}
功能的第二部分根据给定的一个日期计算合同的确认收入。
class
RevenueRecognition...
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
public
Decimal RecognizedRevenue (
long
contractID, DateTime asOf)
...
{
String filter = String.Format("ContractID = {0}AND date <= #{1:d}#", contractID,
asOf);
DataRow[] rows = table.Select(filter);
Decimal result = 0m;
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
foreach (DataRow row in rows) ...{
result += (Decimal)row["amount"];
}
return result;
}
实际上,你可以进一步使用聚合函数。
class
RevenueRecognition...
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
public
Decimal RecognizedRevenue2 (
long
contractID, DateTime asOf)
...
{
String filter = String.Format("ContractID = {0}AND date <= #{1:d}#", contractID,
asOf);
String computeExpression = "sum(amount)";
Object sum = table.Compute(computeExpression, filter);
return (sum is System.DBNull) ? 0 : (Decimal) sum;
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)