觀察者模式的應用:Winform窗體傳值
觀察者模式的概念:
定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知並更新。
今天我們就學着用一下這個觀察者模式,先想象下這個場景:當一個窗體(主窗體)內的值發生變化時,另外幾個窗體內的值也會發生相應的變化。這個最簡單的實現方式是,在子窗體類內創建一個公共方法,在主窗體內創建子窗體的實例。當值發生變化時調用子窗體的公共方法。還有一種簡單方法是用靜態屬性,相當於全局變量,這個只能針對小型應用。第一種有一個缺點,當增加子窗體時,我們需要更改主窗體類的代碼。無法實現動態地增加刪除對子窗體類中值的更新。這時可以引入觀察者模式。
我們先創建一個winform項目,添加FrmMain,FrmSub1,FrmSub2三個窗體,每個窗體都添加一個textbox文本框,實現當主窗體中的文本框值發生變化時,其他兩個子窗體的值也會發生相應的變化。這里我們參考《Head First設計模式》,先設計發布者和訂閱者(這里我感覺還是叫比較順口)。代碼如下:
public interface ISubject
{
/// <summary>
/// 注冊訂閱者
/// </summary>
/// <param name="observer"></param>
void RegisterObserver(IObserver observer);
/// <summary>
/// 刪除訂閱者
/// </summary>
/// <param name="observer"></param>
void RemoveObserver(IObserver observer);
/// <summary>
/// 通知訂閱者
/// </summary>
void NotifyObservers();
}
public interface IObserver
{
/// <summary>
/// 觀察者對發布者的響應方法
/// </summary>
/// <param name="message"></param>
void Update(string message);
}
修改FrmMain類,使其實現ISubject接口,再給文本框增加change事件,代碼如下:
public partial class FrmMain : Form, ISubject
{
private string Message { get; set; }
private List<IObserver> _observers = new List<IObserver>();
public FrmMain()
{
InitializeComponent();
}
private void FrmMain_Load(object sender, EventArgs e)
{
FrmSub1 frmSub1 = new FrmSub1(this);
FrmSub2 frmSub2 = new FrmSub2(this);
frmSub1.Show();
frmSub2.Show();
}
/// <summary>
/// 文本框改變事件,調用通知訂閱者方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void txtMain_TextChanged(object sender, EventArgs e)
{
this.Message = txtMain.Text;
NotifyObservers();
}
/// <summary>
/// 注冊訂閱者
/// </summary>
/// <param name="observer"></param>
public void RegisterObserver(IObserver observer)
{
_observers.Add(observer);
}
/// <summary>
/// 移除訂閱者
/// </summary>
/// <param name="observer"></param>
public void RemoveObserver(IObserver observer)
{
_observers.Remove(observer);
}
/// <summary>
/// 通知訂閱者
/// </summary>
public void NotifyObservers()
{
foreach (IObserver observer in _observers)
{
observer.Update(Message);
}
}
}
子窗體類(訂閱者)的代碼如下:
public partial class FrmSub1 : Form,IObserver
{
public FrmSub1(ISubject subject)
{
InitializeComponent();
//注冊
subject.RegisterObserver(this);
}
//當發布者事件發生時,訂閱者需要執行的方法
public void Update(string message,bool isShown)
{
txtSub.Text = message;
}
}
上面可以看到FrmMain又依賴了FrmSub1和FrmSub2,其實這是需要另一個方法Show()。不過為表現出高層不依賴底層,我們改掉代碼,在NotifyObservers(bool isShown)方法中加入對子窗體是否show的判斷,從而實現了避免FrmMain依賴FrmSub1,FrmSub2
FrmMain類的代碼:
public partial class FrmMain : Form, ISubject
{
private string Message { get; set; }
private List<IObserver> _observers = new List<IObserver>();
public FrmMain()
{
InitializeComponent();
}
private void FrmMain_Load(object sender, EventArgs e)
{
//FrmSub1 frmSub1 = new FrmSub1(this);
//FrmSub2 frmSub2 = new FrmSub2(this);
//frmSub1.Show();
//frmSub2.Show();
//這里我們使用了方法,擺脫了對子窗體的直接依賴
NotifyObservers(false);
}
/// <summary>
/// 文本框改變事件,調用通知訂閱者方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void txtMain_TextChanged(object sender, EventArgs e)
{
this.Message = txtMain.Text;
NotifyObservers(true);
}
/// <summary>
/// 注冊訂閱者
/// </summary>
/// <param name="observer"></param>
public void RegisterObserver(IObserver observer)
{
_observers.Add(observer);
}
/// <summary>
/// 移除訂閱者
/// </summary>
/// <param name="observer"></param>
public void RemoveObserver(IObserver observer)
{
_observers.Remove(observer);
}
/// <summary>
/// 通知訂閱者
/// </summary>
public void NotifyObservers(bool isShown)
{
foreach (IObserver observer in _observers)
{
observer.Update(Message,isShown);
}
}
}
子窗體類的代碼:
public partial class FrmSub1 : Form,IObserver
{
public FrmSub1(ISubject subject)
{
InitializeComponent();
subject.RegisterObserver(this);
}
//這里對Update做了改變,isShown為true,表示事件觸發時子窗體已經顯示
public void Update(string message,bool isShown)
{
if (isShown)
{
txtSub.Text = message;
}
else
{
this.Show();
}
}
}
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
FrmMain frmMain = new FrmMain();
FrmSub1 frmSub1 = new FrmSub1(frmMain);
FrmSub2 frmSub2 = new FrmSub2(frmMain);
Application.Run(frmMain);
}
完整源代碼參考:https://gitee.com/Alexander360/ProDotnetDesignPatternFramework45