使用面向對象的思想 用c#控制台代碼模擬貓抓老鼠
我們先來分析一下貓抓老鼠的過程
1.貓叫了
2.所有老鼠聽到叫聲,知道是哪只貓來了
3.老鼠們逃跑,邊逃邊喊:"xx貓來了,快跑啊!我是老鼠xxx"
一 雙向耦合的代碼
首先需要一個貓類Cat 一個老鼠類Rat 和一個測試類Program
老鼠類的代碼如下
//老鼠類 public class Rat { public string Name { get; set; } //老鼠的名字 public Cat MyCat { get; set; } //老鼠遇到的貓 //老鼠逃跑的方法 public void Run() { Console.WriteLine(MyCat.Name + "貓來了,大家快跑!!我是" + Name); //打印出信息 包含了貓的名字和老鼠本身的名字 } //帶參和無參構造 public Rat() { } public Rat(string name, Cat cat) { this.Name = name; this.MyCat = cat; } }
要讓貓叫的時候依次打印出老鼠的逃跑方法,需要在Cat類里添加一個存放Rat對象的集合
Cat類的代碼如下
public class Cat { public string Name { get; set; } //貓的名字 List<Rat> list = new List<Rat>(); //存放Rat對象的集合 //為集合增加老鼠 public void Add(Rat rat) { list.Add(rat); } //移除 public void Remove(Rat rat) { list.Remove(rat); } //貓叫的方法 public void Shout() { Console.WriteLine("喵喵喵!"); //如果集合非空,循環執行每只老鼠的Run()方法 if (list != null) { foreach (Rat item in list) { item.Run(); } } } public Cat() { } public Cat(string name) { this.Name = name; } }
在Main方法中,我們需要構建幾個Rat對象和一個Cat對象,將Rat對象添加到Cat對象的集合中,調用Cat對象的Shout方法
代碼如下
static void Main(string[] args) { //構建一個Cat對象和兩個Rat對象 老鼠遇到的貓是構建的cat Cat cat = new Cat("Tom"); Rat rat1 = new Rat("Jerry", cat); Rat rat2 = new Rat("TaoQi", cat); //調用貓類的Add方法添加老鼠對象 cat.Add(rat1); cat.Add(rat2); //調用貓的Shout方法 cat.Shout(); Console.ReadKey(); }
運行結果如下
這樣的代碼缺陷很明顯,Cat類和Rat類緊密耦合
貓可能不止要抓老鼠 還要抓小鳥
當然不止是貓會抓 也可能狗拿耗子多管閑事
於是我們可以把貓和狗提出來 繼承自一個抽象類Pet
抓捕的小動物老鼠和小鳥沒有什么關系 但是都能(逃)跑
先不去管小鳥是飛,我們把它們稱作 可以跑的 都實現一個IRunable接口
二 觀察者 模式(發布-訂閱模式)
修改后的代碼如下
新增抽象類Pet ,貓類繼承自Pet (貓類的代碼變化不大 略去不寫
public abstract class Pet { public List<IRunable> list = new List<IRunable>(); public void Add(IRunable i) { list.Add(i); } public void Remove(IRunable i) { list.Remove(i); } public abstract void Shout(); }
接口IRubable 里面定義一個Run方法
public interface IRunable
{
void Run();
}
老鼠Rat和鳥Bird兩個類都實現了這個接口
以Bird為例 代碼如下
class Bird : IRunable { //鳥的名字和遇到的貓 public string Name { get; set; } public Cat MyCat { get; set; } public void Run() { Console.WriteLine(MyCat.Name + "貓來了,快跑吧!我是小鳥" + Name); } public Bird() { } public Bird(string name, Cat cat) { this.Name = name; this.MyCat = cat; } }
Rat類的代碼幾乎沒有變化
那么在Main方法中也只需要稍作修改,增加一個Bird對象 略去不寫 執行后的結果如下
以上貓抓老鼠的例子實際上就是用了一個設計模式:觀察者模式
觀察者模式又名發布-訂閱模式(Publish-Subscribe)
1.Subject類 (通知者 主題)
//抽象類 里面有一個Observer類集合
把所有對觀察者對象的引用保存在一個聚集里,每個主題都可以有多個觀察者.抽象主題提供一個接口,可以增加和刪除觀察着對象
2.Observer類 (觀察者)
//抽象類
抽象觀察者,為所有的具體觀察者定義一個接口,在得到主題的更新時提醒自己
3.ConcreteObserver類
//父類是Observer類
具體觀察者,實現抽象觀察者角色所需求的更新接口,以便使本身的狀態與主題的狀態相協調
4.ConcreteSubject類
//父類是Subject
具體主題,將有關狀態存入具體觀察者對象,在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知
觀察者模式的特點
1.一個主題可以有任意數量依賴他的觀察者,一旦主題的狀態發生改變,所有觀察者都可以得到通知
2.主題發出通知不需要知道具體觀察者
3.具體觀察者也不需要知道其他觀察者的存在
但是
將一個系統分割成一系列相互作用的類有一個很不好的副作用,就是需要維護相關對象間的一致性,使得各個類緊密耦合,這樣會給維護,擴展和重用都帶來不便
應用時機:
當一個對象的改變需要同時改變其他對象的時候使用觀察模式
不知道具體有多少對象有待改變時,應考慮使用觀察者模式
一個抽象模型有兩個方面,其中一個方面依賴於另一個方面
三 委托
舉個栗子 正如之前所說,老鼠會跑Run,小鳥會飛Fly,這根本是兩個毫不相干的方法
但是的確有相同點--它們的返回值類型都是空,傳進的參數列表也都為空
我們怎么樣能把這兩個不相關的類Bird和Rat的對象都裝到Cat中去,再判斷是哪個類依次調用它們的方法?
其實我們可以直接拿出它們的方法來裝到Cat中去.
//委托
委托是什么?
和類一樣是一種用戶自定義類型
委托提供了方法的抽象
委托存儲的是一系列具有相同簽名和返回值類型的方法的地址,調用委托的時候,委托包含的所有方法將被執行
1.定義委托
訪問修飾符 delegate 返回值類型 委托名(參數列表);
public delegate void MyDel(int x);
2.聲明和初始化
委托名 委托對象名;
委托對象名=new 委托名(類名.方法名);
MyDel del; del=new MyDel(Cat.Shout);
委托名=方法名
del=Cat.Shout;
3.委托的運算
委托可以使用額外的運算符來組合.這個運算最終會創建一個新的委托,其調用列表是兩個操作數的委托調用列表的副本連接.委托是恆定的,操作數委托創建后不會被改變,委托組合拷貝的是操作數的副本
MyDel del2=Cat.Catch;
MyDel del3=del+del2;
使用+=為委托新增方法
使用-=為委托移除方法
del+=Cat.Catch;
del-=Cat.Shout;
4.委托調用
if(del!=null){ del();//委托調用 }
5.匿名方法
匿名方法是在初始化委托時內聯聲明的方法
delegate(參數){代碼塊}
delegate int MyDel(int x); MyDel del=delegate(int x){ return x; };
//匿名方法不聲明返回值
Lambda表達式
用來簡化匿名方法的語法
MyDel del1 = (int x) => {return x;};
在參數列表和匿名方法之間放置Lambda運算符=>
Lambda運算符讀作 goes to
MyDel del1 = x => {return x}; MyDel del1 = x => x;
定義委托像定義枚舉一樣,可以定義在類的外部
然后在類中可以創建委托的對象
比如 修改后的我們的程序
(只是一個測試 不寫的很詳細了 還是拿貓和老鼠舉例子 貓類和老鼠類 以及定義的委托的代碼如下)
//定義一個委托 名叫CatShoutEventHandler 沒有返回值 參數列表為空 public delegate void CatShoutEventHandler(); //貓類 public class Cat { //在貓類里定義一個該委托的對象CatShout public CatShoutEventHandler CatShout; public void Shout() { Console.WriteLine("喵喵喵"); //判斷委托內是否為空,若不為空,執行該委托 if (CatShout != null) { CatShout(); } } } //老鼠類 public class Rat { public string Name { get; set; } //老鼠的名字 //老鼠的逃跑方法 public void Run() { Console.WriteLine(Name + "跑了!"); } //無參和帶參構造 public Rat() { } public Rat(string name) { this.Name = name; } }
那么接下來就是我們的Main方法
static void Main(string[] args) { //構建一個貓對象和兩個老鼠對象 Cat cat = new Cat(); Rat rat1 = new Rat("Jerry"); Rat rat2 = new Rat("TaoQi"); //向cat的委托對象CatShout中依次添加老鼠對象的Run方法 //注!!添加的是整個方法 不需要加括號 cat.CatShout += rat1.Run; cat.CatShout += rat2.Run; //調用cat的Shout方法 cat.Shout(); Console.ReadKey(); }
運行結果是這樣的
然而 然而 然而
比如說我來做一個很賤的操作
在Main方法中來一個 cat.CatShout=null;
好了 不管Cat類中的CatShout有沒有初始值或者有沒有賦值過都沒了
我們知道面向對象的的三大特征 封裝,繼承,多態
我們一樣可以把委托封裝起來,以控制它的訪問權限
這就是事件
四 事件
事件(Event)是一個用戶操作,或是一些特定的出現情況.應用程序需要在事件發生時響應事件
事件在類中聲明且生成,且通過使用同一個類或者其他類中的委托與時間處理程序關聯
聲明事件
在類的內部聲明事件,首先必須聲明該事件的委托類型
public delegate void CatShoutEventHandler();
然后聲明事件本身,使用 event 關鍵字
public event CatShoutEventHandler CatShout;
上面的代碼定義了一個名為CatShoutEventHandler的委托和一個CatShout的事件,該事件在生成時會調用委托
聲明事件后可以實例化事件,注冊函數到事件解除事件函數注冊方法
CatShout+=new CatShoutEventHandler(rat.Run); CatShout+=rat2.Run;//將函數Run注冊到事件CatSHout上
在使用事件的時候,如果在封裝類的外部,則該事件只能出現在+=或-=的左邊
所以 用事件封裝過的代碼我們再來一遍
額..好像順理成章的就寫了下去
代碼就這里變了一行 唯一的變化就是多了個event關鍵字
我們在這里使用了一個高大上的工具 reflector
將我們的代碼反編譯之后,可以看到CatShout這個事件其實做了兩件事
里面有兩個方法 一個是add_CatShout(CatShoutEventHandler) 另一個是remov_Cat(CatShoutEventHandler)
分別對應這個事件的運算符號 += 和 -=
所以其實事件就是相當於對委托的封裝
五 Object sender和EventArgs e
然而我又要找問題了,我們翻回去看要求
有一點是老鼠知道貓的名字,要調用貓對象的Name屬性,我們現在試着給貓加上這個屬性
public string Name { get; set; }
我們要排除Cat類與Rat類的耦合,所以不能在Rat類中存放一個Cat對象
當然我們可以在老鼠的Run方法中增加傳進去一個Cat對象,但是這樣需要定義一個這個程序自己使用的委托類型
系統已經有一些定義好的委托類型
public delegate void Action(); public delegate void EventHandler(object sender, EventArgs e);
第一個委托叫Action 它是沒有參數 沒有返回值類型的
第二個叫做EventHandler 它有兩個參數 Object類型的sender 和 EventArgs類型的e
第一個還好,第二個,我去,這是什么東西啊?
其實第二個這個委托類型我們都十分熟悉 因為在winForm窗體應用程序中,控件生成的方法就帶着這兩個參數
我們一般把觸發事件的整個對象封裝成Object類型 做第一個參數
而第二個參數呢 我們首先需要知道什么是EventArgs
[Serializable, ComVisible(true), __DynamicallyInvokable] public class EventArgs { [__DynamicallyInvokable] public static readonly EventArgs Empty = new EventArgs(); }
這是代碼 首先我們知道了,這是個類
是系統定義的類,里面只有一個靜態的readonly的自身變量 為空
也就是通過調用這個靜態方法來返回一個新的(空的)EventArgs本身
要弄懂它,我們來先看一個例子
這是WinForm窗體中的TreeView的事件對應在窗體類中生成的方法
private void tvList_AfterSelect(object sender, TreeViewEventArgs e)
我們可以看到 誒 誒 里面傳進去的第一個參數沒錯是object sender 然而第二個變成了TreeViewEventArgs類型的e
那這個TreeViewEventArgs是什么東西呢
首先沒錯的是它繼承自EventArgs
然后它加多了許多方法,我們知道的是它可以返回我們當前選中的那個節點(而sender則代表的是整個TreeView控件)
sa,我們可以把要用到的一些屬性封裝到EventArgs之中 來排除兩個類的耦合
應用到貓和鼠的例子中去,我們最后的代碼是這樣子的
//自定義的CatShoutEventArgs類,繼承自EventArgs //用作保存Cat對象的Name屬性,還可以擴展其他的功能 public class CatShoutEventArgs : EventArgs { public string CatName { get; set; } public CatShoutEventArgs(string name) { this.CatName = name; } } //定義一個委托 名叫CatShoutEventHandler public delegate void CatShoutEventHandler (object sender,CatShoutEventArgs e); //貓類 public class Cat { public string Name { get; set; } //貓的名字 //在貓類里定義一個事件CatShout,返回值類型是定義的委托 public event CatShoutEventHandler CatShout; public void Shout() { Console.WriteLine("喵喵喵"); //判斷委托內是否為空,若不為空,執行該委托 if (CatShout != null) { //new一個CatShoutEventArgs類,傳入參數是自身的Name CatShoutEventArgs e = new CatShoutEventArgs(Name); //執行CatShout事件,傳入自身和e CatShout(this, e); } } //無參和帶參構造 public Cat() { } public Cat(string name) { this.Name = name; } } //老鼠類 public class Rat { public string Name { get; set; } //老鼠的名字 //老鼠的逃跑方法 public void Run(object sender, CatShoutEventArgs e) { //打出一句話,包括了貓的名字和老鼠的名字 Console.WriteLine(e.CatName + "來了! " + Name + "跑了!"); } //無參和帶參構造 public Rat() { } public Rat(string name) { this.Name = name; } }
嗯 然后Program類中的Main方法並不需要改動
為了視覺效果,我再放上來一遍
static void Main(string[] args) { //構建一個貓對象和兩個老鼠對象 Cat cat = new Cat("Tom"); Rat rat1 = new Rat("Jerry"); Rat rat2 = new Rat("TaoQi"); //向cat的委托對象CatShout中依次添加老鼠對象的Run方法 //注!!添加的是整個方法 不需要加括號 cat.CatShout += rat1.Run; cat.CatShout += rat2.Run; //調用cat的Shout方法 cat.Shout(); Console.ReadKey(); }
好了 好了 最后的最后 看看我們程序的運行效果吧
by天命 2016.11.9