在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
使用面向对象的思想 用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
老鼠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类 (通知者 主题) 2.Observer类 (观察者) 3.ConcreteObserver类 4.ConcreteSubject类
观察者模式的特点
1.一个主题可以有任意数量依赖他的观察者,一旦主题的状态发生改变,所有观察者都可以得到通知
但是 将一个系统分割成一系列相互作用的类有一个很不好的副作用,就是需要维护相关对象间的一致性,使得各个类紧密耦合,这样会给维护,扩展和重用都带来不便
应用时机: 当一个对象的改变需要同时改变其他对象的时候使用观察模式
三 委托
举个栗子 正如之前所说,老鼠会跑Run,小鸟会飞Fly,这根本是两个毫不相干的方法 但是的确有相同点--它们的返回值类型都是空,传进的参数列表也都为空
我们怎么样能把这两个不相关的类Bird和Rat的对象都装到Cat中去,再判断是哪个类依次调用它们的方法? 其实我们可以直接拿出它们的方法来装到Cat中去.
//委托
委托存储的是一系列具有相同签名和返回值类型的方法的地址,调用委托的时候,委托包含的所有方法将被执行
1.定义委托 public delegate void MyDel(int x);
2.声明和初始化 MyDel del; del=new MyDel(Cat.Shout);
委托名=方法名
3.委托的运算
使用+=为委托新增方法
4.委托调用 if(del!=null){ del();//委托调用 }
5.匿名方法 delegate int MyDel(int x); MyDel del=delegate(int x){ return x; }; //匿名方法不声明返回值
Lambda表达式 MyDel del1 = (int x) => {return x;}; 在参数列表和匿名方法之间放置Lambda运算符=> 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 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论