当先锋百科网

首页 1 2 3 4 5 6 7

不使用委托实现Observer模式

 

Observer设计模式中实际上只包含两类对象,一个是Subject(主题),一个是Observer(观察者).他们之间的角色是:

Subject是被监视对象,它往往包含着Observer所感兴趣的内容.

Observer是观察者,它观察Subject.Subject中的某件事情发生的时候(通常是它所感兴趣的内容改变的时候),会被自动告知,Observer则会采取相应的行动(通常为更新自身状态或者显示输出).

 

他们之间交互的核心工作流程是:

(1).Subject提供方法,比如Register()UnRegister(),用于Observer进行注册和取消注册.

Register()方法接受一个Observer的引用作为参数,并保存此引用.

保存的方法通常为在Subject内声明一个集合类,比如List<Observer>

一个Subject可以供多个Observer注册.

(2)调用Subject实例的Register()方法,并将一个Observer的引用传递进去.

(3)Observer包含一个Update()方法,此方法供Subject以后调用.

(4)Subject包含一个Notify()方法,当某些事发生时,调用Notify(),通知Subject.

 

Notify()的方法实现为:遍历保存Observer引用的集合类,然后在Observer的引用上调用Update方法,更新Observer.

某件事是一个不确定的事,对于热水器来说,这个事就是”温度达到一定高度”.它对外界暴露的方法应该是”烧水”,BoilWater()而不是Notify(),所以Notify通常时限为私有方法.

 

 

Observer模式的接口定义

按照面向对象设计的原则:面向接口编程,而非面向实现编程.那么现在应该首先定义SubjectObject的接口,大家可能很自然的想到将这个两个分别命名为ISubjectIObserver.实际上,其实这里约定俗成的命名为:IObservableIObserver,其中IObservableSubject实现.

 

现在先来看看Subject需要实现的接口IObservable

 

1.IOservable接口

首先创建解决方法ObserverPattren,并在其下添加控制台项目ConsoleApp,然后加入IObservable.cs文件,进而完成这个接口.如上面所分析的,Subject将实现这个接口,它只用定义两个方法Register()UnRegister():

    public interface IObservable
    {
        void Register(IObserver obj);//注册IOberver
        void UnRegister(IObserver obj);//取消IObserver的注册
}
 

注意它的两个方法接受IObserver类型的而对象,分别用于注册和取消注册.

 

2.IObserver接口

接下来再来完成IObserver接口,所有的Observer都需要实现这个接口,以便在事情发生时被自动告知(自动调用其Update()方法,改变自身状态),它仅包含一个Update()方法:

    public interface IObserver
    {
        void Update();
    }

强调一下,这里的关键就是Update()方法不是由Observer本身调用,而是由Subject在某件事情发生时调用.

 

3.抽象基类SubjectBase

定义一个抽象类,让它实现IObservable接口,并使用List<IObserver>作为容器的一个默认实现,以后再创建实现IObservable的类(Subject),只需要继承这个基类就可以了,这样可以更好地对代码进行重用:

    public abstract class SubjectBase : IObservable
    {
        //使用一个List<T>作为IObserver引用的容器
        private List<IObserver> container = new List<IObserver>();
        public void Register(IObserver obj)
        {
            container.Add(obj);
        }
 
        public void UnRegister(IObserver obj)
        {
            container.Remove(obj);
        }
        protected virtual void Notify()//通知所有注册了的Observer
        {
            foreach (IObserver observer in container)
            {
                observer.Update();//调用Observer 的Update()方法
            }
        }
    }
 

Observer模式的实现

 

有了前面的准备,现在就可以实现Observer模式,先创建实体类:热水器(Heater),报警器(Alarm),显示器(Screen).其中,热水器是Subject,报警器和显示器是Observer,报警器和显示器关心的东西是热水器的水温,当热水器的水温大于97度时,显示器需要显示”水快烧开了”,报警器发出声音,提示”滴滴滴,水快开了

 

下面一一来看看各个类的具体实现:

1.热水器的实现,热水器需要继承自SubjectBase基类,并添加了BoilWater()方法

    public class Heater : SubjectBase
    {
        private string type;//型号
        private string area;//产地
        private int temprature;   //水温
        public Heater(string type, string area)
        {
            this.type = type;
            this.area = area;
            temprature = 0;
        }
        public string Type
        {
            get
            {
                return type;
            }
        }
 
        public string Area
        {
            get
            {
                return area;
            }        
        }
 
        public Heater() : this("中国台湾","飞天一号") { }
 
        //供子类覆盖,以便子类拒绝被通知,或添加额外行为
        protected virtual void OnBoiled()
        {
            base.Notify();//调用父类的Notify()方法,今儿调用所有注册了Observer的Update()方法
        }
        public void BoilWater()//烧水
        {
            for (int i = 0; i < 100; i++)
            {
                temprature = i + 1;
                if (temprature>97)//当水快烧开时(温度>97度),通知Observer
                {
                    OnBoiled();
                }
            }
        }
 
    }


 

2.报警器和显示器的实现

报警器(Alarm)和显示器(Screen)的实现是类似的,仅仅为了说明多个Observer可以注册同一个Subject.

    #region 显示器
    public class Screen : IObserver
    {
        //Subject在事件发生时调用,通知Observer更新状态(通过Notify()方法)
        public void Update()
        {
            Console.WriteLine("Screen".PadRight(7)+": 水快开了");
        }
    }
    #endregion
 
    #region 报警器
    public class Alarm : IObserver
    {
        public void Update()
        {
            Console.WriteLine("Alarm".PadRight(7)+": 滴滴滴,水快开了");
        }
    }
    #endregion


3.测试代码如下:

 

        static void Main(string[] args)
        {
            Heater heater = new Heater();
            Screen screen = new Screen();
            Alarm alarm = new Alarm();
            heater.Register(screen);//注册显示器
            heater.Register(alarm);//注册报警器
 
            heater.BoilWater();//烧水
            heater.UnRegister(alarm);//取消报警器的注册
 
            Console.WriteLine();
            heater.BoilWater();//再次烧水
 
 
        }

当然了,像上面这样的实现方式,没啥意义.比如说,我们通常希望在Screen上能够即时的显示谁的温度,当水在100度的时候显示”水已经烧开”,而非”快烧开了”.我们还可能希望显示热水器的型号和产地.所以需要在ObserverUpdate()方法中能够获得Subject中所发生的事件监督状况或事件触发者Subject的状态和属性.在本例中事件的进展状况就是水温;事情触发者的状态和属性,则是热水器的型号和产地.此时,通常有两种策略,一种是推模式,一种是拉模式.

 

1.Observer中的推模式

 

顾名思义,推模式就是Subject在事情发生后调用Notify,将事件的状况(水温)及其自身的属性(又称为状态)封装为一个对象,推给Observer.如何实现了?当然是通过Notify()方法,Notify()方法接受这个对象,Notify()方法内部,再次将对象传递给Update()方法.那么现在需要做两件事

 

(1)创建新类型,这个类型封装了我们想要推给Observer(显示器)的事件进展状况(水温),以及事件触发者Subject(热水器)的属性.

ObserverPattren解决方案下重新创建一个控制台项目,命名为ConsoleApp2,并设置为启动项目.将上一个项目ConsoleApp中的文件复制进来,然后创建一个新类型BoiledEventArgs,用它来封装将要推给Observer的数据.

 

    public class BoiledEventArgs
    {
        private int temprature;
        private string type;
        private string area;
 
        public int Temprature
        {
            get
            {
                return temprature;
            }
        }
 
        public string Type
        {
            get
            {
                return type;
            }
        }
 
        public string Area
        {
            get
            {
                return area;
            }
        }
 
        public BoiledEventArgs(int temprature, string type, string area)
        {
            this.temprature = temprature;
            this.type = type;
            this.area = area;
        }                
}
 

注意这个类型的命名虽然是BoiledEventArgs,但是和.NET中的内置类型EventArgs没关系,只是去了一个这样的名字.

 

(2)需要一次修改IObserver接口,Screen类的Update()方法,SubjectBase,以及Heater,让它们可以接受这个EventArgs参数.因为出于示范的目的,后面的例子都将不再使用警报器Alarm,它的存在仅仅是为了说明多个Observer可以注册一个Subject.上面已经示范过了,所以现在完全可以把它删了.

 

先看看IOserver接口

    public interface IObserver
    {
        void Update(BoiledEventArgs e);
    }


接口变了,显示器(Screen)的实现也需要改变:

 

    public class Screen : IObserver
    {
        private bool isDisplayedType = false;//标记变量,标示是否已经打印过
        public void Update(BoiledEventArgs e)
        {
            //打印产地和型号,只打印一次
            if (!isDisplayedType)
            {
                Console.WriteLine("{0} - {1}",e.Area,e.Type);
            }
            if (e.Temprature<100)
            {
                Console.WriteLine(String.Format("Alarm".PadRight(7)+": 水快开了,当前温度: {0}"),e.Temprature);
            }
            else
            {
                Console.WriteLine(string.Format("Alarm".PadRight(7)+": 水已经开了!!!"));
            }
        }
    }

可以看到,Update()方法中,通过传递进来的BoiledEventArgs参数,可以获得事件进展(温度),以及事件触发者的信息(产地和型号).

 

接下来就是看看这个BoiledEventArgs是如何传递给Update()方法的.看下SubjectBase基类和热水器Heater需要怎样修改:

    public abstract class SubjectBase : IObservable
    {
        //使用一个List<T>作为IObserver引用的容器
        private List<IObserver> container = new List<IObserver>();
        public void Register(IObserver obj)
        {
            container.Add(obj);
        }
 
        public void UnRegister(IObserver obj)
        {
            container.Remove(obj);
        }
        protected virtual void Notify(BoiledEventArgs e)//通知所有注册了的Observer
        {
            foreach (IObserver observer in container)
            {
                observer.Update(e);
            }
        }
        
    }
    public class Heater : SubjectBase
    {
        private string type;//型号
        private string area;//产地
        private int temprature;   //水温
        public Heater(string type, string area)
        {
            this.type = type;
            this.area = area;
            temprature = 0;
        }
        public string Type
        {
            get
            {
                return type;
            }
        }
 
        public string Area
        {
            get
            {
                return area;
            }
        }
 
        public Heater() : this("中国台湾", "飞天一号") { }
 
        //供子类覆盖,以便子类拒绝被通知,或添加额外行为
        protected virtual void OnBoiled(BoiledEventArgs e)
        {
            base.Notify(e);//调用父类的Notify()方法,今儿调用所有注册了Observer的Update()方法
        }
        public void BoilWater()//烧水
        {
            for (int i = 0; i < 100; i++)
            {
                temprature = i + 1;
                if (temprature > 97)//当水快烧开时(温度>97度),通知Observer
                {
                    BoiledEventArgs e = new BoiledEventArgs(temprature, type, area);
                    OnBoiled(e);
                }
            }
        }
 
    }


测试代码如下:

    public abstract class SubjectBase : IObservable
    {
        //使用一个List<T>作为IObserver引用的容器
        private List<IObserver> container = new List<IObserver>();
        public void Register(IObserver obj)
        {
            container.Add(obj);
        }
 
        public void UnRegister(IObserver obj)
        {
            container.Remove(obj);
        }
        protected virtual void Notify(BoiledEventArgs e)//通知所有注册了的Observer
        {
            foreach (IObserver observer in container)
            {
                observer.Update(e);
            }
        }
        
    }
    public class Heater : SubjectBase
    {
        private string type;//型号
        private string area;//产地
        private int temprature;   //水温
        public Heater(string type, string area)
        {
            this.type = type;
            this.area = area;
            temprature = 0;
        }
        public string Type
        {
            get
            {
                return type;
            }
        }
 
        public string Area
        {
            get
            {
                return area;
            }
        }
 
        public Heater() : this("中国台湾", "飞天一号") { }
 
        //供子类覆盖,以便子类拒绝被通知,或添加额外行为
        protected virtual void OnBoiled(BoiledEventArgs e)
        {
            base.Notify(e);//调用父类的Notify()方法,今儿调用所有注册了Observer的Update()方法
        }
        public void BoilWater()//烧水
        {
            for (int i = 0; i < 100; i++)
            {
                temprature = i + 1;
                if (temprature > 97)//当水快烧开时(温度>97度),通知Observer
                {
                    BoiledEventArgs e = new BoiledEventArgs(temprature, type, area);
                    OnBoiled(e);
                }
            }
        }
 
    }

 

Observer中的拉模式

 

先看代码:

 

namespace ConsoleApp3
{
    public interface IObservable
    {
        void Register(IObserver obj);//注册IOberver
        void UnRegister(IObserver obj);//取消IObserver的注册
    }
    public interface IObserver
    {
        void Update(IObservable sender);
    }
    public abstract class SubjectBase : IObservable
    {
        //使用一个List<T>作为IObserver引用的容器
        private List<IObserver> container = new List<IObserver>();
        public void Register(IObserver obj)
        {
            container.Add(obj);
        }
 
        public void UnRegister(IObserver obj)
        {
            container.Remove(obj);
        }
        protected virtual void Notify(IObservable obj)//通知所有注册了的Observer
        {
            foreach (IObserver observer in container)
            {
                observer.Update(obj);
            }
        }
 
    }
    public class Heater : SubjectBase
    {
        private string type;//型号
        private string area;//产地
        private int temprature;   //水温
        public Heater(string type, string area)
        {
            this.type = type;
            this.area = area;
            temprature = 0;
        }
        public string Type
        {
            get
            {
                return type;
            }
        }
 
        public string Area
        {
            get
            {
                return area;
            }
        }
 
        public int Temprature
        {
            get
            {
                return temprature;
            }
        }
 
        public Heater() : this("中国台湾", "飞天一号") { }
 
        //供子类覆盖,以便子类拒绝被通知,或添加额外行为
        protected virtual void OnBoiled()
        {
            base.Notify(this);//调用父类的Notify()方法,今儿调用所有注册了Observer的Update()方法
        }
        public void BoilWater()//烧水
        {
            for (int i = 0; i < 100; i++)
            {
                temprature = i + 1;
                if (temprature > 97)//当水快烧开时(温度>97度),通知Observer
                {
                    OnBoiled();
                }
            }
        }
 
    }
    public class Screen : IObserver
    {
        private bool isDisplayedType = false;
        void IObserver.Update(IObservable obj)
        {
            //这里存在一个向下转换(由继承体系中高级别的类向低级别的类转换)
            Heater heater = (Heater)obj;
 
            //打印产地和型号,只打印一次
            if (!isDisplayedType)
            {
                Console.WriteLine("{0} - {1}: ",heater.Area,heater.Type);
                Console.WriteLine();
                isDisplayedType = true;
            }
            if (heater.Temprature<100)
            {
                Console.WriteLine(string.Format("Alarm".PadRight(7)+" : 水快开了,当前温度:{0} .",heater.Temprature));
            }
            else
            {
                Console.WriteLine(string.Format("Alarm".PadRight(7)+": 水已经烧开了"));
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Heater heater = new Heater();
            Screen screen = new Screen();
            heater.Register(screen);//注册显示器
            heater.BoilWater();//烧水
        }
    }
}

 

应该能看出来,和前面的输出完全一样.

不对代码进行讲解了,如果不明白可以留言,最近一段时间,我发现我的写的东西连我自己都看不下去了,废话太多了.有点受不了了.

 

简单的总结一下推模式和拉模式的区别:

推模式好处是按需供给,想要提供给Observer端什么数据,就将这些数据封装称对象,传递给Observer,缺点是需要创建自定义个EventArgs对象.

 

拉模式的好处则是不需要另外定义对象,直接将对象的引用传递进去就可以了.但是缺点是可能会需要暴露我们不想暴露的内部成员,比如本例中的temperature,我们期望将它作为类的内部数据,仅提供给显示器.但是由于使用了拉模式,必须为它再提供一个公共的Temperature访问器,这样在程序的其他地方也可以访问到了temperature,比如说在Program.除了这些,我们不期望Screen可以进行烧水这一动作,但是由于它获得了Heater的引用,BoilWater()方法有事public,所以在Update()方法中也具备了对热水器操作的能力,比如调用BoilWater()方法.

 

小结

关于委托和事件的东西就将这么多了,内容不少,没必要都明白,就算楼主也没有都弄得很明白,你只需要在用的时候能找到就行了.