首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 設計模式無廢話系列(十九):Observer 觀察者模式

     編輯:關於C#

意圖

定義對象間一對多依賴,當一個對象狀態改變,所有依賴者自動收到通知並更新。

適用場景

Web 分頁顯示:初期僅需「上/下頁」按鈕 + GridView。後續需求疊加:顯示頁碼 Label、頁碼列表控制器。每新增一個觀察者,按鈕點擊事件就得多一行通知代碼,耦合失控。引入 Observer 解耦。

完整範例程式碼

using System;
using System.Collections.Generic;
using System.Text;

namespace ObserverExample
{
    class Program
    {
        static void Main(string[] args)
        {
            ButtonPager buttonPager = new ButtonPager();
            ListPager listPager = new ListPager();
            Control gridview = new GridView();
            Control label = new Label();

            buttonPager.changePageHandler += new Pager.ChangePageHandler(buttonPager.ChangePage);
            buttonPager.changePageHandler += new Pager.ChangePageHandler(gridview.ChangePage);
            buttonPager.changePageHandler += new Pager.ChangePageHandler(label.ChangePage);
            buttonPager.changePageHandler += new Pager.ChangePageHandler(listPager.ChangePage);

            listPager.changePageHandler += new Pager.ChangePageHandler(buttonPager.ChangePage);
            listPager.changePageHandler += new Pager.ChangePageHandler(gridview.ChangePage);
            listPager.changePageHandler += new Pager.ChangePageHandler(label.ChangePage);
            listPager.changePageHandler += new Pager.ChangePageHandler(listPager.ChangePage);

            buttonPager.NextPage();
            Console.WriteLine();
            buttonPager.NextPage();
            Console.WriteLine();
            buttonPager.NextPage();
            Console.WriteLine();
            buttonPager.PreviousPage();
            Console.WriteLine();
            buttonPager.PreviousPage();
            Console.WriteLine();
            listPager.SelectPage(2);
            Console.WriteLine();
            listPager.SelectPage(1);
            Console.WriteLine();
            listPager.SelectPage(0);
        }
    }

    abstract class Pager
    {
        protected int pageIndex = 0;
        public int PageIndex { get => pageIndex; set => pageIndex = value; }
        protected int pageCount = 3;
        public int PageCount => pageCount;

        public event ChangePageHandler changePageHandler;
        public delegate void ChangePageHandler(Pager sender);

        protected void ChangePage()
        {
            changePageHandler?.Invoke(this);
        }
    }

    class ButtonPager : Pager, Control
    {
        public void NextPage()
        {
            if (pageIndex < pageCount - 1)
            {
                Console.WriteLine("Click NextPage Button...");
                pageIndex++;
                ChangePage();
            }
        }
        public void PreviousPage()
        {
            if (pageIndex > 0)
            {
                Console.WriteLine("Click PreviousPage Button...");
                pageIndex--;
                ChangePage();
            }
        }
        public void ChangePage(Pager sender)
        {
            base.pageIndex = sender.PageIndex;
            if (pageIndex > 0 && pageIndex < pageCount - 1)
                Console.WriteLine("<<Previous Next>>");
            else if (pageIndex == 0)
                Console.WriteLine("Next>>");
            else
                Console.WriteLine("<<Previous");
        }
    }

    class ListPager : Pager, Control
    {
        public void SelectPage(int pageIndex)
        {
            if (pageIndex >= 0 && pageIndex < pageCount)
            {
                Console.WriteLine($"Click <{pageIndex + 1}> Link...");
                base.pageIndex = pageIndex;
                ChangePage();
            }
        }
        public void ChangePage(Pager sender)
        {
            base.pageIndex = sender.PageIndex;
            for (int i = 1; i <= pageCount; i++)
                Console.Write(pageIndex + 1 == i ? $" <{i}> " : $" {i} ");
            Console.WriteLine();
        }
    }

    interface Control { void ChangePage(Pager sender); }

    class GridView : Control
    {
        public void ChangePage(Pager sender) => Console.WriteLine($"GridView->Show data of page {sender.PageIndex + 1}");
    }

    class Label : Control
    {
        public void ChangePage(Pager sender) => Console.WriteLine($"Label.Text=[{sender.PageIndex + 1}/{sender.PageCount}]");
    }
}

程式碼執行結果

Observer 模式執行結果

程式碼解析

  • 採用 C# 事件-委派 實作 Observer,雖非 GOF 標準結構,但保留核心角色。
  • Pager:抽象主體。ChangePage() 觸發事件;委派參數傳遞主體自身,讓觀察者可讀取狀態。
  • ButtonPager:具體主體。NextPage/PreviousPage 驗證邊界、更新索引、呼叫 ChangePage() 通知觀察者。
  • Control:抽象觀察者介面,定義統一處理簽章。
  • GridView / Label:具體觀察者,只關心「被通知後做什麼」(重新綁定資料、顯示頁碼)。
  • ButtonPager 同時實作 Control → 它既是主體也是觀察者。這在現實極常見(生物鏈中螳螂既捕蟬又被雀捕)。
  • ListPager 同樣雙重角色,觀察 ButtonPager 變化。
  • 訂閱邏輯:ButtonPager 事件掛 4 個委派(含自己),ListPager 同理。自我訂閱屬於程式碼重用策略:主體只負責通知,觀察者只負責響應,不在乎觸發來源。
  • 擴充性:新增 DropDownPager 只需繼承 Pager、實作 Control、掛上所有觀察者;新增 ListBox 只需實作 Control、訂閱所有 Pager 事件。無需修改既有代碼。
  • 註:本例為示範結構,未遵循 .NET 事件最佳實踐(如 EventHandler<T>、EventArgs、虛擬 OnEvent 等)。

何時採用

  • 一個對象行為觸發多個對象行為 → 主體/觀察者關係。
  • 為降低耦合,不希望主體直接呼叫觀察者,改用動態訂閱事件實現自動鏈式響應。
  • 需動態調整訂閱關係(觀察者增減、訂閱事件增減)。

實作要點

  • 抽象主體公開事件,任意觀察者可訂閱。
  • 抽象觀察者定義統一處理行為;C# 事件-委派下此介面較不關鍵,有時甚至限制彈性。
  • 委派訂閱可在觀察者內部或外部定義,外部定義彈性更高。
  • 具體觀察者通常只需實作響應方法。
  • 可有多主體、多觀察者交錯,單類別可雙重角色,主體可提供多事件。應用變化極大。

注意事項

  • 訂閱時需防範:破壞性行為、無限循環、死鎖、觀察者間相互影響。
  • 觀察者數量極大時可能造成效能問題,不宜濫用。
  • 跨網路等無法用事件-委派場景,可退回傳統 Observer 結構(介面+列表)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved