面试题:猫叫、老鼠跑、人醒的一点看法
这些天一直在面试中,做着不同的面试题,唉,悲催
上周做的一道面试题今天正好出现在园里的首页,看了一下这位同学的实现,基本上方向是对的,就是代码上细节没有注意,有一些错误,这里我就写一下我的理解
---------------------------
C#面试题:
猫大叫一声,所有的老鼠都开始逃跑,主人被惊醒。
要求:
1、要有联动性,老鼠和人的行为是被动的
2、考虑可扩展行,猫叫声可能会引起其他联动效应
-------------
步骤1:先提取出对象,猫、老鼠、人(Cat,Mouse,Person)
步骤2:再提取对象的动作,猫叫、老鼠跑、人醒(Cat.Call,Mouse.Run,Person.Wake)
步骤3:关联关系,
这里有两种理解
1) 猫叫->老鼠跑
猫叫->人醒
2) 猫叫->老鼠跑->人醒(多级联动)
步骤4:很明显,对应c#这里可以直接使用委托链和事件实现
另外可以使用设计模式中的观察者模式
------------------------------------------
以下分别给出对应代码:
观察者模式:这里的老鼠有两种实现方式,对应步骤3中的关联关系
声明接口
1 //观察者 2 public interface IObserver 3 { 4 void Update(ISubject sub); 5 }
1 //被观察者 2 public interface ISubject 3 { 4 void Notify(); 5 void AddObserver(IObserver o); 6 void RemoveObserver(IObserver o); 7 }
1 //猫 2 public class Cat:ISubject 3 { 4 //缓存自己的观察者 5 private System.Collections.Generic.List<IObserver> _list; 6 public Cat() 7 { 8 _list=new System.Collections.Generic.List<IObserver>(); 9 10 } 11 //通知所有自己得观察者 12 public void Notify() 13 { 14 foreach(IObserver o in _list) 15 { 16 o.Update(this); 17 } 18 } 19 public void AddObserver(IObserver o) 20 { 21 _list.Add(o); 22 } 23 public void RemoveObserver(IObserver o) 24 { 25 _list.Remove(o); 26 } 27 //猫叫的时候通知观察者 28 public void Call() 29 { 30 System.Console.WriteLine("Cat Call"); 31 Notify(); 32 } 33 }
1 //这里对应步骤3中关联关系的第一种理解 2 //猫叫->老鼠跑 3 //猫叫->人醒 4 5 public class Mouse:IObserver 6 { 7 public void Update(ISubject sub) 8 { 9 Run(); 10 } 11 public void Run() 12 { 13 System.Console.WriteLine("Mouse Run"); 14 } 15 }
1 //这里对应步骤3中关联关系的第二种理解 2 //猫叫->老鼠跑->人醒 3 //这里老鼠是猫的观察者,同时被人观察,所以两个接口都实现 4 public class MouseA:IObserver,ISubject 5 { 6 //实现观察者接口 7 public void Update(ISubject sub) 8 { 9 //注意,这里当猫通知老鼠后,老鼠要接着改变自己得状态,并通知自己得观察者 10 Run(); 11 12 } 13 public void Run() 14 { 15 System.Console.WriteLine("MouseA Run"); 16 //这里老鼠的跑动通知观察者 17 Notify(); 18 } 19 20 #region//实现被观察者接口 21 private System.Collections.Generic.List<IObserver> _list; 22 public MouseA() 23 { 24 _list=new System.Collections.Generic.List<IObserver>(); 25 } 26 27 public void Notify() 28 { 29 foreach(IObserver o in _list) 30 { 31 o.Update(this); 32 } 33 } 34 public void AddObserver(IObserver o) 35 { 36 _list.Add(o); 37 } 38 public void RemoveObserver(IObserver o) 39 { 40 _list.Remove(o); 41 } 42 #endregion 43 }
//人 public class Person:IObserver{ public void Update(ISubject sub) { Wake(); } public void Wake() { System.Console.WriteLine("People Wake"); } }
调用方法:
对应第一种:猫同时有两个观察者,或更多个,只需要调用AddObserver加入就行了
1 public static void Main() 2 { 3 //被观察者 4 ISubject c=new Cat(); 5 //观察者 6 IObserver m=new Mouse(); 7 IObserver p=new Person(); 8 //加入观察者 9 c.AddObserver(m); 10 c.AddObserver(p); 11 //被观察者状态改变 12 ((Cat)c).Call(); 13 }
对应第二种,猫有老鼠一个观察者,老鼠也有人一个观察者,多级联动关系
1 public static void Main() 2 { 3 //被观察者 4 ISubject c=new Cat(); 5 //既是观察者,也是被观察者 6 IObserver m=new MouseA(); 7 //加入观察者 8 c.AddObserver(m); 9 10 //观察者 11 IObserver p=new Person(); 12 //加入观察者 13 ((ISubject)m).AddObserver(p); 14 //被观察者状态改变 15 ((Cat)c).Call(); 16 }
设计模式的使用最大的好处是,解耦合,
这位同学的观察者模式用的是没有错误的,但是代码中有问题,主要是Mouse和Peolple的构造函数中的参数,直接使用了Cat这个对象,这样就使得Mouse、Peolple与Cat绑死在一起,这里的Cat应该换成接口Subject
-----------------------------以下使用事件和委托实现相同的功能,分为三种实现,第一种是单纯的类,并未解耦合,第二种使用接口解耦合,第三种使用抽象类
1 public delegate void NotifyEventHandler(object sender,EventArgs e); 2 3 public class TCat 4 { 5 public event NotifyEventHandler OnNotify; 6 public void DoOnNotify(EventArgs e) 7 { 8 if (OnNotify!=null) 9 OnNotify(this,e); 10 } 11 12 public void Call() 13 { 14 System.Console.WriteLine("TCat Call"); 15 EventArgs e=new EventArgs(); 16 DoOnNotify(e); 17 } 18 } 19 20 public class TMouse 21 { 22 private TCat _Cat; 23 public TMouse(TCat c) 24 { 25 _Cat=c; 26 c.OnNotify+=new NotifyEventHandler(OnMouseRun); 27 } 28 void OnMouseRun(object sender,EventArgs e) 29 { 30 Run(); 31 } 32 33 public void Run() 34 { 35 System.Console.WriteLine("TMouse Run"); 36 } 37 } 38 39 public class TPeople 40 { 41 private TCat _Cat; 42 public TPeople(TCat c) 43 { 44 _Cat=c; 45 c.OnNotify+=new NotifyEventHandler(OnPeopleWake); 46 } 47 void OnPeopleWake(object sender,EventArgs e) 48 { 49 Wake(); 50 } 51 public void Wake() 52 { 53 System.Console.WriteLine("TPeople Wake"); 54 } 55 }
使用方法:
1 public static void Main3() 2 { 3 TCat c=new TCat(); 4 TMouse m=new TMouse(c); 5 TPeople p=new TPeople(c); 6 c.Call(); 7 }
事件的第二种实现:
1 public interface INofify 2 { 3 event NotifyEventHandler OnNotify; 4 } 5 6 public delegate void NotifyEventHandler(object sender,EventArgs e); 7 8 public class CCat:INofify 9 { 10 public event NotifyEventHandler OnNotify; 11 public void DoOnNotify(EventArgs e) 12 { 13 if (OnNotify!=null) 14 OnNotify(this,e); 15 } 16 17 public void Call() 18 { 19 System.Console.WriteLine("CCat Call"); 20 EventArgs e=new EventArgs(); 21 DoOnNotify(e); 22 } 23 } 24 25 public class CMouse:INofify 26 { 27 private INofify _Cat; 28 public CMouse(INofify c) 29 { 30 _Cat=c; 31 c.OnNotify+=new NotifyEventHandler(OnMouseRun); 32 } 33 void OnMouseRun(object sender,EventArgs e) 34 { 35 Run(); 36 } 37 38 public void Run() 39 { 40 System.Console.WriteLine("CMouse Run"); 41 EventArgs e=new EventArgs(); 42 DoOnNotify(e); 43 } 44 45 public event NotifyEventHandler OnNotify; 46 public void DoOnNotify(EventArgs e) 47 { 48 if (OnNotify!=null) 49 OnNotify(this,e); 50 } 51 } 52 53 public class CPeople 54 { 55 private INofify _Cat; 56 public CPeople(INofify c) 57 { 58 _Cat=c; 59 c.OnNotify+=new NotifyEventHandler(OnPeopleWake); 60 } 61 void OnPeopleWake(object sender,EventArgs e) 62 { 63 Wake(); 64 } 65 public void Wake() 66 { 67 System.Console.WriteLine("CPeople Wake"); 68 } 69 }
使用方法:
1 public static void Main() 2 { 3 INofify c=new CCat(); 4 INofify m=new CMouse(c); 5 CPeople p=new CPeople(m);//注意,这里的Peolple并未实现INotify接口,如果需要实现步骤3中的更多级级联,请实现这个接口 6 ((CCat)c).Call(); 7 }
事件的第三种实现:
1 ////Notify 2 public abstract class Notify 3 { 4 public event NotifyEventHandler OnNotify; 5 public void DoOnNotify(EventArgs e) 6 { 7 if (OnNotify!=null) 8 OnNotify(this,e); 9 } 10 //这里是缓存被观察者 11 protected Notify _subject; 12 13 public Notify(Notify c) 14 { 15 if (c!=null) 16 { 17 _subject=c; 18 _subject.OnNotify+=new NotifyEventHandler(Do); 19 } 20 } 21 public abstract void Do(object sender,EventArgs e); 22 23 } 24 25 public class ACat:Notify 26 {//注意,这里由于继承于抽象类,所以必须要由一个Do函数的实现,函数体为空就可以了,要么就把抽象类的抽象函数改成virtual的 27 public ACat(Notify c):base(c) 28 { 29 } 30 public void Call() 31 { 32 System.Console.WriteLine("ACat Call"); 33 EventArgs e=new EventArgs(); 34 DoOnNotify(e); 35 } 36 } 37 38 public class AMouse:Notify 39 { 40 public AMouse(Notify c):base(c) 41 { 42 } 43 public override void Do(object sender,EventArgs e) 44 { 45 Run(); 46 } 47 public void Run() 48 { 49 System.Console.WriteLine("AMouse Run"); 50 EventArgs e=new EventArgs(); 51 DoOnNotify(e); 52 } 53 } 54 55 public class APeople:Notify 56 { 57 public APeople(Notify c):base(c) 58 { 59 } 60 public override void Do(object sender,EventArgs e) 61 { 62 Wake(); 63 } 64 public void Wake() 65 { 66 System.Console.WriteLine("APeople Wake"); 67 } 68 69 }
使用方法:
1 public static void Main() 2 { 3 //不需要被观察者 4 Notify cat=new ACat(null); 5 //观察猫 6 Notify mouse=new AMouse(cat); 7 //观察老鼠 8 Notify people=new APeople(mouse); 9 ((ACat)cat).Call(); 10 }
以上各种代码,区别在于使用模式和事件,或者是接口和抽象类,或者是扩展性的区别,另外使用的时候都是非常简单的,几行代码而已。
还有这位同学的实现,从结果上是正确的,但是缺点也很明显
1)各个类之间毫无结构性的关联,没有充分利用面向对象的一些特性,扩展性很差
2)尤其是调用代码部分,感觉很别扭