观察者模式经典例子

现在很多程序员在面试的时候都遇到过这个问题---<猫叫了,老鼠跑了,主人醒了...>,实现一个连动效果,我也遇到过,感觉这道面试题目挺经典的,挺考验面向对象设计(OOD)的能力,虽然是个很简单的例子,但要考虑到程序的扩展性。比如说有新的需求,要求后面再加上狗叫了,那些写的过死且繁琐的代码就要来次大地震了;再比如说又变需求了,猫叫了是因为被跳蚤咬的,那跳蚤就成为了导火线,就算是用事件和接口写出的扩展性很强的程序,也会有点蔫了......

       这么一连串的反应是由一个行为所引起的,或者是猫叫,亦或者是一个按钮的点击引发,如何能让这一连串反映的扩展性更强,能更坚强的面对新的需求。这就需要一个更佳的思路,更佳的设计模式,今天无意想起了这个问题,也根据我的思路写了一套模式,下面就详细的说下我的想法:

       无论是猫叫,还是老鼠跑,都是一个行为,我们把这个行为抽象出一个基类:


namespace NewCatAndMouse
{
     public abstract class BaseObject
     {
         private string name;

        public string Name
         {
             get { return name; }
             set { name = value; }
         }

         /// <summary>
         /// 抽象出的行为
         /// </summary>
        public abstract void Action();
     }
}


      现在我们再建一个中间层,用来处理这些行为:

namespace NewCatAndMouse
{
     public class ActionHandle
     {
         private BaseObject manager;

         public ActionHandle(BaseObject manager,string name)
         {
             this.manager = manager;
             this.manager.Name = name;
         }

         /// <summary>
         /// 执行
         /// </summary>
         public void Execute()
         {
             this.manager.Action();
         }
     }
}

       现在我们一一实现猫、老鼠和主人(从基类继承):

namespace NewCatAndMouse
{
   public class Cat:BaseObject
      {
        public override void Action()
         {
            Console.Write(this.Name+"(猫)大吼一声!"+"/n");
         }
     }
}


namespace NewCatAndMouse
{
    public class Mouse:BaseObject
     {
        public override void Action()
         {
             Console.Write(this.Name+"(老鼠)仓惶逃跑!"+"/n");
         }
     }
}


namespace NewCatAndMouse
{
     public class Master:BaseObject
     {
          public override void Action()
         {
             Console.Write(this.Name+"(主人)猛然惊醒!" + "/n");
         }
     }
}


三个实现类完成了。现在一一实例化,组合调用?不,那样客户端会显的臃肿而丑陋,有人说:代码是门技术,更是门艺术。所以我们的客户端代码应当越简洁越好。所以,我们把需要的东西写在配置文件里:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <connectionStrings>
      <add name="AssemblyName" connectionString="NewCatAndMouse"/>
   </connectionStrings>
   <appSettings>
     <add key="Cat" value="Tom"/>
     <add key="Mouse" value="Jerry"/>
    <add key="Master" value="Bob"/>
   </appSettings>
</configuration>


然后我们再做一个类来处理配置文件:

namespace NewCatAndMouse
{

     public class SubjectAggregate
     {
         private static List<string> list = new List<string>();
         /// <summary>
         /// 将配置文件里的所有键读入集合,并返回
         /// </summary>
         public static List<string> GetAllObject()
         {
             foreach(string key in ConfigurationManager.AppSettings.AllKeys)
             {
                 list.Add(key);
             }

             if (list.Count < 1)
             {
                 return null;
             }
             else
             {
                 return list;
             }
         }
        
     }
}

       刚才说为了客户端的干净整洁,不要把过多的实例化放在客户端,所以我们就用反射来实例化类:

namespace NewCatAndMouse
{
     public class ReflectionObject
     {

         private static string assemblyName = System.Configuration.ConfigurationManager.ConnectionStrings["AssemblyName"].ConnectionString;
        
         /// <summary>
         /// 通过反射返回指定的类的实例
         /// </summary>
         /// <param name="key"></param>
         /// <returns></returns>
         public static BaseObject GetObject(string className)
         {
             return (BaseObject)Assembly.Load(assemblyName).CreateInstance(assemblyName+"."+className);
         }
     }
}

       下面就是客户端代码了:

namespace NewCatAndMouse
{
     class Program
    {
         static void Main(string[] args)
         {
             List<string> list = SubjectAggregate.GetAllObject();
             if (list != null)
             {
                 for (int i = 0; i < list.Count; i++)
                 {
                     ActionHandle handle = new ActionHandle(ReflectionObject.GetObject(list[i]),System.Configuration.ConfigurationManager.AppSettings[list[i]].ToString());
                     handle.Execute();
                 }
             }
             else
             {
                 Console.Write("Not fount object!");
             }
             Console.Read();
         }
     }
}

这样就可以了,如果需要新的子类直接继承基类,再在配置文件添加一个子类属性就可以了。而且可以再配置文件里自由的组合而无需改动客户端的代码,符合了开放--封闭原则。

         但由于用了反射,所以性能会有些差。而且如果实现类需要有新功能,就得在基类添加,如果功能太多基类就会变的臃肿不堪。所以,它也是有局限性的,最好是派生类不多而且行为较为统一。

 

用事件来实现:

猫类:定义一个猫叫事件;

老鼠类:订阅猫叫事件,在猫发出叫声这个事件后,老鼠逃跑;

主人类:类似于老鼠类,在猫发出叫声这个事件后,主人醒来;

猫类实现如下:

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

namespace CarCry
{
    
/// <summary>
    
/// 猫类的定义
    
/// </summary>
    public class Cat
    {
        
//猫名
        private string _name;
        
//猫叫事件
        public event EventHandler<CatCryEventArgs> CatCryEvent;
        
/// <summary>
        
/// 构造函数
        
/// </summary>
        
/// <param name="name">名字参数</param>
        public Cat(string name)
        {
            _name 
= name;
        }

        
/// <summary>
        
/// 促发猫叫的事件
        
/// </summary>
        public void CatCry()
        {
            CatCryEventArgs args 
= new CatCryEventArgs(_name);
            Console.WriteLine(args);
            CatCryEvent(
this,args);
        }

    }

    
/// <summary>
    
///  猫叫事件参数
    
/// </summary>
    public class CatCryEventArgs:EventArgs
    {
        
//发出叫声的猫的名字
        private string _catname;
        
/// <summary>
        
/// 构造函数
        
/// </summary>
        public CatCryEventArgs(string catname):base()
        {
            _catname 
= catname;
        }

        
/// <summary>
        
///  输出参数内容
        
/// </summary>
        public override string ToString()
        {
            
return "猫 "+ _catname + " 叫了";
        }

    }
}

 

老鼠类实现如下:

 

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

namespace CarCry
{
    
public class Mouse
    {
        
//老鼠名字
        private string _name;
        
/// <summary>
        
/// 构造函数
        
/// </summary>
        
/// <param name="name">老鼠的名字</param>
        
/// <param name="cat">发出叫声的猫</param>
        public Mouse(string name,Cat cat)
        {
            _name 
= name;
            cat.CatCryEvent 
+= CatCryHandle;//订阅猫叫事件
        }

        
/// <summary>
        
/// 猫叫事件处理
        
/// </summary>
        
/// <param name="sender"></param>
        
/// <param name="args"></param>
        private void CatCryHandle(object sender,CatCryEventArgs args)
        {
            Run();
        }

        
/// <summary>
        
/// 逃跑方法
        
/// </summary>
        private void Run()
        {
            Console.WriteLine(
"老鼠 " + _name + " 逃跑了");
        }
    }
}

 

主人类实现如下:

 

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

namespace CarCry
{
    
public class Master
    {
        
//主人名字
        private string _name;

        
/// <summary>
        
/// 构造函数,订阅事件
        
/// </summary>
        
/// <param name="name">主人名字</param>
        
/// <param name="cat"></param>
        public Master(string name,Cat cat)
        {
            _name 
= name;
            cat.CatCryEvent 
+= CatCryHandler;//订阅猫叫事件
        }

        
/// <summary>
        
/// 猫叫事件处理
        
/// </summary>
        
/// <param name="sender"></param>
        
/// <param name="args">猫叫事件</param>
        private void CatCryHandler(object sender,CatCryEventArgs args)
        {
            WakeUp();
        }

        
/// <summary>
        
/// 主人醒了事件
        
/// </summary>
        private void WakeUp()
        {
            Console.WriteLine(
"主人 "+_name+" 醒了") ;
        }

    }
}

 

主函数的调用如下:

 

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

namespace CarCry
{
    
class MainClass
    {
        
static void Main(string[] args)
        {
            Console.WriteLine(
"开始模拟");
            Cat cat 
= new Cat("Tom");
            Mouse mouse1 
= new Mouse("Jack", cat);
            Mouse mouse2 
= new Mouse("jackson",cat);
            Master master 
= new Master("Tao", cat);
            Master master2 
= new Master("Hong",cat);
            cat.CatCry();
            Console.ReadLine();
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章