(42)C#设计模式——观察者模式(Observer Pattern)

定义

从生活中的例子可以看出,只要对订阅号关注的客户端,如果订阅号有什么更新,就会直接推送给订阅了的客户端。从中,我们可以理解观察者模式的定义。

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象,这个主题对象在状态发生改变时,会通知所有观察者对象,使它们能够自动更新自己的行为。

结构

从上面观察者模式的定义和生活中的例子,我们知道了观察者模式中首先会存在两个对象,一个是观察者对象,另一个就是主题对象(被观察的对象),然而,根据面向接口编程的原则,自然就有抽象主题角色和抽象观察者角色。要想主题对象状态发生改变时,能通知到所有观察者角色,则主题角色必须拥有观察者的引用。

可以看出,观察者模式的结构图有以下角色:

  • 抽象主题角色(Subject):抽象主题把所有观察者对象的额引用保存在一个列表中,并提供增加和删除观察者对象的操作,抽象主题角色又叫做被观察者角色,一般由抽象类或接口实现。
  • 抽象观察者角色(Observer):为所有具体观察者定义一个接口,在的到主题通知时更新自己,一般由抽象类或接口实现
  • 具体主题角色(ConcreteSubject):实现抽象主题角色,具体主题角色又叫做具体被观察者角色
  • 具体观察者角色(ConcreteObserver):实现抽象观察者角色所要求的接口,以便使自身状态与主题的状态相协调

实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    //腾讯游戏订阅号类
    public class TenxunGame
    {
        //订阅对象
        public Subscriber Subscriber { get; set; }
        public string Symbol { get; set; }
        public string Info { get; set; }
        public void Update()
        {
            if(Subscriber != null)
            {
                //调用订阅者对象来通知订阅者
                Subscriber.ReceiveAndPrintData(this);
            }
        }
    }
    //订阅者类
    public class Subscriber
    {
        public string Name { get; set; }
        public Subscriber(string name)
        {
            this.Name = name;
        }
        public void ReceiveAndPrintData(TenxunGame txGame)
        {
            Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, txGame.Symbol, txGame.Info);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Subscriber Bob = new Subscriber("Bob");
            TenxunGame txGame = new TenxunGame();

            txGame.Subscriber = Bob;
            txGame.Symbol = "TenXun Games";
            txGame.Info = "Wo had published a new game todday.......";
            txGame.Update();
        }
    }
}

上面的代码实现了监控订阅号的任务。但存在问题:

  1. TenxunGame和Subscriber类之间形成了一种双向依赖关系,即TenxunGame调用了Subscriber的ReceiveAndPrintData方法,而Subscriber调用了TenxunGame类的属性。这样的实现,如果有其中一个类变化将引起另外一个类的变化
  2. 当出现一个新的订阅者时,此时不得不修改TenxunGame代码,即添加另外一个订阅者的引用和在Update方法中调用另一个订阅者的方法

上面的设计违背了“开闭原则。对此我们进一步抽象,既然这里变化的部分是新订阅者的出现,这样我们可以对订阅者抽象出一个接口,用它来取消TenxunGame类与具体订阅者之间的依赖,这一步的改进,确实可以解决问题,但还不能解决出现一个新订阅者就要修改TenxunGame代码的问题。所以,进一步思考——订阅号存在多个订阅者,我们可以采用一个列表来保存所有订阅者对象,在订阅号内部在添加对该列表的操作,这样不就解决了出现了新的订阅者的问题了。并且订阅号也属于变化的部分,所有,我们可以采用相同的方式抽象订阅号,抽象成订阅号类。修改后的实现为:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    
    //订阅号抽象类
    public abstract class TenXun
    {
        //保存订阅者列表
        private List<IObserver> observers = new List<IObserver>();

        public string Symbol;
        public string Info;
        public TenXun(string symbol, string info)
        {
            this.Info = info;
            this.Symbol = symbol;
        }
        #region 新增对订阅者列表的添加和删除操作
        public void AddObserver(IObserver ob)
        {
            observers.Add(ob);
        }
        public void RemoveObserver(IObserver ob)
        {
            observers.Remove(ob);
        }
        #endregion
        public void Update()
        {
            //遍历订阅者进行通知
            foreach (IObserver ob in observers)
            {
                if (ob != null)
                {
                    ob.ReceiveAndPrintData(this);
                }
            }
        }
    }
    //具体订阅号类
    public class TenxunGame : TenXun
    {
        public TenxunGame(string symbol, string info) : base(symbol, info)
        {

        }
    }
    //订阅者接口
    public interface IObserver
    {
        void ReceiveAndPrintData(TenXun tenxun);
    }
    //具体订阅者类
    public class Subscriber : IObserver
    {
        public string Name { get; set; }
        public Subscriber(string name)
        {
            this.Name = name;
        }
        public void ReceiveAndPrintData(TenXun tenxun)
        {
            Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, tenxun.Symbol, tenxun.Info);
        }
    }
    //客户端测试
    class Program
    {
        static void Main(string[] args)
        {
            TenXun tenXun = new TenxunGame("TenxunGame", "Have a new game published...");
            //添加订阅者
            tenXun.AddObserver(new Subscriber("Bob"));
            tenXun.AddObserver(new Subscriber("Lily"));
            tenXun.AddObserver(new Subscriber("Tom"));
            tenXun.Update();       
        }
    }
}

适用场景

  • 当一个抽象模型有两个方面,其中一个方面依赖另一个方面,将这两者封装在独立的对象中以使用它们可以各自独立地改变和复用的情况下。
  • 当一个对象的改变需要同时改变其他对象,而又不知道具体有多少对象有待改变的情况下
  • 当一个对象必须通知其他对象,而又不能假定其他对象是谁的情况下

优缺点

优点:

  • 观察者模式实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层,即观察者。
  • 观察者模式在被观察者和观察者之间建立了一个抽象的耦合,被观察者并不知道任何一个具体的观察者,只是保存着抽象观察者的列表,每个具体观察者都符合一个抽象观察者的接口。
  • 观察者模式支持广播通信。被观察者会向所有的注册过的观察者发出通知。

缺点:

  • 如果一个被观察者有很多直接和间接的观察者时,将所有的观察者都通知到会花费很多时间。
  • 虽然观察者模式可以随时使观察者知道所观察的对象发送了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎样发生变化的。
  • 如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃,在使用观察者模式应特别注意这点。

总结

观察者模式定义了一种一对多的依赖关系,让多个观察者对象可以同时监听一个主图对象,这个主题对象发生变化时,会通知所有观察者对象,使它们能够自动更新自己。解决的是“当一个对象的改变需要同时改变多个其他对象”的问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值