18.Observer观察者(行为型模式)

本文详细介绍了Observer设计模式,包括其动机、意图和结构。通过拍卖的例子,展示了Observer模式如何实现对象间的松耦合通知机制。此外,还探讨了推模式与拉模式的区别,并给出了.NET中事件和委托的实现方式,以及在猫和老鼠、竞拍场景中的应用。文章最后总结了Observer模式的适用性和优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一:动机(Motivation)

<1>在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系” ——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。

<2>使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。

二:意图(Intent)

定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
——《设计模式》GoF

三:结构(Structure)

在这里插入图片描述

四:结构详解

在这里插入图片描述

五:生活中的例子

拍卖,每个投标人都有一个标有数字的牌子用于出价。拍卖师(主题)每次接受一个新的出价都改变了拍卖的当前价格,并且广播给所有的投标人(观察者)进行新的出价。

在这里插入图片描述

六:实现

namespace Test
{
    /// <summary>
    /// Observer
    /// </summary>
    public abstract class 竞拍人
    {
        //Update()
        public abstract void 出价();
    }

    /// <summary>
    /// Subject
    /// </summary>
    public abstract class 拍卖
    {
        protected System.Collections.ArrayList observers = new System.Collections.ArrayList();

        //Attach
        public void 注册竞拍人(竞拍人 person)
        {
            this.observers.Add(person);
            Console.WriteLine("---{0}加入拍卖。", person);
        }

        //Detach
        public void 注销竞拍人(竞拍人 person)
        {
            this.observers.Remove(person);
            Console.WriteLine("---{0}离开拍卖", person);
        }

        //Notify广播
        public virtual void 报价()
        {
            for (int i = this.observers.Count - 1; i >= 0; i--)
            {
                ((竞拍人)this.observers[i]).出价();
            }
        }
        public abstract bool 结束 { get; }
    }

    /// <summary>
    /// ConcreteSubject
    /// </summary>
    public class 艺术品拍卖 : 拍卖
    {
        public int price;
        public 艺术品拍卖(int 起拍价格)
        {
            this.price = 起拍价格;
        }

        //SubjectState
        public int 当前价格
        {
            get { return this.price; }
            set
            {
                if (this.price > value)
                {
                    return;
                }
                this.price = value;
            }
        }

        //Notify广播
        public override void 报价()
        {
            base.报价();
            if (base.observers.Count == 1)
            {
                Console.WriteLine("{0}最终胜出,最终报价为:{1}", base.observers[0], this.price);
            }
            else
            {
                if (base.observers.Count == 0)
                {
                    Console.WriteLine("本次拍卖流拍!");
                }
            }
        }

        //SubjectState
        public override bool 结束
        {
            get { return base.observers.Count == 1 || base.observers.Count == 0; }
        }
    }

    /// <summary>
    /// ConcreteObserver
    /// </summary>
    public class 艺术品竞拍人 : 竞拍人
    {
        //ObserverState
        private int myprice;
        private int topprice;
        private string name;

        //subject
        private 艺术品拍卖 subject;

        public 艺术品竞拍人(int 起始价位, int 最高心理价位, string 竞拍人姓名, 艺术品拍卖 参加的拍卖)
        {
            this.myprice = 起始价位;
            this.topprice = 最高心理价位;
            this.name = 竞拍人姓名;
            this.subject = 参加的拍卖;
            this.subject.注册竞拍人(this);
        }

        public override string ToString()
        {
            return this.name;
        }

        //Update
        public override void 出价()
        {
            if (this.subject.当前价格 >= this.topprice)
            {
                Console.WriteLine("{0}退出,当前最高价格:{1},已超出最高心理价位:{2}", this.name, this.subject.当前价格, this.topprice);
                this.subject.注销竞拍人(this);
            }
            else
            {
                this.myprice += 1;
                this.subject.当前价格 = myprice;
                if (this.myprice >= this.subject.当前价格)
                {
                    Console.WriteLine("{0}报出新的最高价格:{1}", this.name, this.myprice);
                }
            }
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            艺术品拍卖 subject = new 艺术品拍卖(8);
            竞拍人 observer1 = new 艺术品竞拍人(8, 14, "比尔盖茨", subject);
            竞拍人 observer2 = new 艺术品竞拍人(8, 12, "布什", subject);
            竞拍人 observer3 = new 艺术品竞拍人(8, 10, "普京", subject);
            竞拍人 observer4 = new 艺术品竞拍人(8, 10, "萨达姆", subject);

            int n = 1;
            while (!subject.结束)
            {
                Console.WriteLine("========第{0}轮报价=========", n);
                subject.报价();
                n++;
            }

            Console.ReadLine();
        }
    }
}

七:实现结果

在这里插入图片描述

八:实现要点

<1>Observer依赖Subject;
<2>Subject需要维护多个Observer;
<3>Observer的状态依赖于Subject的状态。

九:警察抓罪犯代码

namespace Test
{
    /// <summary>
    /// 关注的目标
    /// </summary>
    public abstract class Subject
    {
        public List<Observer> observers = new List<Observer>();
        public void Attach(Observer observer)
        {
            observers.Add(observer);
        }

        public void Detach(Observer observer)
        {
            observers.Remove(observer);
        }

        public void NotifyAllObservers()
        {
            foreach (var observer in this.observers)
            {
                observer.Update();
            }
        }
    }

    /// <summary>
    /// 关注目标的对象:观察者
    /// </summary>
    public abstract class Observer
    {
        //对目标的通知做出响应
        public abstract void Update();
    }

    public class Criminal : Subject
    {
        private string currentLocation = "";
        public string CurrentLocation
        {
            get { return currentLocation; }
        }
        public void RunAway(string location)
        {
            this.currentLocation = location;
            base.NotifyAllObservers();
        }
    }

    public class PoliceMan : Observer
    {
        private Criminal target = null;
        public PoliceMan(Criminal target)
        {
            this.target = target;
            target.Attach(this);
        }
        public override void Update()
        {
            var location = this.target.CurrentLocation;
            Console.WriteLine("警察在{0}部署警力进行围堵", location);
        }
    }

    public class Citizen : Observer
    {
        private Criminal target = null;
        public Citizen(Criminal target)
        {
            this.target = target;
            target.Attach(this);
        }
        public override void Update()
        {
            var location = this.target.CurrentLocation;
            Console.WriteLine("热心市民:打110报警,坏人在:{0}", location);
        }
    }

    public class Wife : Observer
    {
        private Criminal target = null;
        public Wife(Criminal target)
        { 
            this.target = target;
            target.Attach(this);
        }
        public override void Update()
        {
            var location = this.target.CurrentLocation;
            Console.WriteLine("老婆:在{0}准备接应", location);
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            Subject subject = new Criminal();
            Observer police = new PoliceMan(subject as Criminal);
            Observer citizen = new Citizen(subject as Criminal);
            Observer wife = new Wife(subject as Criminal);

            Criminal criminal = subject as Criminal;

            criminal.RunAway("深圳");
            criminal.RunAway("广州");

            Console.WriteLine();
            subject.Detach(wife);
            criminal.RunAway("美国");

            subject.Detach(police);
            Console.WriteLine();
            criminal.RunAway("澳大利亚");
            Console.WriteLine();

            subject.Detach(citizen);
            criminal.RunAway("月球");

            Console.ReadLine();
        }
    }
}

实现结果
在这里插入图片描述

十:推模式与拉模式

推模式
<1>当有消息时,把消息信息以参数的形式传递(推)给所有观察者;
<2>所有的观察者得到的消息是一样的,也许有些信息对某个观察者来说根本就用不上,也就是观察者不能“按需所取”;
<3>当通知消息的参数有变化时,所有的观察者对象都要变化。

拉模式
<1>当有消息时,通知消息的方法本身并不带任何的参数,是由观察者自己到主体对象那儿取回(拉)消息;
<2>由观察者自己主动去取消息,需要什么信息,就可以取什么,不会像推模式那样得到所有的消息参数。

十一:.NET中的事件和委托

<1>利用事件和委托来实现Observer模式更加的简单和优雅,也是一种更好的解决方案;
<2>通过Observer模式,把一对多对象之间的通知依赖关系的变得更为松散,大大地提高了程序的可维护性和可扩展性,也很好的符合了开放-封闭原则。

十二:(简单)事件和委托版-猫和老鼠

namespace Test
{
    /// <summary>
    /// 委托,用来处理"心情改变"的方法签名
    /// 猫是拉模式
    /// 心情Args是推模式
    /// </summary>
    public delegate void 心情改变Handler(猫 sender, 心情Args e);

    /// <summary>
    /// 事件参数
    /// </summary>
    public class 心情Args
    {
        private bool good = false;
        public bool 好心情
        {
            get { return good; }
            set { good = value; }
        }
    }

    public class{
        private string name;
        public string Name { get { return name; } }

        public(string name)
        {
            this.name = name;
        }

        //猫的事件,心情会改变
        public event 心情改变Handler 心情改变;

        public void 改变心情(bool 好不好)
        {
            if (this.心情改变 != null)
            {
                心情Args e = new 心情Args();
                e.好心情 = 好不好;
                this.心情改变(this, e);
            }
        }
    }

    public class 老鼠
    {
        private string name;
        public 老鼠(string name)
        {
            this.name = name;
        }
        public override string ToString()
        {
            return this.name; 
        }

        public void 应急处理(猫 cat, 心情Args 心情参数)
        {
            if (心情参数.好心情)
            {
                Console.WriteLine("{0}:\t今天{1}的心情不错,不用担心.", this, cat.Name);
            }
            else
            {
                Console.WriteLine("{0}:\t警报警报,{1}今天心情大坏,逃命要紧.", this, cat.Name);
            }
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            猫 tom = new("tom");
            老鼠 jerry = new 老鼠("jerry");
            老鼠 jack = new 老鼠("jack");
            老鼠 steve = new 老鼠("steve");

            tom.心情改变 += new 心情改变Handler(jerry.应急处理);
            tom.心情改变 += new 心情改变Handler(jack.应急处理);

            心情改变Handler steveHandler = new 心情改变Handler(steve.应急处理);
            tom.心情改变 += steveHandler;

            tom.改变心情(true);
            tom.改变心情(false);

            tom.心情改变 -= steveHandler;  //steve不再监听tom 的 心情改变 事件 
            Console.WriteLine("-------------steve不再监听tom 的 心情改变 事件 ----------");

            tom.改变心情(true);
            tom.改变心情(false);

            Console.ReadLine();
        }
    }
}

实现结果

在这里插入图片描述

十三:实现要点

<1>本例:猫和老鼠:
只有单向监听:即,老鼠监听猫的事件;
只有单向广播:即,只有猫向老鼠广播(激发事件)。
<2>+= 可以实现注册监听。
<3>-= 可以实现注销监听。

十四:事件和委托版-竞拍

namespace Test
{
    /// <summary>
    /// 竞拍的事件,拍卖和竞拍人都会根据此事件参数进行判断
    /// </summary>
    public class 竞拍EventArgs : EventArgs
    {
        private int price;
        public int 价格
        {
            get { return this.price; }
            set { this.price = value; }
        }
    }

    public class 拍卖
    {
        public int price;
        public 拍卖(int 起拍价格)
        { 
            this.price = 起拍价格; 
        }

        public int 当前价格
        {
            get { return this.price; }
        }

        //当前最高出价的竞拍人
        private 竞拍人 winner = null;

        //专门处理竞拍人出价的事件,类似于对出价做出响应
        public void 出价Handler(object sender, EventArgs e)
        {
            竞拍EventArgs args = e as 竞拍EventArgs;
            //如果竞排人的价格高于当前的价格
            if (args.价格 >= this.price)
            {
                this.price = args.价格;  //更新当前的价格为最高的
                竞拍人 person = sender as 竞拍人;  //把事件的sender转化为竞拍人
                this.winner = person;  //当前出价最高的人是事件的sender(激发人)
                Console.WriteLine("{0}提出新价格:{1}", person, args.价格);
            }
        }

        //会发出事件:报价(广播)
        public event EventHandler 报价 = null;
        //是否结束
        public bool 结束
        {
            get
            {
                //如果已有最高出价者,并且,只有一个人在报价,此人便胜出,拍卖结束
                if (winner != null && this.报价.GetInvocationList().Length == 1)
                {
                    Console.WriteLine("拍卖结束,获胜者为:{0},最高价格为:{1}", this.winner, this.price);
                    return true;
                }
                //如果没有人参与报价(没有人对拍卖关注),则流拍,此时,拍卖结束
                if (this.报价 == null)
                {
                    Console.WriteLine("本次拍卖流拍.");
                    return true;
                }
                else
                {
                    //既然没有结束,就激发事件:报价,同时,把竞拍参数传出 
                    竞拍EventArgs e = new 竞拍EventArgs();
                    e.价格 = this.price;
                    this.报价(this, e);
                    return false;
                }
            }
        }
    }

    public class 竞拍人
    {
        private int myprice;
        private int topprice;
        private string name;
        public 竞拍人(string 竞拍人姓名, int 起始价位, int 最高心理价位)
        {
            this.myprice = 起始价位;
            this.topprice = 最高心理价位;
            this.name = 竞拍人姓名;
        }
        public override string ToString()
        {
            return this.name;
        }
        //竞拍人会对拍卖的出价事件做出响应,引处为事件:出价
        public event EventHandler 出价 = null;

        //专门处理拍卖出价事件的方法
        public void 报价Handler(object sender, EventArgs e)
        {
            竞拍EventArgs args = e as 竞拍EventArgs;
            //如果拍卖的最高价格还不高于自己的最高价,继续出价
            if (args.价格 <= this.topprice - 2)
            {
                this.myprice += 2;  //这里写成每次+2元
                args.价格 = this.myprice;
                this.出价(this, args);  //激发事件,出价
            }
            else
            {
                //把sender转化为拍卖对象
                拍卖 subject = sender as 拍卖;
                //在拍卖中取消自己对拍卖的报价事件的处理,即取消监听
                subject.报价 -= new EventHandler(this.报价Handler);
            }
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            拍卖 subject = new 拍卖(8);

            竞拍人 observer1 = new 竞拍人("比尔盖茨", 8, 15);
            竞拍人 observer2 = new 竞拍人("布什", 8, 16);
            竞拍人 observer3 = new 竞拍人("普京", 8, 14);
            竞拍人 observer4 = new 竞拍人("萨达姆", 8, 13);

            subject.报价 += new EventHandler(observer1.报价Handler);
            subject.报价 += new EventHandler(observer2.报价Handler);
            subject.报价 += new EventHandler(observer3.报价Handler);
            subject.报价 += new EventHandler(observer4.报价Handler);

            observer1.出价 += new EventHandler(subject.出价Handler);
            observer2.出价 += new EventHandler(subject.出价Handler);
            observer3.出价 += new EventHandler(subject.出价Handler);
            observer4.出价 += new EventHandler(subject.出价Handler);

            int n = 1;
            Console.WriteLine("========第{0}轮报价=========", n);
            while (!subject.结束)
            {
                n++;
                Console.WriteLine("========第{0}轮报价=========", n);
            }

            Console.ReadLine();
        }
    }
}

实现结果

在这里插入图片描述

十五:实现要点

<1>本例:事件和委托版拍卖略为复杂:
拍卖与竞拍人之间相互监听
拍卖监听竞拍人的出价事件
竞拍人监听拍卖的报价事件
这是一种双向监听
<2>更多时候,使用单向监听,即:单向广播。

十六:适用性

<1>需要实现发布-订阅模型时
<2>需要实现广播机制时

十七:总结

<1>使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
<2>目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知,目标对象对此一无所知。
<3>在C#的event中,委托充当了抽象的Observer接口,而提供事件的对象充当了目标对象。委托是比抽象Observer接口更为松耦合的设计。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值