觀察者模式經典例子

現在很多程序員在面試的時候都遇到過這個問題---<貓叫了,老鼠跑了,主人醒了...>,實現一個連動效果,我也遇到過,感覺這道面試題目挺經典的,挺考驗面向對象設計(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();
        }
    }
}

 

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