• 委托、事件、Observer观察者模式的使用解析二


    一、设计模式-Observer观察者模式

    Observer设计模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新。Observer模式是一种松耦合的设计模式。

    Observer设计模式中主要包括如下两类对象:
    Subject:监视对象,它往往包含着其他对象所感兴趣的内容。(如下例中的Boss,需要一直被手下盯着等候他给出指令)
    Observer:监视者,它监视Subject,当Subject中的某件事发生的时候,会告知Observer,而Observer则会采取相应的行动。(如下例中的MemberA和B,他们需要一直盯着Boss准备接收指令采取行动)

    例:

    老大带着小弟去谈判,如果老大举起左手则小A行动,如果老大举起右手则小B行动,如果情况不妙老大摔了杯子则小A小B一起行动。

    要完成以上需求,我们知道如果使用传统编码方式会采用,在老大的Boss类中有一个举手()和一个摔杯子()方法,在这个方法中调用小A和小B的行动方法。

    public class Boss
    {
        private MemberA memberA = new MemberA();
        private MemberB memberB = new MemberB();
        /// <summary>
        /// 举手
        /// </summary>
        /// <param name="hand"></param>
        public void Raise(string hand)
        {
            if (hand == "")
            {
                memberA.Attack();
            }
            else if (hand == "")
            {
                memberB.Attack();
            }
        }
        /// <summary>
        /// 摔杯子
        /// </summary>
        public void Fail()
        {
            memberA.Attack();
            memberB.Attack();
        }
    }
    public class MemberA
    {
        public void Attack()
        {
            //do something...
        }
    }
    public class MemberB
    {
        public void Attack()
        {
            //do something...
        }
    }

    好了,我们按照这种方式完成了编码,此时需求变更了,对方人数过多我们需要多带些人手,增加MemberC、MemberD,就要在Boss类中频繁的修改Raise()和Fail(),使程序毫无扩展性,那么我们改为事件委托的方式试试看。

    #region 观察者模式
    /// <summary>
    /// 左/右手
    /// </summary>
    public enum Hand
    {
        Left,
        Right
    }
    /// <summary>
    /// 监视对象
    /// </summary>
    public class Boss
    {
        //我要举起的手
        public Hand MyHand { get; set; }
        //举手-委托-事件
        public delegate void RaiseDelegate(Hand myHand);
        public event RaiseDelegate RaiseEventHandler;
        //摔杯子-委托-事件
        public delegate void FailDelegate();
        public event FailDelegate FailEventHandler;
        
        public void RaiseHand()
        {
            Console.WriteLine("举手-" + MyHand);
            if (RaiseEventHandler != null)
            {
                RaiseEventHandler(MyHand);
            }
        }
        public void FailAll()
        {
            Console.WriteLine("摔杯子");
            if (FailEventHandler != null)
            {
                FailEventHandler();
            }
        }
    }
    /// <summary>
    /// 监视者A
    /// </summary>
    public class MemberA
    {
        public void HandLeft(Hand myHand)
        {
            if (myHand == Hand.Left)
                Attack();
        }
        public void Attack()
        {
            Console.WriteLine("MemberA攻击");
        }
    }
    /// <summary>
    /// 监视者B
    /// </summary>
    public class MemberB
    {
        public void HandRight(Hand myHand)
        {
            if (myHand == Hand.Right)
                Attack();
        }
        public void Attack()
        {
            Console.WriteLine("MemberB攻击");
        }
    }
    #endregion
    #region 调用代码
    
    Boss boss = new Boss();
    MemberA memberA = new MemberA();
    MemberB memberB = new MemberB();
    
    boss.RaiseEventHandler += memberA.HandLeft;
    boss.FailEventHandler += memberA.Attack;
    
    boss.RaiseEventHandler += memberB.HandRight;
    boss.FailEventHandler += memberB.Attack;
    
    boss.MyHand = Hand.Left;
    boss.RaiseHand();
    boss.FailAll();
    
    #endregion

    改造完成,又有需求变更了,增加老大带的人手,此时我们主类Boss类不需要进行任何修改,只需要增加新的小弟类MemberC、MemberD等等即可,这样也实现了“对扩展说Yes,对修改说No”的宗旨。

    二、.Net中的事件与委托

    仔细看会发现,我们写的和.Net中自带的委托事件不太一样,尤其是Webform时的那些按钮事件

    首先.Net有几条编码规范:

    1、委托类型的名称都应该以EventHandler结尾

    2、委托的原型定义,要无返回值void,要有两个输入参数,一个object类型,一个EventArgs类型(或继承EventArgs)

    3、事件的名称为委托去掉EventHandler剩余的部分

    4、继承EventArgs的子类名称都应以EventArgs结尾

    结合我们的例子解释一下:

    1、委托声明原型参数中object对象代表了Subject,也就是监视对象,本例中就是Boss自己,注册委托的方法(MemberA)中可以访问触发事件的对象(Boss)

    2、EventArgs对象代表了Observer,也就是监视者,其中包含了监视者所需要的数据,本例中就是举起的那只手左还是右

    这样的设计并不是硬套编码规范,它还具有很强的灵活性,如果需要在Observer下显示Subject的信息,比如想显示出手(MemberA)下保护了那个老大(Boss.Name),就需要在委托方法中增加很多参数,而这样我们直接

    把Boss对象引用传递给MemberA的方法,就可以根据自己的需求直接访问Boss了

    #region 观察者模式
    /// <summary>
    /// 左/右手
    /// </summary>
    public enum Hand
    {
        Left,
        Right
    }
    /// <summary>
    /// 监视对象
    /// </summary>
    public class Boss
    {
        //我的手
        public Hand MyHand { get; set; }
        //我的名字
        public string MyName
        {
            get { return "Taiyonghai"; }
        }
        //举手-委托-事件
        public delegate void RaiseEventHandler(object sender, RaiseEventArgs e);
        public event RaiseEventHandler Raise;
        //举手事件参数对象(此对象就是为了整合所有监视者关心的属性,便于直接传递给监视者使用,也就是监视者需要什么字段就定义什么字段)
        public class RaiseEventArgs : EventArgs
        {
            public Hand myHand;
            public RaiseEventArgs(Hand myHand)
            {
                this.myHand = myHand;
            }
        }
        //举手后需要执行事件方法(执行是否调用已注册到此事件上的方法,设为虚方法为继承此类的子类提供的可重写的能力,
        //以便子类自己选择是否接受监视并调用已注册方法,或者不被监视不调用任何方法)
        public virtual void OnRaiseHand(RaiseEventArgs e)
        {
            if (Raise != null)  //有否已有注册方法
            {
                Raise(this, e); //调用所有已注册的方法
            }
        }
        //举手方法
        public void RaiseHand()
        {
            Console.WriteLine("举手-" + MyHand);
            RaiseEventArgs e = new RaiseEventArgs(MyHand);
            OnRaiseHand(e);
        }
            
        //摔杯子-委托-事件
        public delegate void FailEventHandler(object sender);
        public event FailEventHandler Fail;
        //摔杯子需要执行事件方法
        public virtual void OnFailAll()
        {
            if (Fail != null)
            {
                Fail(this);
            }
        }
        //摔杯子方法
        public void FailAll()
        {
            Console.WriteLine("摔杯子");
            OnFailAll();
        }
    }
    /// <summary>
    /// 监视者A
    /// </summary>
    public class MemberA
    {
        public void HandLeft(object sender, Boss.RaiseEventArgs e)
        {
            Boss boss = (Boss)sender;
            if (e.myHand == Hand.Left)
                Console.WriteLine("MemberA单独行动,保护" + boss.MyName);
        }
        public void Attack(object sender)
        {
            Boss boss = (Boss)sender;
            Console.WriteLine("MemberA一起行动,保护" + boss.MyName);
        }
    }
    /// <summary>
    /// 监视者B
    /// </summary>
    public class MemberB
    {
        public void HandRight(object sender, Boss.RaiseEventArgs e)
        {
            Boss boss = (Boss)sender;
            if (e.myHand == Hand.Right)
                Console.WriteLine("MemberB单独行动,保护" + boss.MyName);
        }
        public void Attack(object sender)
        {
            Boss boss = (Boss)sender;
            Console.WriteLine("MemberB一起行动,保护" + boss.MyName);
        }
    }
    #endregion
    #region 调用代码
    
    Boss boss = new Boss();
    MemberA memberA = new MemberA();
    
    boss.Raise += memberA.HandLeft;         //普通注册方法(也可以静态方法注册)
    boss.Fail += memberA.Attack;
    
    boss.Raise += new MemberB().HandRight;  //匿名对象注册方法
    boss.Fail += new MemberB().Attack;      
    
    boss.MyHand = Hand.Left;
    boss.RaiseHand();
    boss.FailAll();
    
    #endregion

    参照别人的讲解,编写了这个小Demo,总算是理解了委托、事件、Observer设计模式及.Net规范下的委托事件写法,重点还是要应用到工作当中才能真正把知识变成自己的

    继续共享出我参照的文章地址:http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-in-CSharp.aspx

    还有一篇解释细节的文章,我还没有时间看,先做个记录,文章地址:http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-Advanced.aspx

  • 相关阅读:
    字符编码
    visual studio 2015 安装记录和问题修复
    TCP状态转换图的理解
    静态库与动态库的编译链接
    运行库glibc
    堆栈的简单认识
    Makefile学习总结
    关于STM32单片机的IAP实现
    ubuntu12.0.4安装启动后无法进入图形操作界面
    观察者模式
  • 原文地址:https://www.cnblogs.com/taiyonghai/p/6549681.html
Copyright © 2020-2023  润新知