第一节:从面向对象思想(oo)开发、接口、抽象类以及二者比较
一. 面向对象思想
1. 面向过程(OP)和面向对象(OO)的区别:
(1):面向过程就是排着用最简单的代码一步一步写下去,没有封装,当业务复杂的时候,改动就很麻烦了
(2):面向对象将复杂的业务分离出来几个类,然后将业务封装到类中的方法进行实现,很简洁。
2. 面向对象的三大特征
(1). 封装:将一些行为以类为单位进行包裹起来,然后通过类进行调用(如People类),可以利用private、public、protected灵活控制属性的可访问性。
好处:
①:保证数据安全(灵活使用private、public、protected进行控制)
②:屏蔽细节,只要方法名不变,可以随意扩展。
③:代码重用
(2). 继承:通过继承,子类可以拥有父类的一切动作(如Student类继承People类)
(3). 多态:多态有很多种。,
(补充“老杨”的经典语录:父类类型的变量可以指向子类类型的对象,调用方法的时候调用的是子类对象的实现。里氏替换原则是多态的另一种叫法,如下面的例子④)
①:通过不同子类继承同一个父类,实现多态(类似的还有子类继承抽象类、或者实现接口)
②:方法的重载本身就是一种多态
③:利用默认参数实现方法多态(利用命名参数实现方法的重载,即方法的多态)
④:运行时的多态(里氏替换原则,声明父类对象,调用虚方法,在子类覆写或者不覆写的情况下,分别调用子类方法或父类方法《只有在运行的时候才知道》)
实体类代码:
1 /// <summary> 2 /// People对一些行为进行封装 3 /// </summary> 4 public class People 5 { 6 public int age { get; set; } 7 8 private string name { get; set; } 9 10 protected string Sex { get; set; } 11 12 13 public void Play() 14 { 15 Console.WriteLine("play方法"); 16 } 17 18 /// <summary> 19 /// 子类可以对其覆写也可以不进行覆写 20 /// </summary> 21 public virtual void VirtualMethord1() 22 { 23 Console.WriteLine("我是父类中的虚方法"); 24 } 25 } 26 27 /// <summary> 28 /// 继承People类 29 /// </summary> 30 public class Student : People 31 { 32 33 /// <summary> 34 /// overload 方法重载实现方法的多态,同名方法不同实现形式 35 /// </summary> 36 /// <param name="id"></param> 37 public void Show(int id) 38 { 39 Console.WriteLine($"id为:{id}"); 40 } 41 public void Show(string id) 42 { 43 Console.WriteLine($"id为:{id}"); 44 } 45 public void Show(int id, string name) 46 { 47 Console.WriteLine($"id为:{id},name为:{name}"); 48 } 49 50 /// <summary> 51 /// 利用命名参数实现方法的重载,即方法的多态 52 /// </summary> 53 /// <param name="id"></param> 54 /// <param name="name"></param> 55 public void play(int id,string name = null) 56 { 57 if (name!=null) 58 { 59 Console.WriteLine($"id为:{id},name为:{name}"); 60 } 61 else 62 { 63 Console.WriteLine($"id为:{id}"); 64 } 65 } 66 67 68 public override void VirtualMethord1() 69 { 70 //base.VirtualMethord1(); //调用父类的虚方法 71 Console.WriteLine("我是子类中对方法的覆写"); 72 } 73 74 } 75 /// <summary> 76 /// 继承People类 77 /// </summary> 78 public class Teacher : People 79 { 80 }
多态的代码:
1 Console.WriteLine("主要是介绍多态的各种形式"); 2 //1. 继承的方式实现多态,People有多种不同的实现形式(接口和抽象类的形式与之类似) 3 People p1 = new People(); 4 People p2 = new Student(); 5 People p3 = new Teacher(); 6 7 //2. 方法的重载也是一种多态,即同名方法不同的实现形式 8 Student s1 = new Student(); 9 s1.Show("1"); 10 s1.Show(2); 11 s1.Show(1, "ypf"); 12 13 //3. 利用命名参数实现方法的重载,即方法的多态 14 Student s2 = new Student(); 15 s2.play(12); 16 s2.play(12,"ypf"); 17 18 //4. 运行时的多态 19 People p4 = new Student(); 20 p4.VirtualMethord1();
调用结果:
二. 抽象类
抽象类是提供多个派生类共享基类的公共定义,它既可以提供抽象方法,也可以提供非抽象方法。抽象类不能实例化,必须通过继承由派生类实现其抽象方法,因此对抽象类不能使用new关键字,也不能被密封。如果派生类没有实现所有的抽象方法,则该派生类也必须声明为抽象类。另外,实现抽象方法由override方法来实现。
抽象类的特点:
(1). 抽象类是一个类,可以包含类的一切东西:属性、字段、委托、方法。
(2). 抽象方法必须包含在抽象类中,抽象方法没有方法体,但抽象类中还可以包含普通的非抽象方法。
(3). 抽象类和抽象方法的关键字均为abstract。
(4). 继承抽象类的子类中,必须显式的覆写(override)其父类中的所有抽象方法。
(5). 抽象类不能直接被实例化,声明的对象只能使用抽象类中已有的方法,不能使用子类新增的方法。
(6). 同一个类只能继承唯一一个父类。
1. 下面新建一个BasePhone类,该类中有属性、字段、委托、抽象方法、普通方法、虚方法、泛型抽象方法。
1 public abstract class BasePhone 2 { 3 //1.属性 4 public string id { get; set; } 5 public string name { get; set; } 6 //2.字段 7 public string userPwd = "maru"; 8 //3.委托 9 public delegate void DoNothing(); 10 //4.抽象方法 11 public abstract void Brand(); 12 public abstract void System(); 13 public abstract void Call(); 14 //5.普通方法(继承的子类无法对其进行覆写,可以对其进行重写) 15 public void show() 16 { 17 Console.WriteLine("这是父类show方法"); 18 } 19 //6.虚方法(可以被任何继承他的子类所覆写) 20 public virtual void ShowVirtual() 21 { 22 Console.WriteLine("这是父类showVirtual方法"); 23 } 24 //7.泛型方法抽象方法 25 public abstract void Do<T>(); 26 }
2. 新建一个iphone类,该类继承了BasePhone类,则继承了该类的属性、字段、和方法,同时必须对其抽象方法(包括泛型抽象方法)进行覆写,虚方法可以覆写也可以不进行覆写。
1 public class iphone : BasePhone 2 { 3 /// <summary> 4 /// 下面四个方法为对BasePhone中四个抽象方法的覆写 5 /// </summary> 6 public override void Brand() 7 { 8 Console.WriteLine("iphone品牌"); 9 } 10 11 public override void System() 12 { 13 Console.WriteLine("iphone系统"); 14 } 15 16 public override void Call() 17 { 18 Console.WriteLine("iphone电话"); 19 } 20 21 public override void Do<T>() 22 { 23 Console.WriteLine("iphone做的事情"); 24 } 25 /// <summary> 26 /// 下面的ITunes方法为子类特有的方法 27 /// </summary> 28 public void ITunes() 29 { 30 Console.WriteLine("iphone连接到ITunes上"); 31 } 32 /// <summary> 33 /// 下面的ShowVirtual方法覆写父类中的虚方法 34 /// </summary> 35 public override void ShowVirtual() 36 { 37 Console.WriteLine("这是子类的showVirtual方法"); 38 } 39 /// <summary> 40 /// 下面的show和父类中的一模一样,但是覆盖不了 41 /// </summary> 42 public void show() 43 { 44 Console.WriteLine("这是子类中的show方法"); 45 } 46 47 }
3. 利用面向抽象的编程思想和里氏替换原则实例化一个iphone实例,该实例可以调用子类中4个覆写父类中的抽象方法;可以调用子类对父类虚方法覆写的方法;但对普通方法show,即使在子类中重新声明了,无论加没加new关键字(重写 ),均是调用父类的方法;该实例不能调用子类中单独新增的方法。
1 { 2 Console.WriteLine("------------------------------ 一.抽象类的使用和特点 ---------------------------------------"); 3 //面向抽象编程 4 BasePhone iphone = new iphone(); //里氏替换原则 5 //1. iphone类中四个覆写的抽象方法 6 iphone.Brand(); 7 iphone.System(); 8 iphone.Call(); 9 iphone.Do<iphone>(); 10 //2. BasePhone中的两个方法 11 iphone.show(); //调用的是父类中的方法 12 iphone.ShowVirtual(); //调用的是子类覆写以后的方法 13 //3. 调用子类特有的方法(调用不了) 14 // iphone.ITunes(); 15 }
结果:
对比父类中的虚方法(virtual)和抽象方法(abstract)的区别:
(1). 抽象方法没有方法体,其继承子类必须对其进行覆写(override).
(2). 虚方法有方法体,其继承子类可以对其进行覆写(override),可以不进行覆写。若进行覆写,调用的则是子类中覆写后的方法;若不进行覆写,则调用的是父类 中的方法。
(3). 抽象方法的关键字是abstract,且必须存在于抽象类中;虚方法的关键字是virtual,可以存在于任何类中。
三. 接口
接口是包含一组虚方法的抽象类型,其中每一种方法都有其名称、参数、返回值。接口方法不能包含任何实现,CLR允许接口可以包含事件、属性、索引器、静态方法、静态字段、静态构造函数以及常数。但是接口不能包含任何静态成员。一个类可以实现多个接口,当一个类实现某个接口时,它不仅要实现该接口定义的所有方法,还要实现该接口从其他接口中继承的所有方法。
接口的特点:
(1). 接口不是类,里面可以包含属性、方法、事件,但不能包括字段和静态成员。
(2). 接口只能包含没有实现的方法。
(3). 子类实现接口,必须要实现该接口定义的所有方法,还要实现该接口从其他接口中继承的所有方法。
(4). 接口不能被直接实例化,声明的对象只能使用接口中的方法,不能使用子类新增的方法。
(5). 同一个类可以实现多个接口。
1. 下面新建两个接口IExtend和IPay,分别包含没有实现的方法Game和Play。
1 public interface IExtend 2 { 3 void Game(); 4 } 5 public interface IPay 6 { 7 void Play(); 8 }
2. 下面新建一个iphone子类,实现接口IExtend和IPay,则必须实现这两个接口中的所有方法。
1 public class iphone : IExtend, IPay 2 { 3 /// <summary> 4 /// 下面两个方法为显式的实现接口中的方法 5 /// </summary> 6 public void Play() 7 { 8 Console.WriteLine("这是子类显式实现了接口中的Play方法"); 9 } 10 11 public void Game() 12 { 13 Console.WriteLine("这是子类显式实现了接口中的Game方法"); 14 } 15 }
3. 下面分别用面向接口编程的方式和正常方式进行调用
1 { 2 Console.WriteLine("------------------------------ 二.接口的使用和特点 ---------------------------------------"); 3 //1.面向接口编程 4 Console.WriteLine("------------------------------ 1.面向接口编程 ---------------------------------------"); 5 IExtend iphone = new iphone(); 6 iphone.Game(); 7 IPay iphone2 = new iphone(); 8 iphone2.Play(); 9 //2.正常编程 10 Console.WriteLine("------------------------------ 2.正常编程 ---------------------------------------"); 11 iphone iphone3 = new iphone(); 12 iphone3.Play(); 13 iphone3.Game(); 14 }
运行结果:
四. 抽象类和接口的比较(搜集于网络)
(一).相同点和不同点
相同点
1. 都不能被直接实例化,都可以通过继承实现其抽象方法。
2. 都是面向抽象编程的技术基础,实现了诸多的设计模式。
不同点
1. 接口支持多继承;抽象类不能实现多继承。
2. 接口只能定义抽象规则;抽象类既可以定义规则,还可能提供已实现的成员。
3. 接口是一组行为规范;抽象类是一个不完全的类,着重族的概念。
4. 接口可以用于支持回调;抽象类不能实现回调,因为继承不支持。
5. 接口只包含方法、属性、索引器、事件的签名,但不能定义字段和包含实现的方法;抽象类可以定义字段、属性、包含有实现的方法。
6. 接口可以作用于值类型和引用类型;抽象类只能作用于引用类型。例如,Struct就可以继承接口,而不能继承类。
(二).规则与场合
1. 请记住,面向对象思想的一个最重要的原则就是:面向接口编程。
2. 借助接口和抽象类,23个设计模式中的很多思想被巧妙的实现了,我认为其精髓简单说来就是:面向抽象编程。
3. 抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能。
4. 接口着重于CAN-DO关系类型,而抽象类则偏重于IS-A式的关系;
5. 接口多定义对象的行为;抽象类多定义对象的属性;
6. 接口定义可以使用public、protected、internal 和private修饰符,但是几乎所有的接口都定义为public,原因就不必多说了。
7. 在接口中,所有的方法都默认为public。
8. “接口不变”,是应该考虑的重要因素。所以,在由接口增加扩展时,应该增加新的接口,而不能更改现有接口。
9. 尽量将接口设计成功能单一的功能块,以.NET Framework为例,IDisposable、IDisposable、IComparable、IEquatable、IEnumerable等都只包含一个公共方法。
10. 接口名称前面的大写字母“I”是一个约定,正如字段名以下划线开头一样,请坚持这些原则。
11. 如果预计会出现版本问题,可以创建“抽象类”。例如,创建了狗(Dog)、鸡(Chicken)和鸭(Duck),那么应该考虑抽象出动物(Animal)来应对以后可能出现风马牛的事情。而向接口中添加新成员则会强制要求修改所有派生类,并重新编译,所以版本式的问题最好以抽象类来实现。
12. 从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实实现。
13. 对抽象类不能使用new关键字,也不能被密封,原因是抽象类不能被实例化。
14. 在抽象方法声明中不能使用 static 或 virtual 修饰符。
(三). MSDN这样建议:
1. 如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单易行的方法来控制组件版本。通过更新基类,所有继承类都随更改自动更新。另一方面,接口一旦创建就不能更改。如果需要接口的新版本,必须创建一个全新的接口。
2. 如果创建的功能将在大范围的全异对象间使用,则使用接口。抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能。
3. 如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,则使用抽象类。
4. 如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。抽象类允许部分实现类,而接口不包含任何成员的实现。
(四). 经典示例
.NET Framework中关于接口和抽象类的使用:
集合类使用了基于接口的设计,请关注System.Collections中关于接口的设计实现;
数据流相关类使用了基于抽象类的设计,请关注System.IO.Stream类的抽象类设计机制。
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。