主窗體開啟子窗體,子窗體需向主窗體傳遞數據(如修改標題、提交表單數據)。本文梳理四種模式,由淺入深展示耦合度降低過程。
// 主窗體
var child = new ChildForm(this);
child.Show();
// 子窗體
public class ChildForm : Form {
private MainForm _main;
public ChildForm(MainForm main) { _main = main; }
private void btnOK_Click(object sender, EventArgs e) {
_main.txtTitle.Text = this.txtInput.Text; // 直接操作主窗體控件
}
}
缺點: 子窗體依賴具體主窗體類型,暴露所有 public 成員,違反迪米特法則,難以複用與測試。
// 定義契約
public interface ITitleChanger {
void ChangeTitle(string title);
}
// 主窗體實現介面
public class MainForm : Form, ITitleChanger {
public void ChangeTitle(string title) { this.Text = title; }
private void btnOpen_Click(object sender, EventArgs e) {
var child = new ChildForm(this); // this 作為 ITitleChanger 傳遞
child.Show();
}
}
// 子窗體依賴介面
public class ChildForm : Form {
private ITitleChanger _changer;
public ChildForm(ITitleChanger changer) { _changer = changer; }
private void btnOK_Click(object sender, EventArgs e) {
_changer.ChangeTitle(this.txtInput.Text); // 僅調用契約方法
}
}
優點: 子窗體僅知曉「能改標題」,不知曉具體實現,降低耦合,便於單元測試(Mock 介面)。
// 子窗體定義委托
public partial class ChildForm : Form {
public delegate void TitleChangedHandler(string title);
public event TitleChangedHandler TitleChanged;
private void btnOK_Click(object sender, EventArgs e) {
TitleChanged?.Invoke("New Title"); // 觸發委托
}
}
// 主窗體訂閱委托
public partial class MainForm : Form {
private ChildForm _child = new ChildForm();
public MainForm() {
InitializeComponent();
_child.TitleChanged += OnChildTitleChanged; // 註冊處理器
}
private void OnChildTitleChanged(string title) {
this.Text = title;
}
private void btnOpen_Click(object sender, EventArgs e) {
_child.Show();
}
}
特點: 無需定義介面,語法簡潔,適合單一回調場景。委托本質是類型安全的函數指針。
// 子窗體:完整事件模式
public partial class ChildForm : Form {
// 1. 事件參數類,攜帶業務數據
public class UserAddedEventArgs : EventArgs {
public User User { get; set; }
public UserAddedEventArgs(User user) { User = user; }
}
// 2. 事件聲明
public event EventHandler<UserAddedEventArgs> UserAdded;
// 3. 觸發方法
protected virtual void OnUserAdded(UserAddedEventArgs e) {
UserAdded?.Invoke(this, e);
}
// 4. 業務邏輯中觸發
private void btnAdd_Click(object sender, EventArgs e) {
var user = new User { Name = txtName.Text, Password = txtPwd.Text };
OnUserAdded(new UserAddedEventArgs(user));
}
}
// 主窗體:訂閱並處理
public partial class MainForm : Form {
private void btnOpenUserDialog_Click(object sender, EventArgs e) {
using (var dialog = new ChildForm()) {
dialog.UserAdded += OnUserAdded; // 訂閱
dialog.ShowDialog();
}
}
private void OnUserAdded(object sender, ChildForm.UserAddedEventArgs e) {
MessageBox.Show($"新用戶:{e.User.Name}");
// 刷新列表、保存數據庫等
}
}
| 模式 | 耦合度 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|---|
| 構造函數傳引用 | 極高 | 極簡單、一次性對話框 | 代碼最少 | 強依賴具體類、難測試、不可複用 |
| 介面隔離 | 中等 | 多種實現、需單元測試 | 契約清晰、可 Mock | 需定義介面、稍顯繁瑣 |
| 委托回調 | 低 | 單一回調、簡單數據傳遞 | 語法簡潔、無額外類型 | 多播委托易誤用、不攜帶豐富上下文 |
| 自定義事件 | 最低 | 標準元件、複雜數據、多訂閱者 | 符合 .NET 規範、支援 EventArgs、可多播 | 代碼量最大 |
「子窗體只負責『引發事件』,不關心『如何處理』——這是鬆耦合的本質。」
// 子窗體暴露公共屬性/方法供主窗體主動拉取
public User CurrentUser { get; private set; }
// 主窗體定時器或事件中拉取
private void timer_Tick(object sender, EventArgs e) {
if (child.CurrentUser != null) {
UpdateUI(child.CurrentUser);
}
}
或結合 INotifyPropertyChanged 實現雙向綁定。