Abstract & Virtual Class的区别
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace ConsoleApplication2 7 { 8 abstract class Item 9 { 10 public abstract void Dispaly(); 11 public abstract void Use(); 12 } 13 class Chair : Item 14 { 15 private string _model; 16 public Chair(string model) 17 { 18 _model = model; 19 } 20 public override void Dispaly() 21 { 22 Console.WriteLine("Chair, model: {0}", _model); 23 } 24 public override void Use() 25 { 26 Console.WriteLine("Use Chair"); 27 } 28 29 } 30 class Potion : Item 31 { 32 private string _type; 33 public Potion(string type) 34 { 35 _type = type; 36 } 37 public override void Dispaly() 38 { 39 Console.WriteLine("Chair, model: {0}", _type); 40 } 41 public override void Use() 42 { 43 Console.WriteLine("Full Charge"); 44 } 45 } 46 class Gun : Item 47 { 48 private string _type; 49 public Gun(string type) 50 { 51 _type = type; 52 } 53 public override void Dispaly() 54 { 55 Console.WriteLine("Chair, model: {0}", _type); 56 } 57 public override void Use() 58 { 59 Console.WriteLine("Bang"); 60 } 61 } 62 class Program 63 { 64 static void Main(string[] args) 65 { 66 List<Item> _inventory = new List<Item>(); 67 _inventory.Add(new Chair("Stuff")); 68 _inventory.Add(new Potion("He1")); 69 _inventory.Add(new Gun("AK47")); 70 71 foreach (var item in _inventory) 72 { 73 item.Dispaly(); 74 item.Use(); 75 } 76 Console.ReadKey(); 77 } 78 79 static int GetInt(string prompt) 80 { 81 Console.WriteLine("{0}> ", prompt); 82 return int.Parse(Console.ReadLine()); 83 } 84 static string GetStrig(string prompt) 85 { 86 Console.WriteLine("{0}> ", prompt); 87 return Console.ReadLine(); 88 } 89 90 } 91 92 93 94 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace ConsoleApplication2 7 { 8 class Item 9 { 10 //区别于Abstract,virtual作为base需要有自己的Methord方法 11 //Abstract只声明"强迫"方法,没有{} 12 //问题2:Virtual的方法,{}里可以什么都不写?stupid 13 public virtual void Dispaly() 14 { 15 } 16 public virtual void Use() { } 17 } 18 class Chair : Item 19 { 20 private string _model; 21 public Chair(string model) 22 { 23 _model = model; 24 } 25 public override void Dispaly() 26 { 27 Console.WriteLine("Chair, model: {0}", _model); 28 } 29 public override void Use() 30 { 31 Console.WriteLine("Use Chair"); 32 } 33 34 } 35 class Potion : Item 36 { 37 private string _type; 38 public Potion(string type) 39 { 40 _type = type; 41 } 42 /* 问题3:继承Virtual的类可以选择要不要override base的方法 43 * 我希望的是继承我的item类,那么就要定义自己的"强迫"方法,要不然你就不要继承 44 45 public override void Dispaly() 46 { 47 Console.WriteLine("Chair, model: {0}", _type); 48 } 49 public override void Use() 50 { 51 Console.WriteLine("Full Charge"); 52 } 53 * */ 54 } 55 class Gun : Item 56 { 57 private string _type; 58 public Gun(string type) 59 { 60 _type = type; 61 } 62 public override void Dispaly() 63 { 64 Console.WriteLine("Chair, model: {0}", _type); 65 } 66 public override void Use() 67 { 68 Console.WriteLine("Bang"); 69 } 70 } 71 class Program 72 { 73 static void Main(string[] args) 74 { 75 Item anotherItem = new Item(); 76 //问题1:可以直接new一个新的base virtual对象, 77 //其实我们不关心instantiate item class itself 78 //我们只关心drive from it 79 80 List<Item> _inventory = new List<Item>(); 81 _inventory.Add(new Chair("Stuff")); 82 _inventory.Add(new Potion("He1")); 83 _inventory.Add(new Gun("AK47")); 84 85 foreach (var item in _inventory) 86 { 87 item.Dispaly(); 88 item.Use(); 89 } 90 Console.ReadKey(); 91 } 92 93 static int GetInt(string prompt) 94 { 95 Console.WriteLine("{0}> ", prompt); 96 return int.Parse(Console.ReadLine()); 97 } 98 static string GetStrig(string prompt) 99 { 100 Console.WriteLine("{0}> ", prompt); 101 return Console.ReadLine(); 102 } 103 104 } 105 106 107 108 }
- 理解:多态就是不同对象对同一个消息的不同相应方式。
举例:cut(消息)
医生收到cut消息后,执行动手术;
理发师收到cut消息后,执行减法;
演员收到cut消息后,停止演出。
三个不同对象,对同一个cut消息的反应都不同,这就是多态。
在C#中abstract和virtual方法都能实现多态。
1.用abstract修饰方法是抽象方法,没有方法体,该类必须声明为abstract class(抽象类)。而用virtual修饰的方法是虚方法,有方法体;
2.子类继承了抽象类,必须重写(override)父类抽象方法。而不一定要重写父类的虚方法;
3.子类只有重写的父类的虚方法后才能实现多态;
- create abstract force继承类要写自己的方法实现
- 我可以List一个Item的array,add进任何继承自item的对象,然后for loop每个执行他们每一个的强迫方法,而不用关心他们到底是那个type,我只要知道在这个array里的每一个对象都有自己的Display()和Use()方法
- Abstract类其实什么都不是,只是force我们要重写,使我们可以做“多态”语法
- Item类是把其他table,portion,gun类的共性抽出来声明
- 好处:
- 提供给其他人接口的时候,Guarantee他们必须实现了强迫方法或者属性(就像TryParse)
- 在写过1000多行代码后,提醒自己继承item的类需要强迫方法和属性
- 常见错误:
常见错误1:
对于抽象类有一个限制:它不能被实例化。也就是说在上节示例中,我们将无法创建一个 Person对象,如果你试图这样写Person person = new Person(),编译器会提示错误信息,“无法创建抽象类或接口‘MySchool.Person’的实例”。
由于无法创建抽象类的实例对象,所以只能通过它的子类来实现该类的方法,除非它的子类也是一个抽象类。
常见错误2:
抽象类不能是密封或者静态的:如果给抽象类增加密封类的访问修饰符sealed或者static,系统会提示错误,“‘MySchool.Person’:抽象类不能是密封的或静态的”,其实很容易理解,抽象类如果不被子类继承并实现它的抽象方法,便没有实际意义。
- 区别Virtual:
注释中的三个问题。
- 注意:
- Abstract类的方法不可以有Implementation
- 一定要有Abstract关键字在class前
- 继承类的”强迫“方法还是要写成override
- Abstract类里其实可以定义abstract methord/property
- Add进item的list里的Chair,Gun,Portion还是他们自己的type,它们只是知道如何应付item类
- Abstract类里还可以定义普通member或者virtual member,Gun继承类也还可以写自己的public方法(见7)
-
View Code
- 还可以继续继承Gun,再重写Display方法,注意因为Gun里移除了默认构造器,这里AK47类不再有默认构造器,所以要定义构造器(见9)
-
View Code
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace ConsoleApplication2 7 { 8 class Item 9 { 10 11 public virtual void Dispaly() 12 { 13 } 14 public virtual void Use() { } 15 } 16 class Chair : Item 17 { 18 private string _model; 19 public Chair(string model) 20 { 21 _model = model; 22 } 23 public override void Dispaly() 24 { 25 Console.WriteLine("Chair, model: {0}", _model); 26 } 27 public override void Use() 28 { 29 Console.WriteLine("Use Chair"); 30 } 31 32 } 33 class Potion : Item 34 { 35 private string _type; 36 public Potion(string type) 37 { 38 _type = type; 39 } 40 41 42 public override void Dispaly() 43 { 44 Console.WriteLine("Chair, model: {0}", _type); 45 } 46 public override void Use() 47 { 48 Console.WriteLine("Full Charge"); 49 } 50 51 } 52 class Gun : Item 53 { 54 private string _type; 55 public Gun(string type) 56 { 57 _type = type; 58 } 59 public override void Dispaly() 60 { 61 Console.WriteLine("Chair, model: {0}", _type); 62 } 63 public override void Use() 64 { 65 Console.WriteLine("Bang"); 66 } 67 public void Shoot() 68 { 69 Console.WriteLine("I shoot the gun!"); 70 } 71 } 72 class Ak47 : Gun 73 { 74 public Ak47() : base("Ak-47") 75 { 76 77 } 78 public override void Dispaly() 79 { 80 Console.WriteLine("Hahahaha"); 81 } 82 } 83 class Program 84 { 85 static void Main(string[] args) 86 { 87 Item anotherItem = new Item(); 88 89 List<Item> _inventory = new List<Item>(); 90 _inventory.Add(new Chair("Stuff")); 91 _inventory.Add(new Potion("He1")); 92 _inventory.Add(new Gun("MP-41")); 93 _inventory.Add(new Ak47()); 94 foreach (var item in _inventory) 95 { 96 Gun gun = item as Gun; 97 if (gun != null) 98 { 99 gun.Shoot(); 100 } 101 item.Dispaly(); 102 item.Use(); 103 } 104 Console.ReadKey(); 105 } 106 107 static int GetInt(string prompt) 108 { 109 Console.WriteLine("{0}> ", prompt); 110 return int.Parse(Console.ReadLine()); 111 } 112 static string GetStrig(string prompt) 113 { 114 Console.WriteLine("{0}> ", prompt); 115 return Console.ReadLine(); 116 } 117 118 } 119 120 121 122 }
- 下图代表Variable type和object type的区别。这里Inventory里都是Item,所以Item.Shoot()会有compile eroor。我们除非对该对象做特殊verify后才可以用。
Gun gun = item as Gun; if (gun != null) { gun.Shoot();
}
- 使用场合:
通过示例学习了抽象类和抽象方法的使用,那么我们在面向对象开发中应该在什么场合中使用抽象类和抽象方法呢?
由于抽象父类中提供的抽象方法,要在子类中实现。因此,我们可以这样理解,父类提供了一个功能或者规定,约束子类的行为。
例如,猫和狗继承自动物类,猫能喵喵地叫,狗能汪汪地叫,如果动物类是一个抽象类,它里面有一个抽象方法“叫”,就可以理解为:是动物就要会叫。在上节示例中,我们可以理解为,父类Person约束子类必须要实现SayHi()方法。
- 里氏替换原则
问题:在第6章中,还遗留了这样一个问题,泛型集合List<FeedBase>用于保存频道对象,为什么父类类型约束的泛型集合可以存储它子类的对象呢?如示例3所示。
1 //Profile类的修改 2 class Profile 3 { 4 public Profile() { } 5 //注意!这里存放的是FeedBase, 6 //而不是任何一个具体的Feed子类 7 public List<FeedBa8e> Feeds = new List<FeedBase>(); 8 public TimeSpan IntervalToRefresh; 9 } 10 //具体的频道对象信息参考第5章 11 AtomFeed gnews = new AtomFeed("Google 新闻", ...); 12 RssFeed sinanews = new RssFeed("新浪新闻", ...); 13 RssFeed sasu = new RssFeed("萨苏", ...); 14 RssFeed lvqiu = new RssFeed("闾丘露薇", ...); 15 RssFeed liang = new RssFeed("张靓颖", ...): 16 Profile.Feeds.Add(gnews); 17 //保存不同类型的子类对象 18 Profile.Feeds.Add(sinanews); 19 Profile.Feeds.Add(sasu); 20 Profile.Feeds.Add(lvqiu); 21 Profile.Feeds.Add(liang);
这里我们定义了两种不同类型的频道对象Atom和Rss,泛型集合中可以保存这两种不同类型的频道对象。在第6章中,我们曾经说过,原则上子类对象 是可以赋绐父类对象的,也可以说子类可以替换父类并且出现在父类能够出现的任何地方。但是反过来,父类对象是不能替换子类的。我们称这种特性为“里氏替换 原则(LSP)”。对于这个原则,通俗一些的理解就是,父类的方法都要在子类中实现或者重写。
里氏替换原则有以下两个关键的技术。
1.is操作符的使用
“is”操作符用于检查对象是否与给定的类型相同。主要的使用方法例如,判断一个object是否是字符串型。
if (obj 1s string) { }
这种用法我们在对频道类型的判断中已经使用过了,如示例2所示。
使用“is”操作符在进行类型判断时,如果所提供的对象可以强制转换为所提供的类型而不会导致引发异常,则is表达式的结果为true。
2.as操作符的使用
里氏替换原则还有一个关键技术“as”操作符,它用于两个对象之间的类型转换。如示例4所示,第4章学习ArrayList时,获取一个元素时需要类型转换,当时我们使用的是强制类型转换,现在使用as来进行类型转换。
1 //建立班级学员的集合 2 ArrayList Students = new ArrayList(); 3 Student scofield = new Student("Scofield", Genders.Male, 28, "越狱狱"); 4 Student zhang = new Student("张靓靓", Genders.Female, 20, "唱歌歌"); 5 Student jay = new Student("周杰杰", Genders.Male, 21, "耍双节棍棍"); 6 Students.Add(scofield); 7 Students.Add(zhang); 8 Students.Add(jay); 9 //通常使用的强制类型转换 10 foreach (Object stuo in Students) 11 { 12 Student stu = (Student)stuo; 13 Console.WriteLine(stu.Name); 14 } 15 //使用as进行类型转换 16 for (int i = 0; i < Students.Count; i++) 17 { 18 Student stu = Student[i] as Student; 19 Console.WriteLine(stu.Name); 20 }
as运算符类似于强制转换,所不同的是,当转换失败时,运算符将产生空,而不是引发异常。例如示例4,如果Students[i] as Teacher,则会返回一个null,不发生异常。