###### tags: `企業及軟體架構模式`
# 9.3 Table Module (表格模組)
> 使用一個「單一執行個體」,它處理某個資料庫資料表或檢視表中所有資料列的「商業邏輯」,例如 CRUD、加總等行為
## 前言
+ 物件導向的關鍵特性:將【資料】與【行為】結合在一起使用。假如我們有一個 Employee 類別,它的每一個執行個體都會對應到一個特定的員工,我們就可以對這員工進行讀取、更新資料,或是其他行為
```csharp=
public class Employee
{
public string Name { get; set; }
public int JobGrade { get; set; }
public int GetSalary()
{
if (JobGrade > 8) {
return 32000;
}
return 22000;
}
}
var employee = new Employee();
employee.Name = 'kyo';
employee.JobGrade = 7;
Console.WriteLine(employee.GetSalary());
```
+ Domain Model 的問題點在於:通常你需要受過大量程式訓練才能==在 domain 物件與 table 物件這兩種表示形式轉換自如。將 table 資料取出轉換為 domain 物件,或是將 domain 物件轉為 table 物件存入資料庫==
```csharp=
public class User //table 物件
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime CreatedOn { get; set; }
}
public class UserInfo //domain 物件
{
public int Id { get; set; }
public string FullName { get; set; }
}
```
+ Table Module 使用資料庫的每個 table 或 view 來對應一個類別,來組織【領域邏輯】
+ Domain Model 與 Table Module 最大的不同在於,如果你有多筆訂單
+ Domain Model: 每張訂單都是一個物件(編按:不一定XD)
+ Table Module: 擁有一個物件處理所有訂單
## 9.3.1 運作原理
+ 【資料】與【行為】打包在一起
+ 與物件最大的差別在於:無法直接取得物件屬性。
+ 例如:取得 Employee 的 Address,必須仰賴 employeeModule.getAddress(long employeeID),而不是 employee.address
+ Table Module 的資料通常是 SQL 呼叫的結果
+ Table Module 雖然有 Table 一詞,但實際上不一定依賴於資料庫的 Table,更多是取決於 application 需要的虛擬表格,包括 table, view、特定 SQL 查詢結果,甚至是程式處理後的資料集合
+ 通常需要來自多個 Table Module 才能一些實際有用的工作
+ 另一種方法是 Table Data Gateway,將來自不同來源的資料包裝成單一 Table Module,所以也有可能不需要連線資料庫
+ 例如:我向 Table Data Gateway 要求該 Employee 出席紀錄,出席紀錄表面上看起來是單一 Table Module,但背後可能來自多個 Table Module 或其他資料來源。
```csharp=
// From chatgpt
public class OrderModule
{
private string _connectionString;
public OrderModule(string connectionString)
{
_connectionString = connectionString;
}
public void CreateOrder(int customerId, int productId, int quantity, DateTime orderDate)
{
// 在這裡插入創建訂單的 SQL 代碼
}
public DataTable GetOrder(int orderId)
{
// 在這裡插入讀取訂單的 SQL 代碼
return new DataTable();
}
public void UpdateOrder(int orderId, int customerId, int productId, int quantity, DateTime orderDate)
{
// 在這裡插入更新訂單的 SQL 代碼
}
public void DeleteOrder(int orderId)
{
// 在這裡插入刪除訂單的 SQL 代碼
}
public decimal GetTotalRevenue(DateTime startDate, DateTime endDate)
{
// 在這裡插入計算總收入的 SQL 代碼和業務邏輯
return 0;
}
}
```
## 9.3.2 使用時機
+ Table Module 是基於表格導向的資料,當你使用 Record Set (or DataRows) 存取表格形式資料時,顯然可以使用 Table Module。
+ Table Module 無法組織複雜的邏輯,也無法建立各類別的關聯,這時 Domain Model 會是更好的選擇
## 9.3.3 範例
+ 目前有 Products, Contracts, RevenueRecognitions 三個 Table Module,我想知道某合約訂單的收入。
```csharp=
//pseudo code
public class ContractModule
{
//...initial ds...
var table = ds.Tables['Contract'];
public DataRow GetContract(int contractId)
{
return DataRow contractRow = table.first(x => x.Id == contractId);
}
public string GetProductId(int contractId)
{
//取得 ProductId
}
public void CalculateRecognitions(int contractId) {
DataRow contractRow = GetContract(contractId);
Decimal amount = (Decimal)contractRow["amount"];
RevenueRecognitions rr = new RevenueRecognitions(table.DataSet);
Product prod = new Product(table.DataSet);
int prodId = GetProductId(contractId);
if (prod.GetProductType(prodId) == "DB") {
rr.Insert(contractId, amount, DateTime.Now());
} else {
...
}
}
}
public class RevenueRecognitions
{
// DataTable 換成 List 應該也適用
public int Insert(int contractId, Decimal amount, Decimal date) {
DataRow newRow = table.NewRow();
lond id = GetNextID();
newRow["ID"] = id;
newRow["contractId"] = contractId;
newRow["amount"] = amount;
newRow["date"] = date;
table.Rows.Add(newRow); //注意: 存在記憶體,而非資料庫
return id;
}
public Decimal RecognizedRevenue(int contractId)
{
DataRow[] rows = table.where(x => x.contractId == contractId);
Decimal result = 0m;
foreach (DataRow row in rows)
{
result += (Decimal)row["amount"];
}
return result;
}
}
```