三層式架構將應用程式分為三個邏輯層次,各司其職、低耦合、高內聚:
| 層次 | 職責 | 本範例類別 | 關鍵技術 |
|---|---|---|---|
| 表現層 (UI) | 使用者介面、輸入驗證、結果顯示 | FrmGUI (WinForm) |
事件處理、DataBinding、例外捕捉 |
| 商業邏輯層 (BLL) | 業務規則、資料驗證、流程協調 | BOCustomer |
屬性驗證、委派 DAL、回傳 DataSet |
| 資料存取層 (DAL) | SQL 執行、參數化、連線管理 | DACustomer |
OleDb (Access)、SQL 組裝、DataAdapter |
using System;
using System.Data;
using System.Windows.Forms;
namespace ThreeTierDemo
{
public partial class FrmGUI : Form
{
private BOCustomer _currentCustomer;
public FrmGUI() => InitializeComponent();
// 新增客戶
private void btnAdd_Click(object sender, EventArgs e)
{
try
{
var customer = BuildCustomerFromUI();
customer.Add(); // 呼叫 BLL
MessageBox.Show("新增成功!");
ClearForm();
}
catch (Exception ex)
{
MessageBox.Show($"錯誤:{ex.Message}");
}
}
// 查詢客戶
private void btnFind_Click(object sender, EventArgs e)
{
try
{
string id = txtID.Text.Trim();
if (string.IsNullOrEmpty(id)) throw new Exception("請輸入客戶 ID");
var bo = new BOCustomer();
DataSet ds = bo.Find(id);
if (ds.Tables[0].Rows.Count == 0)
{
MessageBox.Show("查無資料");
return;
}
BindUI(ds.Tables[0].Rows[0]);
}
catch (Exception ex)
{
MessageBox.Show($"錯誤:{ex.Message}");
}
}
// 更新客戶
private void btnUpdate_Click(object sender, EventArgs e)
{
try
{
var customer = BuildCustomerFromUI();
customer.Update();
MessageBox.Show("更新成功!");
}
catch (Exception ex)
{
MessageBox.Show($"錯誤:{ex.Message}");
}
}
private BOCustomer BuildCustomerFromUI()
{
return new BOCustomer
{
CusID = txtID.Text.Trim(),
FName = txtFName.Text.Trim(),
LName = txtLName.Text.Trim(),
Tel = txtTel.Text.Trim(),
Address = txtAddress.Text.Trim()
};
}
private void BindUI(DataRow row)
{
txtFName.Text = row["CUS_F_NAME"].ToString();
txtLName.Text = row["CUS_L_NAME"].ToString();
txtTel.Text = row["CUS_TEL"].ToString();
txtAddress.Text = row["CUS_ADDRESS"].ToString();
}
private void ClearForm()
{
foreach (Control c in Controls)
if (c is TextBox tb) tb.Clear();
}
}
}
using System;
using System.Data;
namespace ThreeTierDemo
{
public class BOCustomer
{
private readonly DACustomer _dal = new DACustomer();
// 屬性與驗證
private string _fName;
public string FName
{
get => _fName;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("名字不可為空");
_fName = value.Trim();
}
}
private string _lName;
public string LName
{
get => _lName;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("姓氏不可為空");
_lName = value.Trim();
}
}
public string CusID { get; set; } // ID 可由資料庫自動產生,這裡簡化
private string _address;
public string Address
{
get => _address;
set => _address = string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("地址不可為空") : value.Trim();
}
private string _tel;
public string Tel
{
get => _tel;
set => _tel = string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("電話不可為空") : value.Trim();
}
// 業務方法:委派給 DAL
public void Add() => _dal.Add(this);
public void Update() => _dal.Update(this);
public DataSet Find(string id) => string.IsNullOrWhiteSpace(id)
? throw new ArgumentException("查詢 ID 不可為空")
: _dal.Find(id);
}
}
using System;
using System.Data;
using System.Data.OleDb;
namespace ThreeTierDemo
{
public class DACustomer
{
// 連線字串建議從設定檔讀取
private const string ConnStr =
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\\customer.mdb;";
private const string Table = "tblCustomer";
private const string ColID = "CUS_ID";
private const string ColLName = "CUS_L_NAME";
private const string ColFName = "CUS_F_NAME";
private const string ColTel = "CUS_TEL";
private const string ColAddr = "CUS_ADDRESS";
public void Add(BOCustomer bo)
{
string sql = $@"INSERT INTO {Table} ({ColID}, {ColLName}, {ColFName}, {ColAddr}, {ColTel})
VALUES (?, ?, ?, ?, ?)";
using (var conn = new OleDbConnection(ConnStr))
using (var cmd = new OleDbCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@id", bo.CusID);
cmd.Parameters.AddWithValue("@lname", bo.LName);
cmd.Parameters.AddWithValue("@fname", bo.FName);
cmd.Parameters.AddWithValue("@addr", bo.Address);
cmd.Parameters.AddWithValue("@tel", bo.Tel);
conn.Open();
cmd.ExecuteNonQuery();
}
}
public void Update(BOCustomer bo)
{
string sql = $@"UPDATE {Table} SET
{ColLName}=?, {ColFName}=?, {ColAddr}=?, {ColTel}=?
WHERE {ColID}=?";
using (var conn = new OleDbConnection(ConnStr))
using (var cmd = new OleDbCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@lname", bo.LName);
cmd.Parameters.AddWithValue("@fname", bo.FName);
cmd.Parameters.AddWithValue("@addr", bo.Address);
cmd.Parameters.AddWithValue("@tel", bo.Tel);
cmd.Parameters.AddWithValue("@id", bo.CusID);
conn.Open();
cmd.ExecuteNonQuery();
}
}
public DataSet Find(string id)
{
string sql = $"SELECT * FROM {Table} WHERE {ColID} = ?";
var ds = new DataSet();
using (var conn = new OleDbConnection(ConnStr))
using (var adapter = new OleDbDataAdapter(sql, conn))
{
adapter.SelectCommand.Parameters.AddWithValue("@id", id);
adapter.Fill(ds, Table);
}
return ds;
}
}
}
// WinForm
var bo = new BOCustomer(); bo.Add();
// ASP.NET WebForm / MVC Controller
public ActionResult Create(CustomerViewModel vm)
{
var bo = new BOCustomer { FName = vm.FName, ... };
bo.Add();
return RedirectToAction("Index");
}
// WPF / Blazor / Web API 同樣可用
DACustomer 連線字串與 OleDb → SqlClientCachedCustomerRepository[Test]
public void Add_ShouldCallDalAdd_WhenValidCustomer()
{
// Arrange
var mockDal = new Mock();
var bo = new BOCustomer(mockDal.Object) // 需修改 BLL 採用介面注入
{
CusID = "1", FName = "John", LName = "Doe", Tel = "123", Address = "NY"
};
// Act
bo.Add();
// Assert
mockDal.Verify(d => d.Add(It.Is(c => c.CusID == "1")), Times.Once);
}
ICustomerRepository,BLL 依賴介面而非具體 DALTask<DataSet> FindAsync() 避免阻塞 UI 執行緒三層式架構雖非銀彈,但對於中小型專案、團隊協作、長期維護系統,提供了清晰的關注點分離與演進路徑。關鍵在於:嚴格遵守層次呼叫規則 (UI→BLL→DAL,不可跳層或反向依賴),並隨專案成長適時引入介面抽象與依賴注入,平滑演進至整潔架構或領域驅動設計。